this 是 JavaScript 中最容易混淆的核心关键字之一,它不是在定义函数时确定,而是在函数执行调用时动态绑定 。严格模式、普通函数、对象方法、构造函数、事件处理、call/apply/bind、箭头函数都会彻底改变 this 的指向,本文结合完整示例代码拆解每一种场景,理清所有边界。
一、基础前置知识:严格模式与全局对象
1. 全局 window 与变量声明差异
js
运行
javascript
"use strict"; // 严格模式
var name = "王五"; // var声明变量会挂载到window全局对象
let test = "测试"; // let/const 不会污染window
console.log(window.name); // 王五
console.log(window.test); // undefined
var:全局声明自动绑定window,造成全局污染;let / const:存在块级作用域,不挂载全局;- 严格模式
"use strict":禁止普通函数中this指向window,普通函数直接调用时this = undefined,避免全局篡改。
2. 核心底层规则
this 绑定优先级(从低到高):全局默认绑定 < 对象隐式绑定 < call/apply 显式绑定 < bind 硬绑定 < 构造函数 new < 箭头函数(不绑定 this,继承外层)
二、场景 1:普通独立函数调用(默认绑定)
非严格模式
普通函数直接执行,this 指向全局 window:
js
运行
javascript
function normalFn() {
console.log(this); // window
console.log(this.name); // 王五
}
normalFn();
严格模式
开启 "use strict" 后,普通函数 this 为 undefined,访问全局变量会直接报错,防止误操作全局:
js
运行
javascript
"use strict";
function normalFn() {
console.log(this); // undefined
console.log(this.name); // Uncaught TypeError
}
normalFn();
坑点:对象方法提取为独立函数
把对象方法赋值给变量后调用,会降级为普通函数,丢失原有对象绑定:
js
运行
javascript
let obj = {
name: "张三",
say: function () {
console.log(this.name);
}
};
const fn = obj.say; // 仅拿到函数引用,脱离对象
fn(); // 严格模式下undefined,非严格模式输出全局name
三、场景 2:作为对象方法调用(隐式绑定)
当函数作为对象的属性(方法)执行时,this 直接指向调用该方法的对象:
js
运行
javascript
let obj = {
name: "张三",
say: function () {
console.log(this); // {name: "张三", say: ƒ}
console.log(this.name); // 张三
}
};
obj.say();
关键规则
谁调用方法,this 就指向谁,链式调用只看最后调用的对象:
js
运行
css
let a = { name: "A", fn: function() { console.log(this.name) } }
let b = { name: "B", a: a }
b.a.fn(); // 输出A,this指向a,不是b
四、场景 3:手动修改 this 指向 --- call /apply/bind(显式绑定)
三个方法都能手动指定函数内 this,核心区别在是否立即执行 与传参格式。
1. call (obj, 参数 1, 参数 2...)
立即执行函数,参数逐个传递
js
运行
ini
let obj2 = { name: "李四" };
obj.say.call(obj2); // this指向obj2,输出 李四
obj.speak.call(obj2, "你好", "我是李四");
2. apply (obj, 参数数组)
立即执行函数,参数统一放进数组传递,其余逻辑和 call 完全一致
js
运行
arduino
obj.speak.apply(obj2, ["你好", "我是李四"]);
3. bind(obj)
不会立即执行,返回一个全新永久绑定 this 的函数,适合异步场景(事件、定时器)
js
运行
arduino
const fn2 = obj.speak.bind(obj2);
fn2("你好", "我是李四"); // 新函数执行,this永远固定obj2
实战:事件绑定中 bind 的使用
事件监听器会自动修改函数 this 为触发 DOM 元素,如果需要固定 this,只能用 bind:
js
运行
javascript
const oForm = document.querySelector('.add-items');
function addItem(e) {
console.log(this.name); // 想要拿到obj的name
e.preventDefault();
}
// call/apply会直接执行函数,不能丢给addEventListener
const addItemBind = addItem.bind(obj);
oForm.addEventListener('submit', addItemBind);
call /apply/bind 对比表
表格
| 方法 | 是否立即执行 | 传参形式 | 返回值 | 使用场景 |
|---|---|---|---|---|
| call | 是 | 逐个传入 | 函数执行结果 | 参数数量少、临时调用 |
| apply | 是 | 数组传入 | 函数执行结果 | 参数为数组、动态传参 |
| bind | 否 | 新函数调用时传参 | 永久绑定 this 的新函数 | 事件、定时器、异步回调 |
五、场景 4:DOM 事件处理函数中的 this
给 DOM 绑定事件,触发时处理函数内 this 固定指向触发事件的 DOM 元素 ,等同于 e.target(特殊冒泡场景除外):
js
运行
javascript
document.querySelector('.lnk').addEventListener('click', goBaidu);
function goBaidu(e) {
console.log(this); // <a class="lnk"> 触发点击的a标签
e.preventDefault(); // 阻止a标签默认跳转
}
- 不使用 bind:
this= DOM 元素 - 使用 bind 绑定自定义对象:
this= bind 传入的对象
六、场景 5:构造函数 new 调用
函数通过 new 关键字当作构造函数执行时,底层做 4 件事:
- 创建空新对象;
- 将新对象绑定为函数内
this; - 执行构造函数;
- 若无手动 return 对象,自动返回新对象。
js
运行
javascript
function Person(name) {
this.name = name; // this指向新建实例
}
const p = new Person("小明");
console.log(p.name); // 小明
七、场景 6:箭头函数 --- 无独立 this
箭头函数不存在自己的 this ,不会绑定上述任何规则,会直接继承定义时外层作用域的 this:
- 不能用作构造函数(new 会报错);
call/apply/bind无法修改箭头函数 this;- 事件回调、定时器中常用,保留外层 this;
js
运行
javascript
let testObj = {
name: "箭头测试",
fn: () => {
console.log(this.name); // 外层全局name:王五,不会指向testObj
}
}
testObj.fn();
八、完整优先级总结(判断 this 万能流程)
拿到函数调用代码,从上到下匹配第一条规则即可:
- 是否为箭头函数:this = 定义时外层 this;
- 是否用
new构造调用:this = 新建实例; - 是否使用
bind硬绑定:this = bind 传入对象; - 是否使用
call/apply:this = 传入的第一个参数; - 是否作为对象方法调用:this = 调用方法的对象;
- 普通独立调用:严格模式 this=undefined,否则 window。
九、完整可运行示例代码(整合文中全部逻辑)
js
运行
javascript
"use strict";
// 全局var挂载window
var name = "王五";
let oForm = document.querySelector('.add-items');
let obj = {
name: "张三",
say: function () {
console.log("say的this:", this);
console.log("name:", this.name)
},
speak: function (a, b) {
console.log("参数:", a, b);
console.log("speak的this:", this);
}
}
let obj2 = { name: "李四" };
// 1. 对象方法调用
obj.say();
// 2. 提取为普通函数调用
const fn = obj.say;
// fn(); // 严格模式 this = undefined
// 3. call / apply 临时修改this
obj.say.call(obj2);
obj.say.apply(obj2);
obj.speak.call(obj2, '你好', '我是李四');
obj.speak.apply(obj2, ['你好', '我是李四']);
// 4. bind 返回新函数,永久绑定this
const fn2 = obj.speak.bind(obj2);
fn2('你好', '我是李四');
// 5. bind用于事件监听
function addItem(e) {
console.log("表单提交this:", this);
e.preventDefault();
}
const addItemBind = addItem.bind(obj);
oForm.addEventListener('submit', addItemBind);
// 6. DOM事件原生this指向DOM
document.querySelector('.lnk').addEventListener('click', goBaidu);
function goBaidu(e) {
console.log("点击链接this:", this);
e.preventDefault();
}
// 7. 箭头函数无自身this
const arrowTest = () => {
console.log("箭头函数this:", this);
}
arrowTest();
十、写在最后
this 的难点不在于概念复杂,而在于多场景绑定规则互相覆盖。日常开发记住两个核心技巧:
- 事件、定时器需要固定自定义
this→ 使用bind; - 需要复用外层上下文
this(如定时器、嵌套回调)→ 使用箭头函数; - 临时切换对象调用函数 → 使用
call / apply。分清每种调用方式对应的绑定规则,就能彻底规避this指向错乱的 bug。