现在在看新东西的时候,经常会很自然地去思考其内部实现机制,我觉得这个是通向进阶之路的一个很好的思维方式。
我们平时经常会使用到 call()、apply() 以及 bind() 方法,那么你是否清楚这几个方法的内部实现机制呢?在这篇博文中我希望能够通过实现自己的 call()、apply() 和 bind() 方法以使我们能够更好地理解其内部实现机制。
Function.prototype.call
使用过 call() 方法的童鞋应该都知道,call() 方法的作用就是执行调用函数并改变其内部的 this 指向。
在 MDN 中的定义是:call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。
让我们看如下例子:
// 浏览器环境中运行
let count = 0;
let obj = {
count:1
}
function addNum(arg1,arg2){
return this.count + arg1 + arg2;
}
console.log(addNum(2,3)) // 5
console.log(addNum.call(obj,2,3)) // 6
addNum.call(obj) 执行 addNum() 方法并将其内部 this 指向改为 obj,所以最终返回的是 obj.count + arg1 + arg2 。
那么如何将 addNum() 方法内部 this 指向改为 obj 呢?如果对 this 指向有一定了解的同学,很容易可以想到如下的方式:
obj.fn = addNum;
console.log(obj.fn(2,3)); // 6
事实上,call() 方法内部改变 this 指向的机制也是一样的,通过将调用函数作为传入对象的一个属性来调用,来实现 this 指向的改变。
所以接下来,我们便可以通过这个机制来实现 call() 方法,其需要有以下特性:
- 不传参数或者第一个参数传 null,this 指向 window;
- 第一个参数之后的参数作为调用函数的传参接收;
- 改变函数 this 指向,返回调用函数执行结果;
所以,最终实现的 myCall() 方法如下:
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') throw new TypeError('Error');
context = context || window
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
通过 myCall() 方法来实现上述例子依旧能得到正确的返回结果:
console.log(addNum.myCall(obj,2,3)) // 6
Function.prototype.apply
apply() 方法与 call() 方法类似,区别在于 apply() 方法在接收调用函数参数的时候是以数组的形式接收的,所以在对参数的处理时会有所不同。
在 MDN 中的定义是:apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。
同样的,最终实现的 myApply() 方法如下:
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') throw new TypeError('Error');
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
通过 myApply() 方法来实现上述例子依旧能得到正确的返回结果:
console.log(addNum.myApply(obj,[2,3])) // 6
Function.prototype.bind
bind() 方法相较之前的两个函数则要复杂一些。
在 MDN 中的定义是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值,并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
如下例子:
let bindFn1 = addNum.bind(obj);
console.log(bindFn1(2,3)); // 6
let bindFn2 = addNum.bind(obj, 2);
console.log(bindFn2(3)); // 6
let bindFn3 = addNum.bind(obj, 2, 3);
console.log(bindFn3()); // 6
所以我们实现的 bind() 方法需要有以下特性:
- 返回一个函数,该函数可以直接调用也可以通过 new 方式调用;
- 直接调用则改变函数 this 指向,通过 new 方式调用则忽略;
- 返回函数能接收 bind 函数传递的部分参数;
所以,最终实现的 myBind() 方法如下:
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') throw new TypeError('Error');
const _this = this
const args = [...arguments].slice(1)
return function F() {
if (this instanceof F) { // 通过 new 方式调用的情况
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
通过 myBind() 方法来实现上述例子依旧能得到正确的返回结果:
let bindFn1 = addNum.myBind(obj);
console.log(bindFn1(2,3)); // 6
let bindFn2 = addNum.myBind(obj, 2);
console.log(bindFn2(3)); // 6
let bindFn3 = addNum.myBind(obj, 2, 3);
console.log(bindFn3()); // 6
公众号不定时分享个人在前端方面的学习经验,欢迎关注。
网友评论