ECMAScript 5 規定の undefined は書き換え不可能

概要

次のようなコードをよく見ます。

undefined = 1;      // 1

(function () {
  var undefined;    // 同名のローカル変数を定義

  alert(undefined); // undefined
})();

ところが、ECMAScript 5.1 規定の undefined は書き換え不可能([[Writable]]: false)です。

15.1.1.3 undefined # Ⓣ Ⓡ
The value of undefined is undefined (see 8.1). This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

http://es5.github.com/#x15.1.1.3

従って、ローカル変数を定義する必要がなくなります。

undefined = 1;    // 1
alert(undefined); // undefined (OK = Firefox 5 / NG = Google Chrome 12, Opera 11.50, IE8)

ローカル変数 undefined を定義する手法の問題点

ローカル変数 undefined は [[Writable]]: true ですので、ローカル変数定義後に値を書き換えることを許してしまいます。

undefined = 1;      // 1
alert(undefined);   // undefined

(function () {
  var undefined;    // 同名のローカル変数を定義

  undefined = 1;
  alert(undefined); // 1
})();

書き換え不可能なグローバル変数を定義する

ECMAScript 5.1 規定の Object.defineProperty を実装しているブラウザなら書き換え不可能なプロパティ undefined を定義できます。

/**
 * グローバルコード
 */
if (typeof Object.defineProperty === 'function') {
  Object.defineProperty(this, 'undefined', {value: void 0, writable: false, enumerable: false}); // [[Writable]]: false, [[Enumerable]]: false
} else {
  undefined = void 0;
}

undefined = 1;    // 1
alert(undefined); // undefined

Tweetログ

備忘録です。


think49
GC12 のグローバル変数 undefined は [[Writable]]: false ではない模様。 undefined = 1; alert(undefined); // 1
think49
Object.defineProperty(this, 'undefined', {configurable: true}); // TypeError: Cannot redefine property: defineProperty (GC12)
think49
残念ながら、既存の undefined に [[Writable]]: false を付加させるのは無理みたい。書き換え可能なのに definedProperty で再定義できないとは/
Object.defineProperty(this, "undefined", { writable: false }); の間違いではないでしょうかー?
configurable:falseのときにwritableをtrueからfalseにのみ書き換え可能なのはsemantics的な理由もあるけどもっと実用的な理由としてはObject.sealした後にfreezeしても問題ないようにというのが. とECMA262厨発言をしつつ.
think49
うむー。何か勘違いしてるかもしれないですが、MDC によると writable: false は規定値なので140文字の都合上省略してしまいました。仕様は読んでなかったので間違ってるかも…。
think49
実際は {value: void 0, writable: false, enumerable: false} ですが、期待通りに動作しませんでした。
think49
で、configurable: true が既存のプロパティ定義に必要なのかなと当たりをつけて試しましたが、動作せず。 「さて、どうしたものか?」って状況ですー。
undefinedがwirtable:falseでない点については仕様違反であっています. 今のcodeだと, GCの場合undefined = 1のおかげでtypeof undefiendが"number"になって, そちらのpathに入っていないですー.
think49
defineProperty の Attributes の解説は か。HasProperty が false を返す場合は何もしないわけだから false または undefined が規定値になる、と。
属性値のみ書き換える場合はGenericDescriptorを指定するといいです. この場合writableの値だけ変更したいのであれば, defineProperty(this, "undefined", { writable : false })ですね.
think49
ああ、わかりましたです。期待通りに動かないのは "undefind" の typo が原因ですね…。OTL お騒がせしました。
think49
GenericDescriptor はまだよくわかっていないのですが、例示されたコードから推測するに未指定のプロパティは既存の値をそのまま使う仕様になってる、という理解で合ってるでしょうか?
正確にはGenericDescriptorではないのですが><(writableがあるので), {writable: false}の場合, configurable, enumerableはabsentになります.
ものすごーく大雑把に言うと, absentは対象がすでに定義済みproperty descriptorの時は影響を与えないようにする値になりますー. つまり対象descriptorの値そのままということですねー.
think49
なるほど。ES5 も併せて読んでおぼろげながら理解できました。ありがとうございます。
think49
あれ…、となると MDN の writable の項の "Defaults to false." は間違いな気がする。
think49
やっぱり間違いなので先の3件ツイートは削除…。よく読まなきゃ。
think49
"Table 7 — Default Attribute Values" にデフォルト値がのってた。/
8.6.1 Property Attributes - Annotated ES5
think49
var obj = {};
Object.defineProperty(obj, 'hoge', {value: 'hoge'}); // [[Writable]]: false (default)
obj.hoge = 1;
obj.hoge; // hoge
think49
ようやく理解できた!規定値になる条件は「既存プロパティの再定義でなく、Attributes でプロパティを指定しなかった時」なんだなー。