美文网首页
Call-apply-bind实现

Call-apply-bind实现

作者: Raral | 来源:发表于2020-10-27 09:09 被阅读0次

    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
    }
     
    

    相关文章

      网友评论

          本文标题:Call-apply-bind实现

          本文链接:https://www.haomeiwen.com/subject/uuutvktx.html