Array/Stringの汎用メソッドをこだわって定義する
FirefoxではArray.prototypeやString.prototypeにあるメソッドを簡単に転用できるように、コンストラクタ(Array, String)自身に汎用メソッド(generic method)が定義されています。
これにより、
Array.prototype.forEach.call(nodeList, body.appendChild, body)
はArray.forEach(nodeList, body.appendChild, body)
と書くことができるArray.prototype.slice.call(arguments, 1)
はArray.slice(arguments, 1)
と書くことができる
ところが、これはECMAScript標準ではないので、Firefox以外では定義されていません。Array | MDNに次のようなshimが用意されています。
/*globals define*/ // Assumes Array extras already present (one may use shims for these as well) (function () { 'use strict'; var i, // We could also build the array of methods with the following, but the // getOwnPropertyNames() method is non-shimable: // Object.getOwnPropertyNames(Array).filter(function (methodName) {return typeof Array[methodName] === 'function'}); methods = [ 'join', 'reverse', 'sort', 'push', 'pop', 'shift', 'unshift', 'splice', 'concat', 'slice', 'indexOf', 'lastIndexOf', 'forEach', 'map', 'reduce', 'reduceRight', 'filter', 'some', 'every', 'isArray' ], methodCount = methods.length, assignArrayGeneric = function (methodName) { var method = Array.prototype[methodName]; Array[methodName] = function (arg1) { return method.apply(arg1, Array.prototype.slice.call(arguments, 1)); }; }; for (i = 0; i < methodCount; i++) { assignArrayGeneric(methods[i]); } }());
isArrayは消さなければなりません。ミスだと思います。使う分にはこれで十分ですが、次の点が気になります。
- 定義される関数は無名関数(nameなし)なので、デバッグ時のコールスタックが嫌な感じになる
- 定義される関数のlengthプロパティが0なので、lengthプロパティを使う何か(例えばカリー化)に使えない
そこで、evalなりFunctionコンストラクタなりを使って、関数名と引数を合わせるように定義することを考えます。
var call = Function.prototype.call; function assignArrayGeneric(methodName) { var method = Array.prototype[methodName]; var args = generateArgsString(method.length + 1); Array[methodName] = new Function('call', 'method', 'return function ' + methodName + '(' + args + ') { return call.apply(method, arguments); };' )(call, method); } function generateArgsString(length) { var args = []; var i; for (i = 0; i < length; ++i) { args[i] = '$' + i; } return args.join(', '); }
引数の数は、this
相当の引数を受け取る分、+ 1
しています(Firefoxに合わせています)。これで、例えば Array.forEach
には function forEach($0, $1) { return call.apply(method, arguments); }
が定義されます(いわゆるクロージャにより、call
はFunction.prototype.call
、method
はArray.prototype.forEach
を指します)。関数名もlengthプロパティも、外部から使う分には十分になりました。
でも、欲が出てきて、今度は引数名が機械的な $0, $1
である点が気になりました。動的解析に対応したIDEで、$0, $1
よりは array, callbackfn
と表示された方が嬉しいです。
Functionコンストラクタを使うのもどうかと思うので、ES5の仕様書を見ながらそれぞれ書きました。
ご自由にお使いください。