JavaScript中this 的"千层套路":从默认绑定到箭头函数的五种指向
摘要 :
this是 JavaScript 最让人头疼的概念。本文从一段setTimeout的"惊喜"代码入手,拆解this的五种绑定规则------普通函数、对象方法、事件处理、call/apply/bind手动绑定、箭头函数------帮你彻底告别this困惑。
📑 目录
- 一段让你怀疑人生的
this代码 - 规则一:默认绑定------普通函数调用
- 规则二:隐式绑定------作为对象方法调用
- 规则三:事件处理函数绑定------this 指向触发元素
- 规则四:手动绑定------call、apply、bind
- 规则五:箭头函数------没有 this 的 this
- 一点总结
- 互动讨论
一段让你怀疑人生的 this 代码
先来看这段代码:
javascript
javascript
var name = '梅西';
let obj = {
name: '姆巴佩',
say: function(){
setTimeout(function() {
console.log(this.name);
}, 1000);
}
}
obj.say(); // 输出:梅西
你本期待看到"姆巴佩",结果却是"梅西"。
setTimeout 里的函数明明是写在 obj 里面的,为什么 this 指向了外面的 梅西?
这段代码覆盖了 this 的多个关键规则:普通函数、对象方法、setTimeout 异步执行、全局变量污染。理解了它,就理解了一大半的 this 规则。
我们从最基础的规则开始拆解。
规则一:默认绑定------普通函数调用
当一个函数作为普通函数(不是对象方法、不是构造函数、不是事件处理函数)被调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
javascript
ini
var name = '小许';
function say() {
console.log(this.name);
}
const fn = obj.say;
fn(); // 输出:小许(this 指向 window)
这里有一个完整的示例:
javascript
javascript
let obj = {
name: "大许",
say: function() {
console.log(this);
console.log(`${this.name}`);
}
}
const fn = obj.say; // 引用式赋值
fn(); // 这里函数是作为普通函数被调用,this 指向全局 window
obj.say 本来是对象的方法,但通过引用赋值给 fn 后,调用方式变成了"普通函数调用",this 丢失了原来的指向,回到了默认绑定。
如何在 window 上找到 name?
javascript
ini
var name = '梅西'; // var 会挂载到 window
// 或
window.name = '梅西';
let 和 const 声明的变量不会挂载到 window,即使是在全局作用域。common.js 的注释明确说明了这一点:
var声明会在变量环境中,会直接作为全局对象 window 的属性。let声明会在词法环境中,即使是全局变量也是与 window 对象隔离开的。
严格模式下的区别
可以在js文件第一行加上一句:"use strict"; 来强制切换为严格模式
严格模式下,普通函数调用时,
this无法通过默认绑定绑到外层作用域的全局对象上,这时this的值会变成undefined。
javascript
javascript
"use strict";
function say() {
console.log(this); // undefined
}
say();
这其实是一种"纠错"设计------避免你在普通函数中误用全局对象。
规则二:隐式绑定------作为对象方法调用
当函数作为对象的方法被调用时,this 指向调用该方法的对象。
javascript
javascript
let obj = {
name: "姆巴佩",
say: function() {
console.log(this.name);
}
}
obj.say(); // "姆巴佩"
这是最直观的场景:谁调用了函数,this 就指向谁。
高度总结:
this是一个指针,指向函数的调用者。函数运行时指定(不是声明的时候),不是像变量那样在编译阶段指定。
规则三:事件处理函数绑定------this 指向触发元素
当函数作为事件处理函数被调用时,this 指向触发事件的元素对象。
javascript
javascript
document.querySelector('.lnk').addEventListener('click', goBaidu);
function goBaidu(e) {
console.log(this); // 指向 <a href="..."> 元素
e.preventDefault(); // 阻止跳转
}
作为事件处理函数被调用,
this指向触发事件的元素对象。
关键点:
- 事件处理函数的
this不是 window,也不是定义函数的对象------而是触发事件的 DOM 元素。 - 这就是为什么
addEventListener里的回调函数不能用箭头函数,因为它会丢失this(后面会讲)。
为什么 e.preventDefault() 很重要?
背景说明:
form表单提交会默认提交,即使没有指定action,也会在本页面提交,导致刷新页面。e.preventDefault()阻止默认行为,页面不会刷新,可以后续在 JS 中处理数据提交,实现动态页面。
同样,<a href=""> 链接也会默认跳转,用 e.preventDefault() 阻止。
规则四:手动绑定------call、apply、bind
call、apply、bind 可以手动指定函数执行时的 this 指向。
| 方法 | 是否立即执行 | 传参方式 | 返回 |
|---|---|---|---|
call |
立即执行 | 逐个传递 | 函数执行结果 |
apply |
立即执行 | 数组传递 | 函数执行结果 |
bind |
不执行,返回新函数 | 逐个传递 | 新函数 |
javascript
ini
let obj = { name: "大许" };
let obj2 = { name: "大祥" };
obj.say.call(obj2); // this → obj2,立即执行
obj.say.apply(obj2); // 同上,传参用数组
const newSay = obj.say.bind(obj2); // 返回新函数,不立即执行
newSay(); // 执行新函数,this → obj2
// 有参数的情况
obj.speak.call(obj2, 1, 2); // 逐个传
obj.speak.apply(obj2, [1, 2]); // 数组传
const fn2 = obj.speak.bind(obj2);
fn2(1, 2);
bind 的典型场景:异步回调
关键场景:
bind手动指定this,返回一个全新的函数(不是立即执行的),使用场景是异步时的手动修改this指向。
回到开头的 setTimeout 问题,bind 可以解决:
javascript
javascript
setTimeout((function() {
console.log(this.name); // this 被绑定到 obj
}).bind(obj), 1000);
因为 bind 返回新函数且不立即执行,正好满足 setTimeout 延迟执行的场景。
规则五:箭头函数------没有 this 的 this
箭头函数不创建自己的 this ,它会从外层作用域中"继承" this。
javascript
javascript
let obj3 = {
name: '姆巴佩',
say: function(){
setTimeout(() => {
console.log(this.name); // "姆巴佩"
}, 1000);
}
}
obj3.say();
箭头函数里没有 this,所以它不会"丢失" this------它直接使用了外层 say 函数的 this(指向 obj3)。
箭头函数是简化函数,它根本不考虑
this,所以写在箭头函数中的this和写在箭头函数外的this是一样的。
这就是为什么 setTimeout 中用箭头函数能拿到正确的 this------它根本不创建自己的 this,直接从外层取。
一点总结
| 调用方式 | this 指向 |
|---|---|
| 普通函数调用(非严格模式) | 全局对象(window) |
| 普通函数调用(严格模式) | undefined |
| 对象方法调用 | 调用该方法的对象 |
| 事件处理函数 | 触发事件的 DOM 元素 |
构造函数(new) |
新创建的实例对象 |
call / apply / bind |
手动指定的对象 |
| 箭头函数 | 外层作用域的 this(无自己的 this) |
一句话心法 :
this不是你在哪里写的,而是你在哪里怎么调用的 。箭头函数是唯一的例外------它根本没有this。
互动讨论
setTimeout的第一个例子中,为什么var name = '梅西'能影响输出,而用let name = '梅西'就不行?- 事件处理函数中,为什么不能用箭头函数替代普通函数? 试想如果
addEventListener('click', () => {}),this会指向什么? call和apply有什么区别? 什么场景下用apply更合适?bind和call最大的区别是什么? 为什么异步场景需要bind?- 为什么 JavaScript 要设计
this这个机制? 如果完全没有this,代码会变成什么样?
📌 一点心得:
this不是 bug,它是一种设计选择------让函数在不同的调用上下文中复用。理解了"谁调用,指向谁"这条铁律,再加上箭头函数这个例外,你就掌握了this的核心。