1. call / apply / bind 对比
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
- 相同点:
- 都用于绑定函数的this的指向。
- 三个函数都在 Function.prototype上
- 区别:
call/apply调用后会执行该函数,bind调用后会返回一个新函数call的函数参数接收的是参数列表,bind接受的是一个含有多个参数的数组bind的this参数后的其他参数,会在所返回函数进行调用时作为参数。
var name = "globalName";
const obj = {
name: "objName"
}
function logName(name1) {
console.log(this.name, name1);
}
logName("test"); // globalName test
logName.call(obj, "test"); // objName test
logName.apply(obj, ["test"]); // objName test
temp = logName.bind(obj);
temp('test'); // objName test
temp = logName.bind(obj, "test");
temp(); // objName test
2. 实现
2.1 call
先看下面这段代码:
const obj = {
logThis(){
console.log(this);
}
}
obj.logThis() // {logThis: ƒ}
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。这里的上下文对象就是obj。
Function.prototype.myCall = function(context){
context.fn = this;
context.fn();
}
myCall为Function.prototype上的一个函数,由前面隐式绑定可知,当以targetFn.myCall()的形式调用时,myCall中的this指向targetFn。
context为用户指定的this指向的对象,可以同样利用隐式绑定,在该对象上添加键fn,值为this,即targetFn,当以context.fn()的形式调用时,该函数的this指向context。
验证一下:
Function.prototype.myCall = function (context) {
context.fn = this;
context.fn();
};
name = 'global';
obj = {
name: "obj",
};
function logName() {
console.log(this.name);
}
logName(); // global
logName.myCall(obj); // obj
但是还有其他问题需要解决:
-
往Function的原型对象上了不需要的属性
- 在调用后 delete 该属性
-
Function的原型对象可能本来就存在
fn属性,原值会被覆盖- 使用
Symbol生成唯一的键
- 使用
-
需要限制只有函数对象才可以调用
- 通过
this来判断调用该方法的上下文对象
- 通过
-
this 指定为
null或undefined时会自动替换为指向window- 参数处理
-
多的参数需要作为
targetFn调用时的参数- 利用
argments对象 或...args
- 利用
-
需要返回调用结果
完善一下代码:
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== "function") throw new Error("type error");
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
const res = context[fnSymbol](...args);
delete context[fnSymbol];
return res;
};
2.2 apply
由于apply只是在参数的形式上与call存在区别,所以我们可以在myCall的基础上对参数进行处理即可实现。
// 仅修改args
Function.prototype.myApply = function (context, args = []) {
if (typeof this !== "function") throw new Error("type error");
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
const res = context[fnSymbol](...args);
delete context[fnSymbol];
return res;
};
2.3 bind
首先考虑bind返回的是一个函数,其次该函数执行的效果与原来的函数调用call/apply的效果是一致的。因此个人认为可以将其视为一种call/apply的延迟执行。
按照这个思路,可以写出以下代码:
Function.prototype.myBind = function (context, ...args) {
const _this = this;
return function () {
_this.call(context, ...args);
};
};
前面已经提过 this指向targetFn.myBind()中的targetFn,此处用_this将其保存下来。
除了在myCall处调到的问题,myBind需要解决的特别的问题:
- 返回的函数在调用时可以继续传参
- 接受参数,并与原来的参数合并
- 返回的函数被
new时,会忽略原先绑定的this值new调用函数时,函数中的this为所调用函数的一个实例
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== "function") throw new Error("type error");
context = context || window;
const _this = this;
return function Fn(...otherArgs) {
return _this.call(
this instanceof Fn ? this : context,
...args,
...otherArgs
);
};
};
首先将原来返回一个匿名函数改为返回函数Fn,此处通过this instanceof Fn来判断调用场景,若用new调用,则此条件符合,call指定this为当前this。