1. call、apply、bind三者的异同
-
共同点:都可以改变
this
指向 -
不同点:
call()
和apply()
会调用函数,并且改变函数内部this
指向.call()
和apply()
传递的参数不一样,call()
传递参数使用逗号隔开,apply()
使用数组传递bind()
不会调用函数,可以改变函数内部this
指向
-
应用场景
call()
经常做继承apply()
经常跟数组有关系,比如借助于数学对象实现数组最大值最小值bind()
不调用函数,但是还想改变this
指向,比如改变定时器内部的this
指向
call() | apply() | bind() | |
---|---|---|---|
相同点 | 改变函数this 指向 | 改变函数this 指向 | 改变函数this 指向 |
是否调用函数 | 是 | 是 | 否 |
传递参数 | 逗号, 隔开 | 数组形式[] | 逗号, 隔开 |
应用场景 | 继承 | 与数组有关 | 不想调用函数 |
2. 实现call
- 将第一个参数作为
call
函数内部临时对象obj
- 给
obj
一个属性fn
,成为实际执行函数,并将this
关键字指向这个属性 - 执行这个函数,并拿到返回值
- 删除函数属性
- 返回函数执行的结果
// 实现call
Function.prototype.myCall = function (obj, ...args) {
// 判断上下文
const newObj = obj ? Object(obj) : global;
// 将函数设置为对象的属性
newObj.fn = this;
// 执行这个函数,并拿到返回值
const res = newObj.fn(...args);
// 删除这个函数属性
delete newObj.fn;
// 返回值
return res;
};
3. 实现apply
apply
第二个参数是以数组形式传递的,所以基本步骤与call
一致,不同的是函数执行的时候需要进行判断是否传入了第二个参数。如果有,将其传入并执行;若没有,直接执行。
// 实现apply
Function.prototype.myApply = function (obj, arr) {
// 判断上下文
const newObj = obj ? Object(obj) : global;
// 将函数设置为对象的属性
newObj.fn = this;
// 执行这个函数,并拿到返回值
let res;
if (arr) {
res = newObj.fn(...arr);
} else {
res = newObj.fn();
}
// 删除这个函数属性
delete newObj.fn;
// 返回值
return res;
};
4. 实现bind
// 实现bind
Function.prototype.MyBind = function (context) {
// 调用的方法本身
const self = this;
// 类数组->真数组
const args = Array.prototype.slice.call(arguments, 1);
// 中转函数
const temp = function () {};
const fn = function () {
// 将新函数执行时的参数arguments数组化,然后与绑定时的参数合并
const newArgs = Array.prototype.slice.call(arguments);
// 如果被new调用,this应该是fn的实例
return self.apply(this instanceof fn ? this : context || global, args.concat(newArgs));
};
// 中转原型链
temp.prototype = self.prototype;
fn.prototype = new temp();
return fn;
};
源代码:「JavaScript手撕代码」