hiiragi's ブログ

主にコンピュータ関係の備忘録を書いてます

Function.prototype.bindを名前付き引数に対応させる

使えるかどうか分からないけど。
ネイティブメソッドは引数名が取得できないので名前付き引数のbindの有難みが無い…orz

/* bindAny(thisObj[, args][, namedArgs])
 * bindAny(thisObj[, namedArgs][, args]) // Syntax sugar
 */
Function.prototype.bindAny = function(thisObj)
{
    var orig = this;
    
    var boundArgs, boundNamedArgs;
    if (Array.isArray(arguments[1])) {
        boundArgs = arguments[1];
        boundNamedArgs = arguments[2];
    } else {
        boundNamedArgs = arguments[1];
        boundArgs = arguments[2];
    }
    if (!boundArgs) boundArgs = [];
    if (!boundNamedArgs) boundNamedArgs = {};
    
    /* build pre arguments
     * 
     * 元の関数の引数名の列を取得
     * nameに対応する位置にnamedArgs[name]を入れる
     */
    var argNames = orig.toString()
        .match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(/\s*,\s*/);
    var args = [];
    for (var i = 0, l = argNames.length; i < l; ++i) {
        var name = argNames[i];
        if (boundNamedArgs.hasOwnProperty(name)) {
            args[i] = boundNamedArgs[name];
        }
    }
    
    var slice = Array.prototype.slice, 
        news = [];
    function bound()
    {
        /* build actual arguments
         * 
         * pre-argumentsの先頭から順にundefinedにargsを入れる
         * 残ったargsは末尾に追加
         */
        var ta = boundArgs.concat(slice.call(arguments));
        var a = slice.call(args);
        for (var i = 0, l = a.length; 1 <= ta.length && i < l; ++i) {
            if (typeof a[i] === 'undefined')
                a[i] = ta.shift();
        }
        if (ta.length >= 1) a = a.concat(ta);
        
        if (this instanceof bound) { /* I am a constructor! */
            if (!news[l]) {
                var as = [];
                for (var i = 0, l = a.length; i < l; ++i)
                    as.push('a[' + i + ']');
                var evs = 'news[l]=function(){return new orig('
                    + as.join(',')
                    + ');};';
                eval(evs);
            }
            var ret = news[l]();
            if (typeof ret.__proto__ === 'object') {
                ret.__proto__ = bound.prototype;
            }
            return ret;
        } else {
            return orig.apply(thisObj, a);
        }
    }
    
    function fnop() {}
    fnop.prototype = orig.prototype;
    bound.prototype = new fnop();
    return bound;
};


動作確認

function f(a, b, c, d) {
    console.log(arguments);
}

f.bindAny(null, [1, 2], {c: 3, d: 4})(); // f(1, 2, 3, 4)
f.bindAny(null, [1, 2])();               // f(1, 2)
f.bindAny(null, [1, 2])(3, 4);           // f(1, 2, 3, 4)
f.bindAny(null, {c: 3, d: 4})(1, 2);     // f(1, 2, 3, 4)
f.bindAny(null, {c: 3, d: 4})();         // f(undefined, undefined, 3, 4)
f.bindAny(null, {c: 3})(1, 2, 4);        // f(1, 2, 3, 4)


参考