学习 call()
, apply()
, 和 bind()
方法是很重要的,因为它们让你能够控制 JavaScript 中 this
的上下文。在某些情况下,默认的 this
行为可能不符合预期,比如当你从一个对象借用方法到另一个对象,或者是在回调函数中保持正确的上下文时,这些方法提供了灵活性和控制力。通过掌握它们,你可以编写出更加高效、可复用并且上下文感知的函数,这在复杂的应用中尤其有用。
在我们深入了解 call()
, apply()
, 和 bind()
方法之前,先来了解一下 'this'
关键字及其工作机制。
'this'
关键字
让我们通过下面的要点来理解什么时候以及 this
关键字指的是什么:
-
在一个对象的方法中,
this
指向该对象。在对象内部定义的方法中,this
将指向拥有该方法的对象。 -
在一个普通函数中,
this
指向全局对象。在非严格模式下,如果函数是在全局上下文中调用(而不是作为对象的方法),this
指向的是全局对象(在浏览器中是window
)。 -
在严格模式下的函数中,
this
是undefined
。如果函数不是某个对象的方法,并且没有绑定到特定上下文(通过call
,apply
, 或bind
),那么在严格模式下this
将是undefined
。 -
在事件处理程序中,
this
指向接收事件的元素。当触发事件时,this
指向的是触发事件的 HTML 元素。<button onclick="this.style.display='none'"> 点击移除我! </button>
在这里,
this
指向的是接收到onclick
事件的按钮元素本身。
在箭头函数中, this
的行为有所不同。箭头函数没有自己的 this
上下文。相反,this
以词法形式继承自创建箭头函数时周围的上下文。这意味着箭头函数内部的 this
指向的是其外部函数或上下文的 this
值。
const person = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(`嗨,我是 ${this.name}`);
}, 1000);
}
};
person.greet(); // 输出: 嗨,我是 Alice
在这个例子中,setTimeout
内部的箭头函数继承了 greet
方法的 this
,后者指向 person
对象。
call()
方法
call()
方法允许你“借用”一个对象上的函数或方法,并用另一个对象来使用它,只需把另一个对象作为第一个参数传递即可。第一个参数成为了函数内部的 this
值,而其他参数紧随其后。
call()
方法并不会创建新的函数;它只是用提供的上下文和参数运行现有的函数。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
person.fullName.call(person1, "Oslo", "Norway");
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,call()
被用来执行 person
对象中的 fullName
方法,但是使用了 person1
的数据(firstName
和 lastName
),附加的参数则是 “Oslo” 和 “Norway”。
apply()
方法
apply()
方法和 call()
方法非常相似。主要区别在于参数是如何传递给函数的。使用 apply()
时,你可以将参数作为一个数组(或类数组对象)传递,而不是单独传递。
像 call()
一样,apply()
方法也不会创建新的函数。它立即执行函数,使用提供的上下文 (this
值) 和参数。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,apply()
被用来调用 person
对象中的 fullName
方法,但是上下文 (this
) 设置为了 person1
。参数 “Oslo” 和 “Norway” 作为一个数组传递。
bind()
方法
JavaScript 中的 bind()
方法让你能够设置一个函数或方法的上下文(this
值),就像 call()
和 apply()
一样。然而,不同于 call()
和 apply()
,bind()
方法不会立即调用函数。相反,它返回一个新的函数,其中 this
值被设置为你指定的对象。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
const func = person.fullName.bind(person1);
func("Oslo", "Norway");
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,bind()
创建了一个新的函数 func
,其中 this
值被设置为 person1
。这个函数不会立即被调用,但你可以稍后传入参数 “Oslo” 和 “Norway” 来调用它。
示例:带有多上下文的集中式记录器
这里有一个小型但复杂的应用示例,在这个例子中使用 call()
, apply()
, 或 bind()
可以提高效率——尤其是在处理用于记录目的的函数的部分应用时:
假设你有一个集中式的记录函数,用于记录不同用户执行的动作。使用 bind()
可以有效地将 this
上下文设置为不同的用户,避免了重复代码。
const logger = {
logAction: function(action) {
console.log(`${this.name} (ID: ${this.id}) 执行了: ${action}`);
}
};
const user1 = { name: "Alice", id: 101 };
const user2 = { name: "Bob", id: 202 };
// 为不同的用户创建新的记录函数
const logForUser1 = logger.logAction.bind(user1);
const logForUser2 = logger.logAction.bind(user2);
// 执行动作时无需手动传递用户上下文
logForUser1("login");
// 输出: Alice (ID: 101) 执行了: login
logForUser2("purchase");
// 输出: Bob (ID: 202) 执行了: purchase
为什么这样更高效?
上下文重用: 每次记录动作时,你都不需要手动传递用户上下文。上下文 (this
) 只绑定一次,使记录变得可重用且干净。
模块化: 如果你需要添加更多用户或动作,你可以快速地将它们绑定到 logger
上,而不必改变函数本身,使你的代码保持 DRY(不要做重复的工作)。