bind、call、apply 的区别与实现
理解三种改变 this 指向的方法,并手写实现 bind
问题
bind、call、apply 都能改变函数的 this 指向,它们有什么区别?如何手写实现 bind?
解答
为什么需要改变 this 指向
看一个常见的场景:
var name = "lucy";
var obj = {
name: "martin",
say: function () {
console.log(this.name);
}
};
obj.say(); // martin,this 指向 obj
setTimeout(obj.say, 0); // lucy,this 指向 window
定时器中的回调函数在全局上下文执行,this 指向了 window。如果想让 this 指向 obj,就需要改变 this 指向:
setTimeout(obj.say.bind(obj), 0); // martin,this 指向 obj
注意:如果使用 const name = "lucy",setTimeout 会输出 undefined,因为 const/let 声明的变量不会挂载到 window 上,只有 var 会。
apply
接收两个参数:this 指向和参数数组,立即执行函数。
function fn(...args) {
console.log(this, args);
}
let obj = {
myname: "张三"
}
fn.apply(obj, [1, 2]); // this 指向 obj,参数必须是数组
fn(1, 2); // this 指向 window
当第一个参数为 null 或 undefined 时,this 指向 window(浏览器环境):
fn.apply(null, [1, 2]); // this 指向 window
fn.apply(undefined, [1, 2]); // this 指向 window
call
接收参数列表,立即执行函数。
function fn(...args) {
console.log(this, args);
}
let obj = {
myname: "张三"
}
fn.call(obj, 1, 2); // this 指向 obj,参数是列表形式
fn(1, 2); // this 指向 window
同样,第一个参数为 null 或 undefined 时,this 指向 window。
bind
接收参数列表,返回一个新函数,不立即执行。参数可以分多次传入。
function fn(...args) {
console.log(this, args);
}
let obj = {
myname: "张三"
}
const bindFn = fn.bind(obj); // 返回新函数,不立即执行
bindFn(1, 2); // this 指向 obj
fn(1, 2); // this 指向 window
手写实现 bind
bind 的实现需要考虑三点:修改 this 指向、动态传递参数、兼容 new 关键字。
支持两种传参方式:
// 方式一:只在 bind 中传参
fn.bind(obj, 1, 2)()
// 方式二:bind 和返回函数中都传参
fn.bind(obj, 1)(2)
完整实现:
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
const args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? new fn(...arguments) : context,
args.concat(...arguments)
);
}
}
关键点
- apply 和 call 立即执行,bind 返回新函数
- apply 接收参数数组,call 和 bind 接收参数列表
- bind 可以分多次传参,参数会合并
- 第一个参数为 null/undefined 时,this 指向 window(浏览器环境)
- 实现 bind 需要兼容 new 关键字,判断返回函数是否被 new 调用
目录