原生js模拟实现bind call apply
上一章节,我们已经使用原生js模拟实现了 bind 操作,传送门:原生js实现bind,call,apply (一)。
这一章节,我们就来实现一下 call 和 apply。
首先,我们先简单来说一下call 和 apply:
详细的请看JavaScript中的call()和apply()
this.name = 81;
var obj = {
name: 'syw',
getName: function (...params) {
console.log(this.name, params);
}
}
上一章节说过:
var res = obj.getName;
res(); // 输出 81 Array(0)
// 输出Array(0) 是因为我没有传递参数,所以params的长度为0
这样的调用方法,会因为 this 指向的问题得不到正确的值syw
,所以要在调用时改变 this 指向,即可输出正确的值:
var res = obj.getName;
res.call(obj); // syw Array(0)
res.apply(obj); // syw Array(0)
那么,我们怎么使用原生js去实现 call 和 apply 的操作呢?
进入观察阶段:
首先我们可以看到,我们在调用 call 和 apply 之后,改变了 this 的指向,并调用了 getName 方法。
那么转换一下思维【以上的例子为例】:
- res 调用 call 改变 this 指向到 obj,那也就相当于,我们
res.call(obj) 等于 obj.getName()
。 - 也就意味着,我们只需要把当前 res 调用的函数 给到我们传入的 obj,让 obj 去调用即可。
OK,明白了怎么做,我们就实现一下。
进入实现阶段:
首先,看一下完整的实现代码:
// 原生js实现call
Function.prototype.myCall = function (currentThis, ...params) {
// 如果currentThis为null或者undefined时,this 应该指向window
if (currentThis === null || currentThis === undefined) {
currentThis = window;
}
// 创建一个不重复的常量
const unique = Symbol('anything');
currentThis[unique] = this;
// 调用函数并将结果返回
let result = currentThis[unique](...params);
// 删除增加的属性
delete currentThis[unique];
return result;
}
代码解释:
- 为什么在
Function.prototype
上实现我们自己的 myCall 方法,这个我上一章节讲的有,有不懂的小伙伴可以使用上面提供的传送门。
-
首先 myCall 同样需要可以接受 n 个参数,分为两类:
第一类:this的正确指向(currentThis)。
第二类:传递参数(…params)。
var res = obj.getName; res(obj,1,2,3); // obj 第一类 // 1 2 3 第二类
-
在把 res 所调用的函数给到我们传入的
currentThis
之前,我们还要判断一下currentThis
传入没有,如果没传,或者currentThis
是undefined
,那么此时的 this 指向应该指向window
。// 如果currentThis为null或者undefined时,this 应该指向window if (currentThis === null || currentThis === undefined) { currentThis = window; }
这个大家可以自行测试一下:调用call方法啥也不传 ,this 会指向哪里。
-
把 res 调用的方法给到我们传入的正确 this 指向
currentThis
。-
怎么给?肯定不能直接把
this
指代的函数getName
直接赋值给currentThis
,currentThis
是我们传入的 this 指向,getName
覆盖了它,那就没得玩了。 -
所以,我们需要给我们传入的
currentThis
定义一个属性,把getName
给这个属性,这样我们只需要通过currentThis
调用这个属性就好了。当然属性也不能随意设置,我们要保证两点:
-
我们设置的属性要保证不能跟传入的
currentThis
里的属性重复,保证其唯一性,这时,就需要用到Symbol
数据类型了。const unique = Symbol('anything');
-
由于属性是我们自己设置的,所以到最后用完了,我们最好还是要把这个属性删除掉。
-
-
-
后面的代码想必就不用我多说了,不要奇怪:
currentThis[unique] = this;
如果你奇怪,那就证明,你还是没懂 this 是什么。this 指向哪里。
测试:
let gn = obj.getName; gn.myCall(obj,1,2,3); // 输出 syw Array(3)
原生js实现apply
apply 和 call 的区别就在于,call 是传离散的值,apply 是以数组的形式传值
所以,我们只需改正一下调用myApply时接收值的形式就行了。
完整代码:
// 原生js实现apply Function.prototype.myApply = function (currentThis, params) { // 这里使用params接收数组 // 如果currentThis为null或者undefined时,this 应该指向window if (currentThis === null || currentThis === undefined) { currentThis = window; } // 创建一个不重复的常量 const unique = Symbol('anything'); currentThis[unique] = this; // 调用函数并将结果返回 let result = currentThis[unique](...params); // 调用函数时结构一下把值传进去。 // 删除增加的属性 delete currentThis[unique]; return result; }
测试:
let gn = obj.getName; gn.myApply(obj, [1, 2, 3]); // 输出 syw Array(3)
完结·撒花