使用js模拟实现apply和call

306 阅读2分钟

先来看call的MDN解释

call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

总是觉得比较难懂,我的理解是:写在函数原型链上的一个方法,能够改变调用这个方法的函数的this指向。

来个示例:

var value = 2
var foo = {
    value: 1
};

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

定义了bar并且在windows环境下执行,this指向的是windows下的全局变量,如果我们使用call指向了foo对象,那么this就指向了foo

call改变了this的指向,并且执行了函数

我们要怎么进行模拟呢?我们知道对于this,谁调用了它,它就指向谁,而且会沿着作用域链一层一层往外找,那么我们把bar写在对象foo里面的话效果应该是相同的吧

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

效果是一样的呢,那我们可不可以重写一个call2,它的效果是被调用之后就在调用的对象里面增加一个属性bar,然后再调用之后删除这个属性,是不是可以做到和call一样的效果呢?

Function.prototype.call2 = function(context) {
    context.nihaoya = this;  //nihaoya是一个要被删除的属性,取什么无所谓
    console.log(this)  // 方法中,谁调用方法,this就指向谁,这里指向bar
    context.nihaoya();
    delete context.fn;
    console.log(context) //操作下来foo是没有变化的
}

// 测试一下
var foo = {
    value: 1
};

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

bar.call2(foo); // 1

我们这里的call只能传入一个对象参数,需要再改造一下,从第二个参数开始就是传入的其他参数了

Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = Array.from(arguments).slice(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

嗯,没啥问题,应该还要加一点对于传入函数类型的判断。嗨,算了,懒得加了,主体大概就是这样子。

apply的模拟实现

Function.prototype.apply2 = function(context) {
    context.fn = this;
    var args = arguments[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.apply2(foo, ['kevin', 18]); 
//kevin
//18
//1

实现了call再来实现apply真是洒洒水啦