这三个函数的使用方式很像,细微之处有所区别。但是都可以划分为三部分,调用者、绑定this以及绑定参数。
1.call
用法回顾: 以下面的例子为例,调用者为fn,绑定this为obj,绑定值为1,2。
const obj = {
id: 19
}
const fn = function () {
const args = [...arguments]
console.log(args)
console.log(this.id)
}
fn.call(obj, 1, 2) // [1,2] 19
模拟实现:
Function.prototype.call = function (context) {
// 1.获取绑定this
var context = context || window
// 2.获取调用者 this永远指向调用者
var fn = this
// 3.获取绑定参数
var args = [...arguments].slice(1)
// 4.绑定this执行调用者函数,并将结果返回
context.f = fn
var result = context.f(...args)
delete context.f
return result
}
这种方式需要说明的一点是conext.f = fn
以及delete context.f
。
context是绑定this, fn是调用者。所以在context上新增一个属性,其值是fn函数。执行这个属性就可以获取到想要的结果。然后再把这个属性删除即可。
但是这种方式的弊端就在此处。假如context本身就有一个名为f
的属性,调用call方法后,会导致该属性被删除。有一种解决方式是使用es6的sympol。但是es3的api使用es6......
目前大多数模拟实现都是这种思路,弊端的解决,我还没有看原生是如何处理实现的。暂且记下~
2.apply
apply与call的唯一区别只是参数的不同。
Function.prototype.apply = function (context) {
// 1.获取绑定this
var context = context || window
// 2.获取调用者 (this永远指向调用者)
var fn = this
// 3.获取绑定参数 (如果参数存在且为数组)
if (arguments[1] && arguments[1] instanceof(Array)) {
var args = arguments[1]
// 4.绑定this执行调用者函数,并将结果返回
context.f = fn
var result = context.f(...args)
delete context.f
return result
} else {
throw new Error('Error!')
}
}
3.bind
bind与call、apply不同的一点在于,bind只是返回一个绑定了this的新函数。
Function.prototype.bind = function (context) {
// 1.获取绑定的this
var context = context || window
// 2.获取调用者
var fn = this
// 3.获取绑定参数
var args = [...arguments].slice(1)
return function () {
// 4.将此函数中arguments与外层获取的args连接,得到最终参数
const arr = args.push(...arguments)
// 5.调用
return fn.call(context, ...arr)
}
}
4.总结
心血来潮,复习了下call、apply以及bind的原理模拟实现。主要原因是在es6-shim
遇见了Function.prototype.call.bind(Array.prototype.push)
这句语法。具体文章链接可见下方。