zoukankan      html  css  js  c++  java
  • 手写实现call,apply函数

    call的使用

    举个例子:

    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 对象本身添加了一个属性,这可不行呐!

    不过也不用担心,我们用 delete 再删除它不就好了~

    所以我们模拟的步骤可以分为:

    1. 将函数设为对象的属性
    2. 执行该函数
    3. 删除该函数
      以上个例子为例,就是:
    // 第一步
    foo.fn = bar
    // 第二步
    foo.fn()
    // 第三步
    delete foo.fn
    

    fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。

    根据这个思路,我们可以尝试着去写第一版的 call2 函数:

    // 第一版
    Function.prototype.call2 = function(context) {
        // 首先要获取调用call的函数,用this可以获取
        context.fn = this;
        context.fn();
        delete context.fn;
    }
    
    // 测试一下
    var foo = {
        value: 1
    };
    
    function bar() {
        console.log(this.value);
    }
    
    bar.call2(foo); // 1
    

    实现第二步

    最一开始也讲了,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 对象中取值,取出第二个到最后一个参数,然后放到另一个数组里。

    比如这样:

    // 以上个例子为例,此时的arguments为:
    // arguments = {
    //      0: foo,
    //      1: 'kevin',
    //      2: 18,
    //      length: 3
    // }
    // 因为arguments是类数组对象,所以可以用for循环
    var args = [];
    for(var i = 0; i < arguments.length-1; i++) {
        args[i] = arguments[i+1]
    }
    
    // 执行后 args为 ['kevin', 18]
    

    第二版代码如下:

    // 第二版
    Function.prototype.call2 = function(context) {
        context.fn = this;
        let args = [];
        for(let i = 0; i < arguments.length - 1; i++) {
            args[i] = arguments[i + 1]
        }
        context.fn(...args)
        delete context.fn;
    }
    
    // 测试一下
    var foo = {
        value: 1
    };
    
    function bar(name, age) {
        console.log(name)
        console.log(age)
        console.log(this.value);
    }
    
    bar.call2(foo, 'kevin', 18); 
    // kevin
    // 18
    // 1
    

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

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

    举个例子:

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

    虽然这个例子本身不使用 call,结果依然一样。

    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 (context) {
        var context = context || window;
        context.fn = this;
    
        let args = [];
        for(let i = 0; i < arguments.length - 1; i++) {
            args[i] = arguments[i + 1]
        }
        // args = [...arguments].slice(1) 也可以直接使用内置函数
        let result = context.fn(...args)
    
        delete context.fn
        return result;
    }
    
    // 测试一下
    var value = 2;
    
    var obj = {
        value: 1
    }
    
    function bar(name, age) {
        console.log(this.value);
        return {
            value: this.value,
            name: name,
            age: age
        }
    }
    
    bar.call(null); // 2
    
    console.log(bar.call2(obj, 'kevin', 18));
    // 1
    // Object {
    //    value: 1,
    //    name: 'kevin',
    //    age: 18
    // }
    

    到此,我们完成了 call 的模拟实现,给自己一个赞 b( ̄▽ ̄)d

    apply实现

    apply 的实现跟 call 类似

    Function.prototype.apply = function (context) {
        var context = Object(context) || window;
        context.fn = this;
      	let result
      	let args = arguments[1]
      	if (args) {
        	result = context.fn(...args)
      	} else {
        	result = context.fn()
    	}
        delete context.fn
        return result;
    }
    

    转载链接

  • 相关阅读:
    C#中异步和多线程的区别
    猫 老鼠 人的编程题
    C#中数组、ArrayList和List三者的区别
    经典.net面试题目
    sql有几种删除表数据的方式
    内存池的实现
    A*算法为什么是最优的
    传教士与野人问题
    d3d导致cairo不正常
    c++中的signal机制
  • 原文地址:https://www.cnblogs.com/huangguofeng/p/13735863.html
Copyright © 2011-2022 走看看