手动封装bind、call、apply方法

“ 在学习和工作中,显式的改变this指向也是常遇到的,那么如何显式的改变this指向呢?聪明的你一定猜到啦,肯定是调用bind、call、apply这三种方法来改变。但是今天我并不会介绍这三个方法的使用,而是手动封装它们,做到知其所以然。”

1. bind方法封装

bind方法创建一个新的绑定函数,调用新绑定函数,会在指定的作用域中传入参数并执行。bind函数与call和apply函数不一样在于,它会返回一个新的绑定函数,当你再执行它的时候才会真正的执行。了解了之后我们就来看看bind如何封装的,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 由于是函数调用,那么在函数原型上添加方法即可
Function.prototype.myBind = function (context) {
// 首先判断绑定的是否是函数
if (typeof this !== 'function') {
throw new TypeError('not function');
}
const _this = this;
const args = [...arguments].slice(1);
// 这里不能使用箭头函数,因为箭头函数不能实例化、arguments
return function F () {
// 是否实例化
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
}
};

2. call方法封装

在上面写完bind函数封封装之后,接下来我们再来写一下call方法的封装,call方法就比bind方法更简单,因为调用它并不会像bind方法会返回一个函数,调用call方法一次就会执行。接下来就是看看call方法如何封装的,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.myCall = function (context = window) {
if (typeof this !== 'function') {
throw new TypeError('not function');
}
// 将方法引用到指定对象中,对象的key为fn(作用:改变this指向)
context.fn = this;
const args = [...arguments].slice(1);
// 执行函数,它的this指向context
const result = context.fn(...args);
delete context.fn;
return result;
}

3. apply方法封装

在02中写完了call方法的封装,接下来就来写一下apply方法的封装,它与call方法的不同之处在于传递的第二个参数不同,call要求传递的第二个参数以及第二个之后的参数值可以是任意类型,而apply传递的第二个参数是一个数组或者类数组。接下来就是看看apply方法如何封装的,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 Function.prototype.myApply = function (context = window) {
if (typeof this !== 'function') {
throw new TypeError('not function');
}
context.fn = this;
let result;
// 判断是否给apply第二个参数传递值
if (arguments[1]) {
result = context.fn(arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
};