1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Google Apps ScriptでObject.seal()を使うとハマる

Last updated at Posted at 2019-11-28

概要

特に制限しない限りJavaScriptのオブジェクトには任意のプロパティを追加できるが、Object.seal()メソッドを使ってこれを抑制することができる。

Object.seal() メソッドは、オブジェクトを封印して、新しいオブジェクトを追加することを抑制し、すべての既存のプロパティを設定変更不可にします。現存するプロパティの値は、書き込み可能である限り変更できます。MDN web docs

例えばobj2 = Object.seal(obj1)とすると、obj2にプロパティを追加できなくなる。モダンなJavaScript実行環境において"use strict"でstrict modeにしていれば、プロパティの追加を試た時点でTypeError例外が発生する。

しかしGoogle Apps ScriptのObject.seal()はプロパティの追加を抑制するだけでなく、プロパティ値がnullのものをundefinedにしてしまう副作用がある。あと、GASのstrict-modeは中途半端なので信用できない。

JavaScriptの正しい挙動を確認

僕の認識が間違っているかもしれないので、まずはChromeのコンソールでJavaScriptの本来あるべき挙動を確認する。

sealされていないオブジェクトへのプロパティの追加 (Chrome)

function MyClass1() {
  this.prop1 = "hello";
  this.prop2 = null;
  this.prop3 = undefined;
  this.prop4 = 3.1415;
}//MyClass1

function myClass1Test(){
  var myClass1 = new MyClass1();
  myClass1.prop5 = "world"; // add new property
  var Logger = console;
  Logger.log(myClass1.prop1); //hello
  Logger.log(myClass1.prop2); //null
  Logger.log(myClass1.prop3); //undefined
  Logger.log(myClass1.prop4); //3.1415
  Logger.log(myClass1.prop5); //world <- the new property
}//myClass1Test

期待したとおり、sealされていないオブジェクトmyClass1に新しいプロパティprop5を追加することができた。

sealされたオブジェクトへのプロパティの追加 (Chrome)

function MyClass2() {
  this.prop1 = "hello";
  this.prop2 = null;
  this.prop3 = undefined;
  this.prop4 = 3.1415;
  return Object.seal(this);
}//MyClass2

function myClass2Test(){
  var myClass2 = new MyClass2();
  myClass2.prop5 = "world"; // adding a new property
  Logger.log(myClass2.prop1); //hello
  Logger.log(myClass2.prop2); //null
  Logger.log(myClass2.prop3); //undefined
  Logger.log(myClass2.prop4); //3.1415
  Logger.log(myClass2.prop5); //undefined <- as expected
  Logger.log(Object.keys(myClass2)); //[prop1, prop2, prop3, prop4]  
}//myClass2Test

期待したとおり、sealされたオブジェクトでmyClass2に新しいプロパティprop5を追加することはできない。non-strictモードなのでTypeError例外は発生せず一見するとworldを値として持つ新しいプロパティprop5を追加できたように見えるが、Object.keys()でプロパティ名を列挙してもprop5は存在せず、prop5の値を取得しようとするとundefinedである。

Google Apps Scriptの奇妙な挙動を確認

Google Apps Scriptでもsealすることによりプロパティの追加を抑制できる。しかしobj2 = Object.seal(obj1)するとobj1においてnullなプロパティ値がobj2においてundefinedにされてしまう。

function MyClass2() {
  this.prop1 = "hello";
  this.prop2 = null;
  this.prop3 = undefined;
  this.prop4 = 3.1415;
  var sealed = Object.seal(this);
  Logger.log(this.prop2); // undefined <- UNEXPECTED, should be null
  Logger.log(Object.keys(myClass2)); //[prop1, prop2, prop3, prop4]  
  return sealed;
}//MyClass2

function myClass2Test(){
  var myClass2 = new MyClass2();
  Logger.log(myClass2.prop2); // undefined <- UNEXPECTED!!

  myClass2.prop5 = "world";   // add new property
  Logger.log(myClass2.prop5); // undefined <- as expected
  Logger.log(Object.keys(myClass2)); //[prop1, prop2, prop3, prop4]

  myClass2.prop2 = null; // workaround
  Logger.log(myClass2.prop2); // null <- as expected
}//myClass2Test

コンストラクタfunction MyClass2を観察するとObject.seal()が返すオブジェクトではもともとnullだったプロパティ値がundefinedになっていることがわかる。プロパティが失われているわけではない。sealされたあとに再びobj2.prop2の値をnullにすることはできる。そしてそれがworkaroundである。

結論

Google Apps Scriptのobj2 = Object.seal(obj1)obj1のプロパティのうち値がnullであるものをすべてundefinedに書き換えて返す。したがって値がnullなプロパティを持つオブジェクトをsealしたなら、obj2undefinedになっている当該プロパティの値を再びnullにしてやらなくてはならない。

想像

Google Apps ScriptがベースにしているバージョンのMozilla Rhinoのバグ。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?