Symbol
シンボル
シンボル(Symbol)は、ES2015(ES6) で追加された新たなプリミティブ型です。
Symbol([description])
シンボルを作成します。description にはデバッグの際にシンボルを見分けるための文字列を指定します。new は使用しません。
const sym1 = Symbol();
const sym2 = Symbol("foo");
description に同じ名前を指定しても、Symbol() は毎回新しいオブジェクトを生成します。下記の実行結果は false となります。
const sym1 = Symbol("foo");
const sym2 = Symbol("foo");
console.log(sym1 === sym2); // => false
シンボルは辞書のキーとして使用することができます。
const sym1 = Symbol("foo");
const sym2 = Symbol("baa");
var obj = {
[sym1]: "Yamada",
[sym2]: function() {
console.log(this[sym1]);
}
}
obj[sym2](); // => "Yamada"
シンボルの使い道
シンボルは互換性を維持したまま、オブジェクトに新たな機能やプロパティを追加するために考案されました。例えば、ES2015(ES6) で追加された for ... of ... 構文は、オブジェクトのイテレーターメソッドを呼び出します。イテレーターメソッドを iterator() としてしまうと、すでに iterator() という名前のメソッドを独自実装しているプログラムに影響を与えてしまいます。PHP では、__ で始まるメソッドは将来の言語拡張で利用される可能性があると明示しているので、__iterator() とすればよいのですが、JavaScript ではそのようなルールを決めていなかったため、シンボルを用いて [Symbol.iterator]() とあらわすようにしました。
JavaScript の仕様追加によって作成されたシンボルは 「Well-knownシンボル」 として定義されます。
自己開発しているオブジェクトでシンボルを利用するケースはほとんど無いのですが、下記の様に、標準オブジェクト String に hello() メソッドを追加する場合など、メソッドをシンボルで識別することにより、メソッド名の重複を気にすることなく、拡張が可能となります。
const hello = Symbol("hello");
String.prototype[hello] = function() { console.log("Hello " + this); }
export { hello as default };
const hello = Symbol("hello");
String.prototype[hello] = function() { console.log("Hello " + this + "!!!"); }
export { hello as default };
import hello1 from "./hello1.js"; import hello2 from "./hello2.js"; "Tanaka"[hello1](); // => "Hello Tanaka" "Tanaka"[hello2](); // => "Hello Tanaka!!!"
<script type="module" src="sample.js"></script>
プロパティ
Symbol.length
常に 0 を返します。
console.log(Symbol.length); // => 0
Symbol.prototype
シンボルのプロトタイプを返します。
symbol.description
シンボルの description を取得します。ES2019(ES10) で追加されました。
const sym1 = Symbol("foo");
console.log(sym1.description); // => "foo"
メソッド
Symbol.for(key)
key で識別されるグローバルシンボルを生成します。key に対応するグローバルシンボルがすでに存在する場合はそれを返します。下記の例は true となります。
const sym1 = Symbol.for("foo");
const sym2 = Symbol.for("foo");
console.log(sym1 === sym2); // => true
Symbol.keyFor(sym)
グローバルシンボル sym を引数とし、グローバルシンボルのキーを取得します。
const sym1 = Symbol.for("foo");
console.log(Symbol.keyFor(sym1)); // => "foo"
Well-knownシンボル
Symbol.iterator
for ... of ... で参照されるイテレーターメソッドを指定するシンボルです。
class MyClass {
constructor(name) {
this.name = name;
}
*[Symbol.iterator]() {
for (var i = 0; i < this.name.length; i++) {
yield this.name.charAt(i);
}
}
}
var obj = new MyClass("Yamada");
for (o of obj) {
console.log(o); // => "Y", "a", "m", "a", "d", "a"
}
Symbol.asyncIterator
非同期の 反復可能オブジェクト で使用されるシンボルです。
const myAsyncIterableObject = {
async *[Symbol.asyncIterator]() {
yield "A";
yield "B";
yield "C";
},
};
(async () => {
for await (const x of myAsyncIterableObject) {
console.log(x);
}
})();
Symbol.match
string.startsWith(), string.endsWith(), string.includes() メソッドは、引数が正規表現の場合に TypeError を返しますが、Symbol.match が flase に設定されたオブジェクトは、正規表現とはみなされなくなります。
var re1 = /foo/;
var re2 = /baa/;
re1[Symbol.match] = false;
console.log("/foo/".startsWith(re1)); // => true
console.log("/baa/".startsWith(re2)); // => TypeError
Symbol.replace
string.replace() から呼び出されるリプレースメソッドを指定します。
var upper = {};
upper[Symbol.replace] = (str) => str.toUpperCase();
console.log('xyz'.replace(upper));
Symbol.search
string.search() から呼び出されるサーチメソッドを指定します。
var mysearch = {searchStr: "Y"};
mysearch[Symbol.search] = (target) => target.search(this.searchStr);
console.log("XYZ".search(mysearch)); // => 1
Symbol.split
string.split() から呼び出されるスプリットメソッドを指定します。
var mysplit = {delimiter: "/"};
mysplit[Symbol.split] = (target) => target.split(this.delimiter);
console.log("2019/12/01".split(mysplit)); // => ["2019", "12", "01]
Symbol.hasInstance
instanceof から呼び出される判定メソッドを指定します。
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // => true
Symbol.isConcatSpreadable
array.concat() において、オブジェクトが concat() のための平坦化が可能か否かを示します。
var arr1 = ["A", "B", "C"]; var arr2 = [3, 4, 5] console.log(arr1.concat(arr2)); // => ["A", "B", "C", 3, 4, 5] arr2[Symbol.isConcatSpreadable] = false; console.log(arr1.concat(arr2)); // => ["A", "B", "C", [3, 4, 5]]
Symbol.unscopables
with において、オブジェクトのプロパティがスコープ対象になるか否かを指定します。下記の場合、foo は対象、baa は非対象となります。
var obj = {foo: 1, baa: 2};
obj[Symbol.unscopables] = {foo: false, baa: true};
with (obj) {
console.log(foo); // => 1
console.log(baa); // => ReferenceError
}
Symbol.species
オブジェクトのデフォルトコンストラクタを指定します。下記の例で、Symbol.species を指定しない場合は、.map() メソッドはデフォルトコンストラクタである MyClass オブジェクトを返却しますが、species を Array のコンストラクタで上書きすることにより、MyArray オブジェクトではなく、Array オブジェクトを返却するようになります。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
let arr1 = new MyArray(1, 2, 3);
let arr2 = arr1.map((x) => x * 2);
console.log(arr1); // => MyArray(1, 2, 3)
console.log(arr2); // => Array(1, 2, 3)
Symbol.toPrimitive
オブジェクトをプリミティブ値に変換するための変換メソッドを指定します。hint には、"default", "string", "number" いずれかの値が入ります。
class MyClass {
constructor(str) {
this.value = str;
}
[Symbol.toPrimitive](hint) {
if (hint == "string") {
return String(this.value);
} else if (hint == "number") {
return Number(this.value);
} else {
return this.value;
}
}
}
var obj = new MyClass("123");
console.log("" + obj); // => hint == "default"
console.log(`${obj}`); // => hint == "string"
console.log(+obj); // => hint == "number"
Symbol.toStringTag
Object.prototype.toString() で参照されるオブジェクトの説明文字列を指定します。
class MyClass {
get [Symbol.toStringTag]() {
return "MyClass";
}
}
var obj = new MyClass();
console.log(Object.prototype.toString.call(obj)); // => "[object MyClass]"
Symbol.matchAll
str.matchAll() が内部的に呼び出すメソッドを示すシンボルです。
const result = "2024-12-31".matchAll(/[0-9]+/g);
console.log(Array.from(result, v => v[0])); // => ['2024', '12', '31']
という処理は内部的には下記のような [Symbol.matchAll]() メソッドを呼び出して実現しています。
const re = {
*[Symbol.matchAll](str) {
for (const n of str.matchAll(/[0-9]+/g)) { yield n; }
}
}
const result = re[Symbol.matchAll]("2024-12-31");
console.log(Array.from(result, v => v[0])); // => ['2024', '12', '31']