Symbol.unscopables

Symbol.unscopables は静的データプロパティで、ウェルノウンシンボルである Symbol.unscopables を表します。with 文はスコープオブジェクト上で、その with 環境内でバインドから除外されるプロパティの集合を持つプロパティを、このシンボルで検索します。

試してみましょう

ウェルノウンシンボル Symbol.unscopables です。

Symbol.unscopables のプロパティ属性
書込可能不可
列挙可能不可
設定可能不可

解説

[Symbol.unscopables] シンボル(Symbol.unscopables でアクセス)は、with 環境バインドでプロパティ名が字句変数として公開されるのを除外するために、任意のオブジェクトに定義することができます。なお、厳格モードでは、with 文は使用できず、このシンボルは必要ありません。

[Symbol.unscopables] オブジェクトのプロパティを true (または任意の真値)に設定すると、with スコープオブジェクトの対応するプロパティをスコープ対象外するため、with 本体スコープに導入されません。プロパティを false (または偽値)に設定すると、スコープ対象となり、字句スコープ変数として現れます。

x がスコープ対象外かどうかを判断するとき、[Symbol.unscopables] プロパティのプロトタイプチェーン全体に対して x というプロパティを呼び出します。つまり、[Symbol.unscopables] をプレーンオブジェクトとして宣言した場合、Object.prototype のプロパティ(例えば toString など)もスコープ対象外になり、これらのプロパティが通常スコープされていると想定している古いコードでは後方互換性が発生する可能性があるということです(下記の例を参照してください)。独自の [Symbol.unscopables] プロパティでは、そのプロトタイプとして null を持つようにすることをお勧めします(例えば Array.prototype[Symbol.unscopables] がそうなっています)。

このプロトコルは、DOM API (Element.prototype.append() など)でも利用されています。

with 文内のスコープ

次のコードは、ES5 以下であれば正しく動作します。しかし、 ECMAScript 2015 以降では、Array.prototype.values() メソッドが導入されました。これは、with 環境内で "values" はメソッドになり、with 文の外の変数ではなくなったということです。

js
var values = [];

with (values) {
  // [Symbol.unscopables] が存在しない場合、ECMAScript 2015 から
  // 値は Array.prototype.values になります。
  // そのため、エラーが発生します。
  values.push("something");
}

この with (values) を含むコードは、Array.prototype.values() が追加されたとき、Firefox において一部のウェブサイトで不具合が発生しました(Firefox Bug 883914)。さらに、将来配列メソッドが追加された場合、それが暗黙的に with スコープを変更すると壊れる可能性があることになります。そのため、Array.prototype[Symbol.unscopables] というシンボルが導入され、ArrayArray.prototype[Symbol.unscopables] として実装され、いくつかの Array メソッドが with 文にスコープされるのを防ぎます。

オブジェクト内の unscopables

自分のオブジェクトに [Symbol.unscopables] を設定することもできます。

js
const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

obj[Symbol.unscopables] = {
  // オブジェクトに `null` プロトタイプを持たせて、
  // `Object.prototype` メソッドがスコープから外れないようにする
  __proto__: null,
  // `foo` はスコープ対象
  foo: false,
  // `bar` はスコープ対象外
  bar: true,
  // `baz` は省略。`undefined` は偽値なので、これもスコープ対象(既定値)
};

with (obj) {
  console.log(foo); // 1
  console.log(bar); // ReferenceError: bar is not defined
  console.log(baz); // 3
}

プロトタイプが null ではないオブジェクトを [Symbol.unscopables] として使うのは避ける

[Symbol.unscopables] のプロトタイプを削除せずに、プレーンオブジェクトとして宣言すると、微妙なバグを発生させる可能性があります。[Symbol.unscopables] の前に動作する次のコードを考えてみましょう。

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
};

with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "Use with statements, you must not"
}

後方互換性を保つために、character にプロパティを追加するときに [Symbol.unscopables] プロパティを追加することにしました。ナイーブにこうやるかもしれません。

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // Make `student` unscopable
    student: true,
  },
};

しかし、上のコードは次のようにすると壊れてしまいます。

js
with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "[object Undefined]"
}

これは character[Symbol.unscopables].toString を探すと、真値である Object.prototype.toString() を返しているため、with() 文の中の toString() 呼び出しを、globalThis.toString() を参照させるためです。また、this なしで呼び出されているため、thisundefined であり、[object Undefined] を返しています。

このメソッドが character でオーバーライドされていない場合でも、これをスコープ不能にすることで this の値が変更されます。

js
const proto = {};
const obj = { __proto__: proto };

with (proto) {
  console.log(isPrototypeOf(obj)); // true; `isPrototypeOf` がスコープされ `this` は `proto` になる
}

proto[Symbol.unscopables] = {};

with (proto) {
  console.log(isPrototypeOf(obj)); // TypeError: Cannot convert undefined or null to object
  // `isPrototypeOf` はスコープされず `this` は undefined になる
}

これを修正するには、常に [Symbol.unscopables] が、Object.prototype プロパティを含まない、スコープ対象外にしたいプロパティのみを持つするようにしてください。

js
const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // オブジェクトに `null` プロトタイプを持たせて、
    // `Object.prototype` メソッドがスコープから外れないようにする
    __proto__: null,
    // `student` をスコープ対象外にする
    student: true,
  },
};

仕様書

Specification
ECMAScript Language Specification
# sec-symbol.unscopables

ブラウザーの互換性

BCD tables only load in the browser

関連情報