call和apply的模拟实现

我心飞翔 分类:javascript

一句话介绍call

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

2、看看他是怎么工作的

let foo = {
    value: 'foo.value'
}
var value = 'window.value'

function bar() {
    console.log(this.value)
}

bar() // window.value
bar.call(foo) // foo.value 
 

总结: 说白了就是要改变 this 的指向
注意:

  1. call 改变了this 的指向, 指向了foo
  2. bar函数执行了

3、 先实现第一种

先试试水

foo = {
    value: 'foo.value',
    bar: function() {
        console.log(this.value)
    }
}

foo.bar(); // foo.value
 

哈哈哈。。。试水成功了,是我们要的效果,但是 foo 多了一个属性,这不是我们想要的,那就直接 delete 不就好了吗

是时候展示一波真正的技术了

Function.prototype.call1 = function(context) {
    context.fn = this;
    context.fn();
    delete context.fn;
}

bar.call1(foo); // foo.value
 

效果还不错吧,但是还有不少缺陷,比如 call1 无法传入参数

模拟实现第二步

这一步的目标就是可以传入参数,而且参数的个数是不确定的

那么我们可以

Function.prototype.call2 = function(context, ...args) {
    context.fn = this;
    context.fn(...args);
    delete context.fn;
}

function bar2(name) {
    console.log(name, this.value);
}

bar2.call2(foo, 'tutu') // tutu foo.value 
 

嗯,效果还可以啊,可是 callES3 的产物,我们用 ES6 的语法有点不太好,所以我们可以。。。

Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
    }
    //args 长这个样子的 ["arguments[1]", "arguments[2]", "arguments[3]"]
    // context.fn(arguments[1],arguments[2],arguments[3])
    eval('context.fn(' + args +')');
    delete context.fn;
}

function bar3(a, b, c) {
    console.log(this.value, a + b + c)
}
// bar3.call2(foo, 1, 2, 3)
 

这里有一个不常用的函数eval, 他是这么用的。
到这里模拟代码已经完成了 80% 了,距离成功已经不远了

最后一步了,已经离成功不远了

这次需要解决两个问题

  1. this 参数可以传 null,当为 null 的时候,视为指向 window
  2. 函数是可以有返回值的

举个例子

function barTest() {
    return this.value;
}

console.log(barTest.call(null)) // window.value
 

开始最后的操作吧

Function.prototype.call3 = function(context) {
    context = context || window;
    context.fn = this;
    var args = [];
    for(let i = 1;i < arguments.length; i++) {
        args.push('arguments[' + i + ']')
    }
    var result = eval('context.fn(' + args +')');
    delete context.fn;
    return result;
}

 function bar3(a, b, c) {
    console.log(this.value)
    return a + b + c;
}
var sum = bar3.call3(null, 1, 2, 3);
console.log(sum)
// window.value
// 6
 

再来实现一个 apply

原理和 call 是一样的,区别是参数的形式不一样,call 的入参形式正常的入参,apply 的参数是以数组的形式传入的。
写个例子:

function bar(a, b){
   console.log(a + b)
}

bar.apply(null, [1, 2])  // 3
bar.call(null, 1, 2) // 3
 
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 + ']');
        }
        //  'context.fn(' + args + ')' ==> context.fn(arr[0],arr[1],arr[2])
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}
 

写在最后

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。
最后感谢冴羽 大神的 JavaScript深入之call和apply的模拟实现

作者:掘金-call和apply的模拟实现

回复

我来回复
  • 暂无回复内容