ES6 Symbol を使ってオレオレメソッドを拡張する

More than 3 years have passed since last update.

ECMAScript6にシンボルができた理由 で紹介されている Symbol の使い方を見て、fmfm するためのエントリです。


似て非なる 2つの NativeObject 拡張ライブラリを同時に読み込むとどうなるか

Array に JSON.stringify(Array) 相当の機能を拡張するライブラリが2つ(AwesomeArray.js, ExArray.js)あります。これらを読み込むと Array#toJSON が拡張されます。

AwesomeArray.js と ExArray.js には、カンマの後ろにスペースがある/ない という違いがあります。


ES5 式

ES5 で書かれた AwesomeArray.js と ExArray.js です。

ES5 までは、AwesomeArray.js を読み込んだ後に ExArray.js を読み込むと、AwesomeArray.js で拡張した Array#toJSON が ExArray.js のそれに上書きされてしまいます。

AwesomeArray.js と ExArray.js が混在してしまう状況では、最終的にどちらが機能するかは、読み込み順に依存することになりカオスでした。

// AwesomeArray.js

(function(global) {
Array.prototype.toJSON = function() {
return "[" + this.join(",") + "]"; // without space
};
})(this);

// ExArray.js

(function(global) {
Array.prototype.toJSON = function() {
return "[" + this.join(", ") + "]"; // with space
};
})(this);

var array = [1,2,3];

console.log(array.toJSON()); // -> [1, 2, 3] (ExArray.js の Array#toJSON が機能している)


ES6 式

ES6 からは、Symbol を使う事でこれら2つのライブラリが共存可能になり、呼び分ける事も可能になります。

// AwesomeArray.js

(function(global) {
// シンボルを作成します。文字列 "AwesomeArray Array#toJSON" はデバッグ時の識別用です
var toJSON = Symbol("AwesomeArray Array#toJSON");

// JSON.stringify(Array) 相当のメソッドを追加します。
// Symbol で追加するため for in などの enum ではメソッド名は列挙されません
Array.prototype[toJSON] = function() {
return "[" + this.join(",") + "]"; // without space
};

// Symbol を export しライブラリの外から呼び出し可能にします
global.AwesomeArray = { "toJSON": toJSON }; // export Symbol
})(this);

// ExArray.js

(function(global) {
// シンボルを作成します。文字列 "ExArray Array#toJSON" はデバッグ時の識別用です
var toJSON = Symbol("ExArray Array#toJSON");

// JSON.stringify(Array) 相当のメソッドを追加します。
// Symbol で追加するため for in などの enum ではメソッド名は列挙されません
Array.prototype[toJSON] = function() {
return "[" + this.join(", ") + "]"; // with space
};

// Symbol を export しライブラリの外から呼び出し可能にします
global.ExArray = { "toJSON": toJSON }; // export Symbol
})(this);


Test

AwesomeArray.js と ExArray.js を読み込み、

AwesomeArray.js で追加した Array#toJSON と ExArray.js で追加した Array#toJSON が共存できているかテストします。

require("AwesomeArray.js");

require("ExArray.js");

var array = [1,2,3];
var toJSON = AwesomeArray.toJSON;

console.log( array[AwesomeArray.toJSON]() ); // -> "[1,2,3]"
console.log( array[ExArray.toJSON]() ); // -> "[1, 2, 3]"
console.log( array[toJSON]() ); // -> "[1,2,3]"


Object.getOwnPropertySymbols

for ... inObject.getOwnPropertyNames で Symbol が見えない事を確認します

console.log( Object.getOwnPropertyNames(Array.prototype) );

// -> [ "length", "constructor", "toString", "toLocaleString",
// "join", "pop", "push", "concat", "reverse", "shift",
// "unshift", "slice", "splice", "sort", "filter", "forEach",
// "some", "every", "map", "indexOf", "lastIndexOf", "reduce",
// "reduceRight", "entries", "keys", "copyWithin", "find",
// "findIndex", "fill", "includes" ]

Object.getOwnPropertySymbols で、Symbol を可視化できます。

console.log( Object.getOwnPropertySymbols(Array.prototype) );

// -> [ Symbol(Symbol.unscopables),
// Symbol(Symbol.iterator),
// Symbol(AwesomeArray Array#toJSON), <- 追加されている
// Symbol(ExArray Array#toJSON) ] <- 追加されている