JavaScript的秘密生活:身份
Source: Dev.to
“点左侧”规则
Timothy 懒散地坐到主工作台的椅子上,把笔掉在一段代码上。他看起来很疲惫。
“我已经不再知道自己是谁了,Margaret,”他嘀咕道。
Margaret 停下手中的整理,走了过来。
“那是个深刻的哲学问题,Timothy。”
“这不是哲学,是这个函数,”他敲了敲纸张说,“我在user对象里写了一个printName函数。运行时它会打印 ‘Timothy’。但当我把完全相同的函数传给一个助手时,它忘记了自己是谁,打印出undefined。它正经历身份危机。”
Margaret 把一块滚动黑板拉到桌子旁,拿起一支粉笔。
“函数并没有危机。你只是错误地认为
this属于函数本身。事实并非如此。”
她在黑板上画了一个大函数。
“在 JavaScript 中,
this不是一个固定的标签。它是一个问题。当代码运行时,函数会环顾四周并自问:‘谁调用了我?’”
示例:方法调用
const user = {
name: "Timothy",
speak: function() {
console.log("My name is " + this.name);
}
};
user.speak();
// ^ 看向左侧
// 对象 'user' 正在调用该函数。
// 因此:'this' 是 'user'。
“看最后一行,”Margaret 指着点说,“规则很简单:看点左侧。”
“user这个词就在那儿,”Timothy 回答。
“没错。因为你是通过user调用它的,函数就会用‘谁调用了我?’这个问题的答案‘user’来回答。”
示例:丢失的上下文
Timothy 把他的 bug 写在黑板上。
const myFunction = user.speak;
myFunction();
// ^ 看向左侧
// 没有点。没有对象。
// 输出: "My name is undefined"
“我没有改动函数内部的代码!”Timothy 辩解道。“是同一个函数!”
“函数内部的代码确实没有变,”Margaret 同意,“但调用位置变了。”
“看看myFunction()左侧,有点吗?有对象吗?”
“没有。只有函数名。”
“正是如此。当没有点时,函数没有所有者。在我们始终使用的严格模式下,this会变成undefined。”
“那以前呢?”
“会默认指向全局的window对象——这是一场灾难的前奏。”
强制上下文
使用 .call() 的一次性调用
const stranger = { name: "Margaret" };
// 我们强制让 `speak` 此时使用 `stranger` 作为 `this`
user.speak.call(stranger);
// 输出: "My name is Margaret"
“使用
.call(),你是在告诉函数:‘我不在乎你现在在哪里。对于这一次特定的执行,你的身份就是这个对象。’”
使用 .bind() 的永久绑定
// 我们创建一个新函数,永久锁定到 `user`
const boundFunction = user.speak.bind(user);
boundFunction();
// 输出: "My name is Timothy"(永远如此)
“
.bind()并不会立即执行函数。它返回一个新的函数副本,永远记住它的所有者。无论以后怎么调用,this始终是user。”
总结
- 有点吗? →
this是左侧的对象。 - 没有点? →
this在严格模式下是undefined,在非严格模式下是全局对象。 - 使用了
.call()或.bind()? →this是你显式设置的对象。
“我以为这跟函数所在的位置有关,”Timothy 承认道。
“那是常见的误解,”Margaret 边把手上的粉笔抖落边说,“在 JavaScript 中,身份并不是关于你是谁,而是关于在你说话的那一刻,谁在握着你。”