# bind、call、apply的实现

这3个方法,其实都在Function的原型链上。

# bind实现

很简单,第一个参数是作用域,后面如果还有的话,就是函数前面的参数。其实它应该也算是柯里化的一种应用。

这是ES5的写法:

Function.prototype.bind = function(scope){
    var fn = this;
    var args = [].slice.call(arguments);
    args.shift();
    return function(){
        return fn.apply(scope, args.concat([].slice.call(arguments)));
    };
};

这是ES6的写法:

Function.prototype.bind = function(scope, ...args){
    var fn = this;
    return function(...args2){
        return fn.apply(scope, args.concat(args2));
    };
};

测试:

function Foo(age) {
    console.log(this.name);
    console.log(age);
}

var nf = Foo.bind({name: 'abc'});
nf(18);

var nf2 = Foo.bind({name: 'abc'}, 22);
nf2();

# apply实现

这是用ES6call的实现:

Function.prototype.apply = function (scope, args) {
    return this.call(scope, ...args);
};

如果能使用bind的话,也很简单:

Function.prototype.apply = function(scope, args){
    var fn = this;
    return fn.bind(scope)(...args);
};

ES5里,如果不能用call,则要麻烦多了。思路是使用eval函数,把函数本身赋给scope,再执行,最终删除新加的属性。

Function.prototype.apply = function (scope, arr) {
    scope = scope || window;
    var args = [];
    for (var i = 0; i < arr.length; i++) {
        var arg = arr[i];
        if (typeof arg === 'string') {
            args.push('"' + arg + '"');
        } else if (typeof arg === 'object') {
            scope['arg' + i] = arg;
            args.push('scope["arg' + i + '"]');
        } else {
            args.push(arg);
        }
    }

    scope.fn = this;
    var res = eval('scope.fn(' + args.join(',') + ')');
    delete scope.fn;
    for (var key in scope) {
        if (key.startsWith('arg')) {
            delete scope[key];
        }
    }
    return res;
};

测试:

function Foo(age, sex, options) {
    console.log(this.name);
    console.log(age);
    console.log(sex);
    console.log(options)
    return 'abcdefg'
}
var res = Foo.apply({ name: 'def' }, [45, 'man', { email: 'ss' }]);
console.log(res);

# call实现

实现了applycall就容易了。

如果能使用bind的话,call很简单:

Function.prototype.call = function(scope, ...args){
    var fn = this;
    return fn.bind(scope)(...args);
};

使用apply的方式:

Function.prototype.call = function(scope){
    var fn = this;
    var args = [].slice.apply(arguments);
    args.shift();
    return fn.apply(scope, args);
};

如果用ES6,可以这样:

Function.prototype.call = function(scope, ...args){
    scope.fn = this;
    var res = scope.fn(...args);
    delete scope.fn;
    return res;
};

ES5里,如果不能用apply,大致与上面实现差不多,这里就赘述了。

测试:

var res = Foo.call({ name: 'def' }, 45, 'man', { email: 'ss' });
console.log(res);