概要
ECMAScript (ECMAScript 2016)を元に「意図的に汎用」の意味を解説します。
Array.prototype.forEach
The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.
(意訳) 「forEach関数は意図的に汎用です。this
値がArrayオブジェクトである必要はありません。従って、別種のオブジェクトのメソッドとして使用する為に委譲することができます。」
例えば、次のように書けます。
var arrayLikeObject = {0: 1, 1: 2, 2: 3, length: 3}; // 配列ではないけど配列と同じプロパティを持つオブジェクト
Array.prototype.forEach.call(arrayLikeObject, function (value) {
console.log(value); // 1 -> 2 -> 3
});
また、次のように別のコンストラクタの prototype
に入れる事も出来ます。
function ArrayLike () {
for (var i = 0, len = arguments.length; i < len; ++i) {
this[i] = arguments[i];
}
this.length = arguments.length;
}
/**
* NOTE: サンプル故に簡略化しましたが、実用では Object.defineProperty で {enumerable: false} を指定する事を推奨します
* コード例として Array.prototype.concat の節を参照して下さい。
*/
ArrayLike.prototype.forEach = Array.prototype.forEach;
new ArrayLike('a', 'b', 'c').forEach(function (value) {
console.log(value); // "a" -> "b" -> "c"
});
Array.prototype.concat
NOTE2: The concat function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.
(意訳) 「concat関数は意図的に汎用です。this
値がArrayオブジェクトである必要はありません。従って、別種のオブジェクトのメソッドとして使用する為に委譲することができます。」
Array.prototype.forEach
と同様の記述です。同じようにコードを書いてみましょう。
var arrayLikeObject = {0: 1, 1: 2, 2: 3, length: 3}, // 配列ではないけど配列と同じプロパティを持つオブジェクト
array = Array.prototype.concat(arrayLikeObject, [4, 5, 6]);
console.log(JSON.stringify(array)); // [{"0":1,"1":2,"2":3,"length":3},4,5,6]
Array.prototype.concat
は Array.prototype.forEach
と違い、疑似配列を配列とみなしません。
従って、疑似配列コンストラクタの prototype
に入れても期待通りに動かない、という欠点があります。
function ArrayLike () {
for (var i = 0, len = arguments.length; i < len; ++i) {
this[i] = arguments[i];
}
this.length = arguments.length;
}
Object.defineProperty(ArrayLike.prototype, 'concat', {
writable: true,
configurable: true,
enumerable: false,
value: Array.prototype.concat
});
var arrayLike = new ArrayLike('a', 'b', 'c').concat({0: 'd', 1: 'e', length: 2});
console.log(JSON.stringify(arrayLike)); // [{"0":"a","1":"b","2":"c","length":3},{"0":"d","1":"e","length":2}]
Array.prototype.concat
には次の性質があります。
-
this
値が配列でなくても良い -
this
値が配列でなかった場合、配列としては扱わず、配列内の要素として扱う - 返り値は配列である (※上記コードでは
ArrayLike
のインスタンスとはならない)
従って、他のコンストラクタの prototype
に入れる関数としては不向きで互換コードを自前で書く必要があります。
まとめ
ECMAScript 規定のメソッドの大半は「意図的に汎用」に設計されています。
汎用性の度合いに違いはあれど、関数を単体として利用したり、他のコンストラクタの prototype
に入れる事を考慮した設計になっています。
もし、prototype上にユーザ定義関数を作ろうとするなら、「汎用性」をどこまで維持するか、考えてみると面白いかもしれません。