this 关键字
同一个标签函数,可以先说出杯子的名字,再找不到那件商品,然后又说出另一只杯子的名字。花括号没动。变的是调用方式。JavaScript 在调用时选定 this,不是在你敲函数的时候。
普通方法调用之后
方法那一页把函数放在对象上,用 product.tag() 跑起来。那种形状里,this 就是点号前面的对象。那一页停在这里。这一页函数还在原处,调用变了:从对象上摘下来、用 call 对准别处、用 bind 锁住,或者写成箭头。
调用决定 this 的五种形状
📊 方法、裸调用、call、bind、箭头
| 形状 | 例子 | this 落在哪里 |
|---|---|---|
| 方法 | product.tag() |
点号前面的对象 |
| 裸调用 | let show = product.tag; show() |
不是那个对象(严格模式下是 undefined) |
| call | product.tag.call(cup) |
你交给 call 的对象,只对这一次 |
| bind | product.tag.bind(product) |
以后的调用都锁在那个对象上 |
| 箭头 | 写在另一个函数里的 () => this.name |
外层函数的 this,不是点号前面的对象 |
先给调用起名字,再读 this。把函数放在对象上,并不会把 this 冻住。作决定的是调用。bind 为以后的调用提前作决定。箭头不理调用,去看包着它的那个函数。
"use strict";
let product = {
name: "Mug",
tag() {
return this.name;
}
};
console.log(product.tag());
点号仍然点出对象
product.tag() 运行 tag,并把 this 设成 product。控制台打印 Mug。这是方法那一页的规则,留在这里当基线。后面四次调用是故意离开这条基线的。
"use strict";
let product = {
name: "Mug",
tag() {
if (this == null) {
return "missing";
}
return this.name;
}
};
let show = product.tag;
console.log(show());
把函数从对象上摘下来,杯子就跟着点号走了
show 和 product.tag 是同一个函数。show() 的点号前面没有对象,严格模式下 this 就是 undefined。守卫返回 missing。拿掉守卫,this.name 会抛错。这一页守着严格模式,结果才好预料。非严格脚本可能把裸调用指到全局对象上。
"use strict";
let product = {
name: "Mug",
tag() {
return this.name;
}
};
let cup = { name: "Cup" };
console.log(product.tag.call(cup));
call 把函数借给一个对象用一次
product.tag.call(cup) 跑的仍是杯子上的 tag,但这一次 this 是 cup。控制台打印 Cup。product.name 还是 Mug。call 不会把函数贴到 cup 上。额外参数放在数组里时,apply 做的是同一件事——这个标签用不到它们。
"use strict";
let product = {
name: "Mug",
tag() {
return this.name;
}
};
let show = product.tag.bind(product);
console.log(show());
bind 把对象留给以后的裸调用
bind 返回一个新函数。里面的 this 一直是 product,哪怕 show() 没有点号。控制台打印 Mug。原来的 tag 不动。按钮、计时器或其他函数准备在前面不带对象地调用方法时,就用 bind。
"use strict";
let shelf = {
name: "Shelf",
make() {
let product = {
name: "Mug",
tag: () => this.name
};
return product.tag();
}
};
console.log(shelf.make());
箭头不从点号取 this
product.tag() 看起来像方法调用,可 tag 是箭头。箭头没有自己的 this。它用的是 make 里的 this,而 make 是按 shelf.make() 跑的,所以 this 是 shelf。控制台打印 Shelf,不是 Mug。若 product 上是普通方法,就会打印 Mug。箭头在更早的一页出现过;这一页只看它们对 this 做了什么。
让调用和工作对上
对象在点号前面?就把 this 读成那个对象。函数摘下来了,又没绑定?别指望那个对象。换一个对象、只跑一次?用 call。以后不带点号的调用仍要看到这只杯子?先 bind。写的是箭头?往外看包着它的函数,不要看点号前面的对象。构造函数和类也会设置 this,那是后面的页面。下一课是 JSON:描述数据的文本,不是又一条 this 规则。
把锁好的标签放到页面上
用 bind 把 tag 锁在杯子上,再不带点号地调用那个新函数。把结果写进一个段落。
<!DOCTYPE html>
<html>
<head>
<title>this-keyword — page</title>
</head>
<body>
<p id="line">…</p>
<script>
"use strict";
let product = {
name: "Mug",
tag() {
return this.name;
}
};
let show = product.tag.bind(product);
let box = document.getElementById("line");
box.textContent = show();
</script>
</body>
</html>
段落显示 Mug。show() 的点号前面没有对象,这一行仍然写出杯子的名字,因为 bind 锁住了 this。只改 product.name,刷新,段落会跟着走。下一课:JavaScript 中的 JSON —— 描述数据的文本,不是 this 的新规则。
练习步骤
- 调用
product.tag(),读到Mug。 - 把函数放进
show,在严格模式下调用show(),读到missing。 - 调用
product.tag.call(cup),读到Cup。 - 把
tag绑到product上,不带点号调用新函数,读到Mug。 - 运行
shelf.make(),读到Shelf,不是Mug。 - 在 HTML 演示里只改
product.name,刷新,再读段落。
常见失误
- 因为函数待在对象上,就以为
this被冻住了 - 在严格模式下调用
show(),却还指望Mug - 以为
call把tag复制到了cup上 - 指望
bind改写原来的tag,而不是返回新函数 - 把箭头当成方法来写,却指望点号前面的对象
- 把下一页当成又一条
this规则——JSON 是数据的文本格式
小结
- 选定
this的是调用,不是花括号 object.method()把this设成点号前面的对象- 严格模式下的裸调用不会把
this指向那个对象 call把this设成你传入的对象,只对这一次bind返回一个this保持锁定的函数- 箭头从包围它的函数里取
this - 绑定后的调用可以填上页面里的一行
- 下一课:JSON,描述数据的文本
🧠 测试您的知识
测试您的知识
通过这个互动测验挑战自己,看看你对这个主题的理解程度如何
📝 说明
- 仔细阅读每个问题
- 为每个问题选择最佳答案
- 您可以随时重新参加测验
- 您的进度将显示在顶部