概要
特に制限しない限り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したなら、obj2
でundefined
になっている当該プロパティの値を再びnull
にしてやらなくてはならない。
想像
Google Apps ScriptがベースにしているバージョンのMozilla Rhinoのバグ。