JavaScript温故而知新——call()和apply()的实现

2,112 阅读2分钟

一、call()

call()通过指定this的指向来实现函数的间接调用,并且可以传入函数调用的参数

举个例子:

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

首先我们要知道call()在这里起的作用:

  1. 指定this的指向为foo
  2. 执行bar函数

我们可以模拟一下这两个效果:

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

可以看到将bar设为foo的属性,便实现了将this指向foo。但是这样做给foo对象平白无故的添加了一个属性,因此我们还要通过delete删除它。
因此主要实现思路如下:

// 1.将函数设为对象的属性
foo.fn = bar
// 2.执行该函数
foo.fn()
// 3.从对象中删除该函数
delete foo.fn

根据这个思路先实现一个简易版的:

Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}

接下来实现call给定参数执行函数:
要注意传入的参数个数是不确定的,我们可以从arguments对象中取值,即第二个到最后一个参数便是要传入的参数。

Function.prototype.call2 = function(context) {
    context.fn = this;
    
    var args = [];
    // 遍历arguments类数组对象,取得第二个开始的参数,放到数组里面去
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    // 执行后 args为 [arguments[1], arguments[2], arguments[3]]
    
    // 这里 args 会自动调用Array.toString()方法
    eval('context.fn(' + args +')');
    delete context.fn;
}

此时call()方法的核心已经实现了,下面要完善call()的功能:

  1. 第一个参数可以传入null,表示指向window
  2. 函数是可以有返回值的

完整实现

Function.prototype.call2 = function(context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    
    var result = eval('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.call2(null); // 2
console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

二、apply()

apply的实现跟call类似,区别在于apply第二个参数传入的是一个参数数组

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;
}

结尾

系列文章: