美文网首页
call和apply的模拟实现

call和apply的模拟实现

作者: 小泡_08f5 | 来源:发表于2019-06-14 16:38 被阅读0次

    call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法

    如:

    var foo = {
        value: 1
    };
    
    function bar() {
        console.log(this.value);
    }
    
    bar.call(foo); // 1
    

    注意两点:

    1. call 改变了this的指向, 指向到foo
    2. bar函数执行了
    模拟实现第一步

    试想当调用call的时候, 把foo对象改造如下:

    var foo = {
        value: 1,
        bar: function() {
            console.log(this.value)
        }
    };
    
    foo.bar(); // 1
    

    这个时候this就指向了 foo,
    这样给foo对象本身添加了一个属性bar, 所以最后执行完函数,我们得把属性删除
    模拟步骤:
    1.将函数设为对象的属性
    2.执行该函数
    3.删除该函数

    // 第一步
    foo.fn = bar
    // 第二步
    foo.fn()
    // 第三步
    delete foo.fn
    

    fn 是属性名, 可随意取,因为最后会删除掉

          // 第一版
          var foo = {
                value: 1,
                fn: bar  // 新增属性指向 函数bar
            };
            
            function bar() {
                console.log(this.value);
            }
            console.log(foo);
            foo.fn(); // 执行函数
            delete foo.fn;  // 删除函数
            console.log(foo);
    
    image.png

    根据这个思路, 试着写一个call2方法

    // 第二版
            Function.prototype.call2 = function(obj){
                console.log(this); // this指向调用该方法的函数bar
                console.log(obj); // 传入的foo对象
                obj.fn = this; // 往foo对象新增属性fn, 指向函数bar
                obj.fn(); // 执行函数fn(bar), 成功输出1
                delete obj.fn; // 删除属性fn
                console.log(obj); // 最后没有改变foo对象本身(里面的属性)
            }
    
            var foo = {
                value: 1
            };
            
            function bar() {
                console.log(this.value);
            }
            bar.call2(foo);
    
    image.png
    模拟实现第二步

    call 方法还能给定参数执行函数

    var foo = {
        value: 1
    };
    
    function bar(name, age) {
        console.log(name)
        console.log(age)
        console.log(this.value);
    }
    
    bar.call(foo, 'kevin', 18);
    // kevin
    // 18
    // 1
    

    注意:传入的参数并不确定。怎么办? 我们可以从 Arguments 对象中取值, 取出第二个到最后一个参数, 放在一个数组里。

    // 第三版
            Function.prototype.call2 = function(obj){
                console.log(arguments);
                var args = [];
                for(var i=1; i<arguments.length; i++){
                    args.push(arguments[i]); 
                }
                console.log(args); // ["Ailse", "18"]
                console.log(args.join(',')); // "Ailse,18"
                obj.fn = this;
                obj.fn(args.join(','));
                delete obj.fn;
            }
    
            var foo = {
                value: 1
            };
            
            function bar(name,age) {
                console.log(arguments);
                console.log(name,age);
            }
            bar.call2(foo, "Ailse","18");
    

    但是这样是不行的,看控制台输出,call2函数里的3个console:


    image.png

    这样执行fn传入的参数是一个字符串“Ailse,18”, 函数bar接收的参数永远只能接收到一个字符串参数了,如下打印:


    image.png

    怎么正确传参呢,
    通过eval方法拼成一个函数,类似这样:
    这里简单说一下eval()方法:

    eval 是全局对象上的一个函数,会把传入的字符串当做 JavaScript 代码执行。如果传入的参数不是字符串,它会原封不动地将其返回。eval 分为直接调用和间接调用两种,通常间接调用的性能会好于直接调用。
    进一步了解eval方法:https://juejin.im/post/5bead276e51d452ceb51e027

     eval('obj.fn('+args+')');
    

    args 还得改一下,不然会报错

    args.push('arguments['+i+']');
    
    image.png

    代码改一下

    // 第三版
            Function.prototype.call2 = function(obj){
                var args = [];
                for(var i=1; i<arguments.length; i++){
                    args.push('arguments['+i+']'); 
                }
                obj.fn = this;
                eval('obj.fn('+args+')');
                delete obj.fn;
            }
    
            var foo = {
                value: 1
            };
            
            function bar(name,age) {
                console.log(arguments);
                console.log(name,age);
            }
            bar.call2(foo, "Ailse","18");
    

    这样就成功输出了


    image.png
    模拟实现第三步

    模拟代码已经完成80%, 还有两个小点要注意:

    1.this参数可以传null, 当为null的时候,视为指向 window

    var value = 1;
    
    function bar() {
        console.log(this.value);
    }
    
    bar.call(null); // 1
    

    2.函数是可以有返回值的

    var obj = {
        value: 1
    }
    
    function bar(name, age) {
        return {
            value: this.value,
            name: name,
            age: age
        }
    }
    
    console.log(bar.call(obj, 'kevin', 18));
    // Object {
    //    value: 1,
    //    name: 'kevin',
    //    age: 18
    // }
    

    都好解决, 最后代码:

    // 第四版
            Function.prototype.call2 = function(obj){
                var obj = obj || window;
    
                var args = [];
                for(var i=1; i<arguments.length; i++){
                    args.push('arguments[' + i + ']');
                }
    
                obj.fn = this;
                var result = eval('obj.fn('+args+')'); // eval方法拼成一个函数
                delete obj.fn;
                return result;
            }
    
            var foo = {
                value: 1
            };
            
            function bar(name,age) {
                console.log(name,age);
                return {
                    name: name
                }
            }
            console.log(bar.call2(null, "Ailse","18"));
    
    apply的模拟实现

    apply的实现跟call类似

    Function.prototype.apply = function (context, arr) {
        var context = Object(context) || window;
        context.fn = this;
    
        var result;
        if (!arr) {
            result = context.fn();
        }
        else {
            var args = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                args.push('arr[' + i + ']');
            }
            result = eval('context.fn(' + args + ')')
        }
    
        delete context.fn
        return result;
    }
    

    参考:https://juejin.im/post/5907eb99570c3500582ca23c

    面试题:call与apply的模拟思路
    关键点:

    • call 改变了this的指向, 指向到foo
    • bar函数执行了

    1.将函数设为对象的属性
    2.执行该函数
    3.删除该函数

    传入的参数并不确定怎么办?(通过从Arguments对象取值)
    如何正确传参?(通过eval方法拼成一个函数)

    相关文章

      网友评论

          本文标题:call和apply的模拟实现

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