call、bind、apply 原理与原生实现

call, apply, bind 算是比较面试中比较常问到的几个问题,再我们实际开发中也算是常用到的几个方法,了解清楚他们背后的运行机制和原理对一个前端开发者来说也就十分的必要了。

用法

先来看看他们各自的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

var petter = {
name:'petter',
};

function say(text, color) {
console.log(this.name + ':' + text + ',' + color);
}

// call 的用法
say.call(petter, 'call', 'red');

// apply 的用法
say.apply(petter, ['call', 'red']);

// bind 的用法
var petter_say = say.bind(petter, 'call');
petter_say('red');

call 和 apply的区别只是在第二参数,他们第一个参数都是用于改变函数this的指向,call 后面需要按顺序传入函数所需的多个参数, apply 则需要将函数所需的参数放入数组中传给apply的第二参数。

bind 和他们又很不一样,bind会返回一个改变了this的新的函数,后面和call一样,你可以把所有参数提前传入,也可以后面再把参数传入。

原生实现

了解他们各自的用法之后,我们也可以自己通过原生的代码去实现上面的方法

call 的实现

1
2
3
4
5
6
7
8
9
10
11
12

Function.prototype.my_call = function(new_this, ...params) {
// 用来做唯一索引用的,也可以不用symbol,为了安全用symbol能够保证外面绝对无法访问
// 整个原理是需要把方法挂载到传入的对象上去,然后通过这个对象去调用用这个函数
const fn_symbol = Symbol('fn');
if (new_this === null || new_this === undefined) {
new_this = window; // 传入null或者undefined的时候需要让this指向window
}
new_this[fn_symbol] = this; // 这里的this指向的是这个方法
new_this[fn_symbol](...params);
delete new_this[fn_symbol];
}

apply 的实现

实现了call之后,apply 的实现也是类似的

1
2
3
4
5
6
7
8
9
10
// 唯一的区别就是这里的params不需要展开运算符
Function.prototype.my_apply = function(new_this, params) {
const fn_symbol = Symbol('fn');
if (new_this === null || new_this === undefined) {
new_this = window;
}
new_this[fn_symbol] = this;
new_this[fn_symbol](...params);
delete new_this[fn_symbol];
}

bind 的实现

bind 和 call 有点类似,但又不太一样,bind 会返回一个新的函数,所以我们得保存传入的this然后返回一个新的函数出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Function.prototype.my_bind = function(new_this, ...params) {
const fn_symbol = Symbol('fn');
if (new_this === null || new_this === undefined) {
new_this = window;
}
new_this[fn_symbol] = this;
return function(...new_params) {
new_this[fn_symbol](...[...params, ...new_params]);
};
}

// 或者可以借助call来实现
Function.prototype.my_bind_c = function(new_this, ...params) {
const fn = this;
return function (...new_params) {
fn.call(new_this, ...params, ...new_params);
};
}

现在我们可以更好的去理解为什么bind之后的方法无法再去改变他的this了,因为闭包的关系,传入的new_this我们是无法再去改变他的指向的.