Call-apply-bind实现

2020-10-27  本文已影响0人  Raral

call apply bind 原理解析和实现

call

概念
简单来说就是当我执行 A 方法的时候,希望通过传入参数的形式将一个对象 B 传进去,用以将 A 方法的作用域对象替换为对象 B。

代码

var value = "全局的value";
    let tempObj = {
        value: "指定对象的value"
    }

    function test() {
        console.log(arguments); //类数组
        console.log(this);
        console.log("输出:" + this.value);
    }
    test(); //输出:全局的value
    console.dir(test)
    test.call(tempObj, 1, 2, 3); //输出:指定对象的value

应用场景(es5构造函数的继承)

function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    function Student(name, age, score) {
        //调用Person的call方法把当前this传入替换调Person内部this作用域对象。
        Person.call(this, name, age);
        this.score = score;
    }
    var s = new Student("lisi", 12, 100);
    console.log(s) //{name: "lisi", age: 12, score: 100}

功能实现和思路

  1. 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
  2. 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
  3. 截取作用域对象参数后面的参数
  4. 执行调用函数,记录拿取返回值
  5. 销毁调用函数,以免作用域污染

具体实现

Function.prototype.mCall = function(context) {
        // 1. 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
        console.log(this);
        context = context || window;
        // 2.绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来临时挂在参数里)
        context.fn = this; //
        // 3.截取作用域对象参数后的参数
        let args = [...arguments].slice(1);
        console.log(args instanceof Array);

        // 4.执行调用函数就是第二步保存的fn,记录返回值

        let result = context.fn(...args);
        // 5.销毁调用函数,以免作用域污染;
        Reflect.deleteProperty(context, 'fn');
        return result;
    }
    test.mCall(tempObj, 1, 2, 3);//输出:指定对象的value

apply

使用过的人应该都知道,apply 和 call 的功能完全一致,区别唯有使用上的一丝丝差别

Function.prototype.call = function(context, args1, args2, args3 ...);

Function.prototype.apply = function(context, [args1, args2, args3 ...]);

具体实现

 Function.prototype.mApply = function(context) {
        context = context || window;
        context.fn = this;
        let result;
        if (arguments[1]) {
            result = context.fn(...arguments[1]);
        } else {
            result = context.fn();
        }
        Reflect.deleteProperty(context, "fn");
        return result;
    }

    test.mApply(tempObj, [1, 2, 3]);

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用

这同样是 MDN 上给出的解释,意思应该已经很明显了,和 call 方法类似,调用是都是将内部的 this 作用域对象替换为第一个参数,不过需要注意开始和结尾,调用 bind 方法时会创建一个新的函数返回待调用

具体实现

Function.prototype.mBind = function (context) {
    // 获取绑定时的传参
    let args = [...arguments].slice(1),
        // 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
        F = function () {},
        // 记录调用函数,生成闭包,用于返回函数被调用时执行
        self = this,
        // 定义返回(绑定)函数
        bound = function () {
            // 合并参数,绑定时和调用时分别传入的
            let finalArgs = [...args, ...arguments]
            
            // 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
            // 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
            return self.call((this instanceof F ? this : context), ...finalArgs)
        }
    
    // 将调用函数的原型赋值到中转函数的原型上
    F.prototype = self.prototype
    // 通过原型的方式继承调用函数的原型
    bound.prototype = new F()
    return bound
}
 
上一篇下一篇

猜你喜欢

热点阅读