LoginSignup
12
4

More than 5 years have passed since last update.

[JavaScript] 意図的に汎用な関数

Posted at

概要

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.concatArray.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上にユーザ定義関数を作ろうとするなら、「汎用性」をどこまで維持するか、考えてみると面白いかもしれません。

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4