动手实现一下 JavaScript 中的 call, apply 和 bind

在 JavaScript 中 call、apply、bind 都有些类似,但又有些不同,它们都用于改变 this 的指向,其中 call 和 apply 会直接执行函数,而 bind 会返回一个新函数,另外 call 和 apply 的传参方式也有些不同,下面就来分别模拟实现一下。

call

首先看下 call 的使用

var foo = {
name: 'mofang',
};

function bar() {
console.log(this.name);
console.log(...arguments);
return 123;
}

bar.call(foo);
// mofang
bar.call(foo, 1, 'abc');
// mofang
// 1 abc
// 改变 bar 中的 this 指向,修改为 foo,因此在 bar 内部的 this.name 等价于 foo.name

然后我们来实现一下

Function.prototype._call = function(context) {
// 参数如果为空则指向 window
context = context || window;
const args = [...arguments].slice(1);

// 将调用 call 的对象添加到 context 上
context.fn = this;

// 执行 fn,此时 fn 内的 this 指向 context,同时传入参数...args
const ret = context.fn(...args);

delete context.fn;

// 返回函数执行结果
return ret;
};

bar._call(foo);
bar._call(foo, 1, '2');

apply

再来看一下 apply 的使用,apply 的使用与 call 类似,只不过第二个参数是传入一个数组

var foo = {
name: 'mofang',
};

function bar() {
console.log(this.name);
console.log(...arguments);
}

bar.apply(foo);
// mofang
bar.apply(foo, [1, 'abc']);
// mofang
// 1 abc

接下来模拟实现一下:

Function.prototype._apply = function(context) {
context = context || window;

// 获取参数列表,如果没有参数就传入一个空数组
const args = arguments[1] || [];

// 把调用 apply 的对象挂载在 context 上
context.fn = this;

// 执行 context.fn,fn 内部的 this 指向 context, 这样 fn 就拥有了获取 context 内部属性的能力
const ret = context.fn(...args);

// 删掉多余的 fn
delete context.fn;

// 返回 fn 执行的结果
return ret;
};

bind

最后来看一下 bind 是如何实现的,首先需要知道的是 bind 和 new 同时使用时,new 的优先级要高。

// var foo = bar._bind(obj);

Function.prototype._bind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('_bind must called on a function');
}

// 获取参数列表
var args = [].slice.call(arguments, 1);

// 存储调用函数(bar),方便后边使用 apply
var fToBind = this;
// 构造一个纯净的函数,用于存储原函数的原型
var fNop = function() {};
// 绑定的函数,还需要拿出去调用,这里再封装一个函数
var fBound = function() {
// 这里需要判断是否使用 new 调用了 bound,如果是则 this 指向实例,否则指向 context
return fToBind.apply(
this instanceof fToBind ? this : context,
args.concat([].slice.call(arguments))
);
};

fNop.prototype = this.prototype;
fBound.prototype = new fNop();

return fBound;
};

然后测试一下:

var obj = {};

function bar(name) {
this.name = name;
}

var foo = bar._bind(obj);

foo('mofang');
console.log(obj.name); // mofang
var w = new foo('walker');
// 通过 new 调用,内部 this 指向实例
console.log(obj.name); // mofang
console.log(w.name); // walker