bind、call、apply

73 阅读2分钟

不同点

fn.bind(obj)返回的是一个函数

fn.call(obj,parm1,parm2)返回的是一个处理结果

fn.apply(obj,[parm1,parm2])返回的是一个处理结果

相同点

它们共同的作用是改变函数里面 this 的指向

举例
var g = 3;        // 全局变量 window的属性

let obj1 = {
    g:30
}
let obj2 = {
    id: Symbol("ids"),
    g:300,
    c:function() {
        return this.g;
    }
}
/*常态下调用obj2的c方法*/
console.log(obj2.c(),"obj2.c()");

//调用obj2的c方法在链式调用bind方法
console.log(obj2.c.bind(window),"obj2.c.bind(window)")
console.log(obj2.c.bind(obj1),"obj2.c.bind(obj1)")

//调用obj2的c方法在链式调用call方法
console.log(obj2.c.call(obj1),"obj2.c.call(obj1)")

//调用obj2的c方法在链式调用apply方法
console.log(obj2.c.apply(obj1),"obj2.c.apply(obj1)")

如何传参
var g = 3;        // 全局变量 window的属性

let obj1 = {
    g:30
}

let obj2 = {
    id: Symbol("ids"),
    g:300,
    c:function(parms1,parms2) {
        console.log(parms1+parms2);
    }
}

//传参数给bind
obj2.c.bind(obj1)(1,1);

//传参数给call
obj2.c.call(obj1,2,2)

//传参数给apply
obj2.c.apply(obj1,[3,3])

bind的源码实现


    Function.prototype.bind_es5 = function (context) {

        if (typeof this !== "function") {
            throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);    // 获取bind函数传递过来的参数

        var f = function () {
            return self.apply(
                    this instanceof self ? this : context, 
                    args.concat(Array.prototype.slice.call(arguments))
                );
        }

        //f.prototype = this.prototype;  //不能直接这样赋值,因为后续你如果修改f.prototype ,那么 this.prototype也会被修改。

        // 解决方法:使用空函数来继承原型链
        // var Temp = function () {};
        // Temp.prototype = this.prototype;
        // f.prototype = new Temp();

        // 再进行优化的解决方法:使用Object.create();
        f.prototype = Object.create(this.prototype);
        return f;

    }

    function bar(a, b) {
        this.a = a;
        console.log(this.value);
        console.log(a);
        console.log(b);
    }
    bar.prototype.add = {

    }
    let obj = {
        value: 123
    }
    bar.bind(obj, 0)();
    let fn = bar.bind_es5(obj, 0);
    fn.prototype.doAdd = {            // 注意:这里修改了当前fn的原型
    }

    let F1 = bar.bind_es5(obj, 0);
    let f1 = new F1();
    console.log(f1)                   // 发现并没有受到影响。


call的源码实现

    // es5存在着属性被修改的可能性,以及使用了eval方法
    Function.prototype.call_ES5 = function(context){
        var obj = context || window;     // 获取对象
        var args = Array.prototype.slice.call(arguments,1); // 获取方法参数
        obj.fn = this;         // 给对象定义此方法
        var result = eval('obj.fn('+args.toString()+')');
        delete obj.fn;
        return result;
    }

    // 使用es6对属性进行改进,以及避免使用eval方法
    Function.prototype.call_ES6 = function(context){
        var obj = context || window;     // 获取对象
        var args = Array.prototype.slice.call(arguments,1); // 获取方法参数

        var key = Symbol('key');
        obj[key] = this;         // 给对象定义此方法
        var result = obj[key](...args);
        delete obj[key]
        return result;
    }


    function bar(a,b,c) {
        return {
            value:this.value,
            a:a,
            b:b
        }
    }
    var foo = {
        value: 1
    };

    console.log(bar.call_ES5(foo,1,2,3));
    console.log(bar.call_ES6(foo,1,2,3));

apply的源码实现

apply与call方法相似,只是它的参数问题,反而比call好写,而且不需要用到eval这个危险函数。