彻底搞懂 JavaScript 中的 this 关键字
从一个实战场景说起
最近在做一个基于 localStorage 的待办事项应用,核心功能是通过表单收集用户输入并保存到本地存储。在处理表单提交事件时,遇到了一个经典问题:this 的指向到底是谁?
html
<form class="add-items">
<input type="text" name="item" placeholder="Add a new tapas" required>
<input type="submit" value="+ Add Item">
</form>
<a href="#" class="lnk">跳转百度</a>
javascript
document.querySelector(".lnk").addEventListener("click", goBaidu);
function goBaidu(e) {
console.log(this); // this 指向什么?
e.preventDefault();
}
如果您也经常遇到类似困惑,那么这篇文章将帮助您彻底理解 this 的绑定规则。
this 的核心原则
this是在函数运行时确定的,而非声明时!
this 的指向取决于调用函数的方式 ,而非函数定义的位置。这是理解 this 的关键。
六种绑定规则
规则一:普通函数调用
当函数作为普通函数被调用时,this 指向全局对象 window(非严格模式下)。
javascript
var name = "李刚";
function sayName() {
console.log(this.name); // 输出:李刚
}
sayName(); // this 指向 window
严格模式下的变化:
javascript
"use strict";
function sayName() {
console.log(this); // 输出:undefined
}
sayName();
启用严格模式 "use strict" 可以避免 this 意外指向 window,是现代 JavaScript 开发的最佳实践。
规则二:对象方法调用
当函数作为对象的方法被调用时,this 指向调用该方法的对象。
javascript
let obj = {
name: "王文",
say: function() {
console.log(this.name); // 输出:王文
}
};
obj.say(); // this 指向 obj
注意:引用式赋值的陷阱
javascript
const fn = obj.say; // 引用式赋值,函数脱离了对象
fn(); // this 指向 window(非严格模式)
当对象的方法被单独赋值给变量后,this 的绑定关系会丢失。
规则三:构造函数调用
当函数作为构造函数使用 new 关键字调用时,this 指向新创建的实例对象。
javascript
function Person(name) {
this.name = name; // this 指向新创建的实例
}
const person = new Person("张三");
console.log(person.name); // 输出:张三
规则四:事件处理函数调用
在 DOM 事件处理函数中,this 指向触发事件的元素。
javascript
document.querySelector(".lnk").addEventListener("click", function(e) {
console.log(this); // this 指向 <a> 元素
e.preventDefault();
});
规则五:手动指定 this(call/apply/bind)
call 和 apply
两者都能手动指定 this 指向,区别在于参数传递方式:
javascript
let obj = {
name: "王文",
say: function() {
console.log(this.name);
},
speak: function(a, b) {
console.log(a, b);
console.log(this);
}
};
let obj2 = {
name: "金大轩"
};
obj.say.call(obj2); // 输出:金大轩
obj.say.apply(obj2); // 输出:金大轩
obj.speak.call(obj2, 1, 2); // 参数逐个传递
obj.speak.apply(obj2, [1, 2]); // 参数通过数组传递
bind
bind 方法不会立即执行函数,而是返回一个绑定了 this 的新函数:
javascript
const fn2 = obj.speak.bind(obj2); // 返回新函数,不执行
fn2(1, 2); // 调用新函数时才执行
规则六:箭头函数中的 this
箭头函数没有自己的
this!
箭头函数的 this 继承自外层作用域的 this,这是它与普通函数的本质区别。
javascript
let obj = {
name: "姆巴佩",
say: function() {
setTimeout(() => {
console.log(this.name); // 输出:姆巴佩(继承外层 this)
}, 1000);
}
};
obj.say();
经典面试题:setTimeout 中的 this 陷阱
这是一个非常经典的面试题,比较两种写法的区别:
写法一:普通函数 + bind
javascript
var name = "梅西";
let obj = {
name: "姆巴佩",
say: function() {
setTimeout((function() {
console.log(this.name) // 输出:姆巴佩
}).bind(this), 1000);
}
};
obj.say();
通过 bind(this) 手动绑定了 setTimeout 回调函数中的 this。
写法二:箭头函数
javascript
var name = "梅西";
let obj = {
name: "姆巴佩",
say: function() {
setTimeout(() => {
console.log(this.name) // 输出:姆巴佩
}, 1000);
}
};
obj.say();
箭头函数继承了外层 say 方法中的 this,无需手动绑定。
如果不用箭头函数也不绑定 this
javascript
var name = "梅西";
let obj = {
name: "姆巴佩",
say: function() {
setTimeout(function() {
console.log(this.name) // 输出:梅西(this 指向 window)
}, 1000);
}
};
obj.say();
普通函数作为 setTimeout 回调时,this 默认指向 window。
深度分析:var 与 let 对 this 的影响
var 声明的变量会挂载到 window 对象
javascript
var name = "全局变量";
console.log(window.name); // 输出:全局变量
console.log(this.name); // 输出:全局变量(在全局作用域中)
这就是为什么普通函数调用时 this.name 能访问到用 var 声明的变量。
let/const 声明的变量不会污染 window
javascript
let name = "全局变量";
console.log(window.name); // 输出:undefined
console.log(this.name); // 输出:undefined
let 和 const 声明的变量不会成为 window 对象的属性,这是 ES6 带来的重要改进。
this 指向速查表
| 调用方式 | this 指向 |
|---|---|
| 普通函数调用 | window(非严格模式)/ undefined(严格模式) |
| 对象方法调用 | 调用该方法的对象 |
| 构造函数调用 | 新创建的实例对象 |
| 事件处理函数 | 触发事件的 DOM 元素 |
| call/apply/bind | 手动指定的对象 |
| 箭头函数 | 外层作用域的 this |
总结
理解 this 的关键在于:关注函数的调用方式,而非定义位置。
在实际开发中,推荐以下实践:
- 始终启用严格模式
"use strict" - 使用箭头函数避免
this绑定问题 - 使用
bind预绑定this(适合需要复用的函数) - 优先使用
let/const而非var
掌握 this 的绑定规则,不仅能让您写出更优雅的代码,也是通过前端面试的必备技能。