JavaScript的call()和apply()的用法及模拟实现

call()方法使用一个指定的this值和一个或多个参数来调用一个函数;

var value = 2;
var bar = {
    value: 1;
}
function foo(name) {
    console.log(this.value + '-' + name);
}
foo('Lora');	//2-Lora		此时foo的this指向全局的window对象
foo.call(bar, 'Lora');	//1-Lora		使用call()方法调用了foo方法,并将foo的this指向bar

下面我们来一步步实现call()

第一步,实现通过指定的this值来调用函数。
Function.prototype.myCall = function (context) {
  	context.fn = this;  //  这里的this指向将来调用myCall()的那个函数
  	context.fn();
  	delete context.fn;
}
foo.myCall(bar) //1
  1. bar添加属性fn,将foo赋值给bar.fn
  2. 调用函数bar.fn,此时函数内部的this指向的就是bar
  3. 函数调用完成后,删除属性bar.fn
第二步,实现传递参数

由于不清楚要传的参数具体有多少个,所以myCall的形参就只写一个context,来接收要指定的this值,其余参数通过arguments来获取;

Function.prototype.myCall = function (context) {
    context.fn = this;
    var args = [];	//使用args来接收除了context以外的参数
    for (var i = 1; i < arguments.length; i++) {
        //因为arguments的第一个元素是参数context,所以从i = 1开始
        args.push(arguments[i]);
    }
    context.fn(...args);
    delete context.fn;
}

//测试一下
function f(name, age) {
    console.log(`${this.vlaue}-${name}-${age}`);
}
f.myCall(bar, 'Lora', 20);	//1-Lora-20
第三步,优化
  1. this参数可以传null,当为null的时候,视为指向window
  2. 有些时候函数是有返回值的,而不仅仅是处理一些逻辑,例如:
function p(name, age) {
    return { name, age };
}
console.log(p('Lora', 20));	//{ name: 'Lora', age: 20 }

上面这个函数返回的就是一个对象。

现在我们针对上面两个问题进行优化;

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

//测试一下
function p2(name, age) {
    return {
        value: this.value,
        name,
        age
    }
}
var result = p2.myCall(bar, 'Lora', 20);
console.log(result);	//{value: 1, name: 'Lora', age: 20}

至此,模拟实现call()方法已全部完成。

apply()的模拟实现

apply()的用法与call()类似,区别在于传递参数的类型不同。

apply()的第一个参数为指定的this,剩余参数以一个数组的形式传递,如下:

function foo(name, age) {
    console.log(`${this.value}-${name}-${age}`)
}
var bar = {
    value: 1
}
foo.apply(bar, ['Lora', 20]);	//1-Lora-20

因此只需在myCall()的基础上,对参数的处理做下改动即可,如下:

Function.prototype.myApply = function (context, array) {
    var context = context || window;
    context.fn = this;
    var res;
    //判断是否传入数组
    if (!array) {
        res = context.fn();
    } else {
        res = context.fn(...array);
    }
    delete context.fn;
    return res;
}

//测试一下
function testApply(name, age) {
    return {
        value: this.value,
        name,
        age
    }
}
var result = testApply.myApply(bar, ['Lora', 20]);
console.log(result);	//{value: 1, name: 'Lora', age: 20}

完结

本篇文章主要内容是如何手写call()apply(),毕竟是面试中会经常出现的。

关于call()apply()的更多应用场景,请参看MDN文档:

Function.prototype.call()

Function.prototype.apply()

参考原文链接

posted @ 2020-09-28 17:02  kyoumei  阅读(158)  评论(0)    收藏  举报