JavaScript中的this指向规则:this的指向取决于函数调用方式而非定义位置。
主要规则包括:
1)全局作用域中指向window;
2)独立函数调用默认绑定全局对象(严格模式下为undefined);
3)对象方法调用指向调用对象;
4)构造函数指向新实例;
5)箭头函数继承定义时的外层this。
优先级顺序为:new绑定>显式绑定>隐式绑定>默认绑定。
箭头函数this固定不可修改,适合回调场景但不可用作构造函数。
严格模式会影响默认绑定结果,类方法需注意this丢失问题。
理解这些规则需把握"调用方式决定this指向"的核心原则。
call、apply和bind是 JavaScript 中用于显式绑定this的三个核心方法。它们都能改变函数执行时的上下文,但在调用时机 和参数传递方式上有所不同。
关联阅读推荐
在 JavaScript 中,this 的指向并不是在编写代码时确定的,而是在函数调用时根据调用方式动态决定的。
理解 this 的关键在于:this 不是在编写时绑定的,而是在函数被调用时绑定的。
以下是 this 在不同场景下的指向规则总结:
JavaScript 中 this 的指向规则
| 调用场景 | 调用方式/示例 | this 指向对象 |
备注/说明 |
|---|---|---|---|
| 全局作用域 | 在浏览器控制台或脚本直接执行 console.log(this) |
window 对象 (浏览器) / global (Node.js) |
在严格模式下("use strict"),全局作用域中 this 依然指向全局对象。 |
| 独立函数调用 | 直接执行 fn() |
window 对象 (浏览器) / global (Node.js) |
这是非严格模式下的默认绑定。在严格模式下,this 为 undefined。 |
| 对象方法调用 | obj.fn() |
调用该方法的对象 (即 obj) |
谁调用指向谁。如果方法赋值给变量再调用(如 const fn = obj.fn; fn()),则变成独立函数调用。 |
| 构造函数调用 | new Fn() |
新创建的实例对象 | new 操作符会强制将 this 绑定到正在构建的新对象上。 |
| 箭头函数 | () => {} |
定义时的外层作用域的 this |
箭头函数没有自己的 this,它继承自包含它的函数/作用域,且一旦绑定无法更改(call/apply/bind 无效)。 |
| DOM 事件绑定 | element.onclick = fn 或 addEventListener |
触发事件的 DOM 元素 | 标准事件监听中,this 指向绑定事件的元素。 |
| 显式绑定 | fn.call(obj) / fn.apply(obj) / fn.bind(obj) |
call/apply/bind 的第一个参数指定的对象 |
手动强制指定 this 的指向。 |
| 计时器/异步回调 | setTimeout(fn, 1000) 或 setInterval |
window 对象 (浏览器) |
回调函数通常作为独立函数执行。若想在回调中使用外部 this,可使用箭头函数或 bind。 |
特殊情况与注意事项
-
严格模式(
"use strict"):- 在独立函数调用中,
this不再指向window,而是undefined。这可以防止无意中修改全局对象。
- 在独立函数调用中,
-
优先级:
-
new调用 > 显式绑定 (call/apply/bind) > 对象方法调用 > 默认绑定 (独立函数)。 -
箭头函数的
this具有最高优先级 ,因为它不会被任何方式(包括new)修改。
-
-
事件处理中的区别:
-
addEventListener中,this指向元素。 -
内联事件(
onclick="fn()")中,this指向window,除非在 HTML 属性中显式传入this(如onclick="fn(this)")。
-
理解 this 的关键在于记住函数被调用时的具体形式,而不是它在哪里定义(箭头函数除外)。
关键点解析与易错点
1. "谁调用,指向谁"原则
这是判断 this 最通用的法则(箭头函数除外)。
obj.fn()->this是obj。fn()->this是window(非严格) 或undefined(严格)。
2. 箭头函数的特殊性
箭头函数没有 自己的 this。它像是一个"透视镜",直接穿透看到定义它那一层代码的 this 是什么,它就是什么。
- 无法修改 :对箭头函数使用
call,apply,bind传入第一个参数是无效的,会被忽略。 - 常见用途 :在
setTimeout、数组方法 (map,filter) 或事件监听器中,为了保留外层对象(如 Vue/React 组件实例)的this。
3. 严格模式的影响
在现代开发(模块化、构建工具、React/Vue 等)中,默认通常都是严格模式。
- 这意味着:独立的函数调用
func()中,this是undefined,而不是window。如果你尝试访问this.property,会直接报错Cannot read properties of undefined。
4. 类方法中的 this 丢失
在 ES6 Class 中,方法默认是不绑定 this 的。
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
}
const p = new Person('Alice');
const sayHi = p.greet;
sayHi();
// 报错 (严格模式): Cannot read properties of undefined (reading 'name')
// 因为 sayHi 作为普通函数调用,this 为 undefined
解决方案:
- 在构造函数中绑定:
this.greet = this.greet.bind(this); - 使用箭头函数定义类字段(推荐):
greet = () => { ... }
5. 优先级
如果多种规则冲突,优先级如下:
new绑定 (构造函数) >- 显式绑定 (
call/apply/bind) > - 隐式绑定 (对象方法
obj.fn()) > - 默认绑定 (独立函数调用)
(注:箭头函数不参与此优先级排序,它在定义时就确定了)
箭头函数的作用域
箭头函数(Arrow Functions)是 ES6 引入的一种简洁的函数语法,它在作用域 (Scope)和 this 指向上与传统函数(普通函数)有显著区别。
核心结论:箭头函数没有自己的 this、arguments、super 或 new.target 绑定。它的 this 值是由定义时所在的上下文(外层作用域)决定的,也就是所谓的"词法作用域"(Lexical Scoping)。
以下是详细解析:
1. this 的词法作用域(最核心的区别)
- 普通函数 :
this的值取决于调用方式 。- 作为对象方法调用:
this指向该对象。 - 作为普通函数调用:
this指向全局对象(浏览器中是window,严格模式下是undefined)。 - 使用
call/apply/bind:this可以被显式修改。
- 作为对象方法调用:
- 箭头函数 :
this的值取决于定义位置 。- 它不会创建自己的
this,而是捕获其所在上下文的this值。 - 一旦定义,
this就固定了,无法通过call、apply或bind改变。
- 它不会创建自己的
代码示例
javascript
const obj = {
name: 'Alice',
// 普通函数
regularFunc: function() {
console.log('Regular:', this.name);
// 这里的 this 指向 obj
},
// 箭头函数
arrowFunc: () => {
console.log('Arrow:', this.name);
// 这里的 this 不指向 obj,而是指向定义时的外层作用域(通常是 window 或 undefined)
}
};
obj.regularFunc(); // 输出: Regular: Alice
obj.arrowFunc(); // 输出: Arrow: undefined (在浏览器非严格模式下可能是 window)
2. 典型应用场景:回调函数中的 this
箭头函数最大的用处是在回调函数中保持外层 this 的上下文,避免了 var that = this 或 .bind(this) 的繁琐写法。
场景:定时器或数组遍历
javascript
function Counter() {
this.count = 0;
// 错误示范:普通函数在 setInterval 中,this 指向 window
setInterval(function() {
this.count++;
// 报错或无效,因为这里的 this 不是 Counter 实例
}, 1000);
// 正确示范:箭头函数继承外层的 this (即 Counter 实例)
setInterval(() => {
this.count++;
// 这里的 this 就是 Counter 实例,可以正常访问 count
console.log(this.count);
}, 1000);
}
const c = new Counter();
3. 其他作用域特性
除了 this,箭头函数在以下方面也没有自己的绑定,同样遵循词法作用域:
-
arguments对象:- 箭头函数内部没有
arguments对象。 - 如果访问
arguments,它会去外层作用域查找。 - 替代方案 :使用剩余参数(Rest Parameters)
...args。
- 箭头函数内部没有
javascript
const func = () => {
console.log(arguments); // ReferenceError: arguments is not defined (除非外层有)
};
// 正确用法
const funcWithRest = (...args) => {
console.log(args);
};
-
不能用作构造函数:
- 箭头函数没有
[[Construct]]内部方法,不能使用new关键字调用。 - 尝试
new会抛出TypeError。
- 箭头函数没有
-
没有
super、new.target:- 同样继承自外层作用域。
4. 总结对比表
| 特性 | 普通函数 (Function Declaration/Expression) | 箭头函数 (Arrow Function) |
|---|---|---|
this 绑定 |
动态绑定 (取决于调用方式) | 词法绑定 (取决于定义位置) |
arguments |
有自己的 arguments 对象 |
无 ,需使用剩余参数 ...args |
| 可用作构造函数 | 是 (new Func()) |
否 (抛出错误) |
可更改 this |
是 (call, apply, bind) |
否 (调用这些方法无效) |
拥有 super/new.target |
是 | 否 (继承自外层) |
| 适用场景 | 对象方法、构造函数、需要动态 this 的场景 |
回调函数、闭包、保持外层 this 的场景 |
什么时候不应该使用箭头函数?
- 定义对象方法时 :如果你需要方法内部的
this指向该对象本身,不要用箭头函数。 - 定义构造函数时 :需要用
new调用的函数。 - 需要访问
arguments对象时。 - 事件监听器中需要动态
this时 (虽然现代开发中常用箭头函数配合显式传参,但如果依赖this指向触发事件的 DOM 元素,普通函数更方便)。
理解箭头函数的"词法作用域"特性,关键在于记住:它只是外层作用域的一个快捷引用,而不是一个新的作用域边界(针对 this 而言)。
call / apply / bind 对比总结
call、apply 和 bind 是 JavaScript 中用于显式绑定 this 的三个核心方法。它们都能改变函数执行时的上下文,但在调用时机 和参数传递方式上有所不同。
| 特性 | call |
apply |
bind |
|---|---|---|---|
| 核心功能 | 调用函数,并指定 this 指向 |
调用函数,并指定 this 指向 |
不立即调用,返回一个新函数(该函数的 this 被永久绑定) |
| 执行时机 | 立即执行 | 立即执行 | 延迟执行 (返回的函数需要在后续调用) |
| 返回值 | 原函数的执行结果 | 原函数的执行结果 | 一个新函数 (闭包) |
| 参数传递方式 | 参数列表 (逐个传递) func.call(thisArg, arg1, arg2) |
数组/类数组对象 func.apply(thisArg, [arg1, arg2]) |
参数列表 (逐个传递,也可预置部分参数) func.bind(thisArg, arg1) |
| 主要用途 | 1. 借用其他对象的方法 2. 构造函数继承 | 1. 借用方法 2. 处理数组参数 (如 Math.max) 3. 将类数组转为数组 (旧写法) |
1. 事件回调中保持 this 2. 柯里化 (Currying) 3. 固定函数的上下文供后续使用 |
能否修改 this |
能 (仅对当次调用有效) | 能 (仅对当次调用有效) | 能 (永久绑定,后续无法再通过 call/apply 修改) |
| 示例代码 | fn.call(obj, 1, 2) |
fn.apply(obj, [1, 2]) |
const newFn = fn.bind(obj, 1);<br>newFn(2); |
详细场景解析与代码示例
1. 参数传递的区别 (call vs apply)
这是两者最直观的区别。
如果你知道参数的具体个数,用 call 更直观;如果参数是一个数组,用 apply 更方便。
javascript
function sum(a, b, c) {
return a + b + c;
}
const args = [1, 2, 3];
// call: 逐个传参
console.log(sum.call(null, 1, 2, 3)); // 6
// apply: 传入数组
console.log(sum.apply(null, args)); // 6
// 经典案例:求数组最大值
// Math.max 不接受数组,只接受参数列表
console.log(Math.max.apply(null, [1, 5, 9])); // 9
// (ES6 后通常用展开运算符: Math.max(...[1, 5, 9]))
2. 执行时机的区别 (call/apply vs bind)
call 和 apply 是"即插即用",而 bind 是"制造工具"。
javascript
const obj = { name: 'Alice' };
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
// call/apply: 立即执行
greet.call(obj, 'Hello', '!'); // 输出: Hello, Alice!
greet.apply(obj, ['Hi', '.']); // 输出: Hi, Alice.
// bind: 返回新函数,不立即执行
const boundGreet = greet.bind(obj, 'Hey');
// 此时什么也没发生
// 稍后在需要的地方调用
boundGreet('?'); // 输出: Hey, Alice?
3. bind 的永久绑定特性
一旦使用 bind 绑定了 this,后续再尝试用 call 或 apply 修改这个新函数的 this 是无效的。
javascript
function show() {
console.log(this.name);
}
const objA = { name: 'A' };
const objB = { name: 'B' };
const funcA = show.bind(objA);
funcA(); // 输出: A
funcA.call(objB); // 输出: A (依然指向 objA,call 失效)
4. 常见应用场景
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 构造函数继承 | call |
在子类构造函数中调用父类构造函数,传递参数方便。 Parent.call(this, name) |
| 数组操作借用 | call / apply |
让非数组对象使用数组方法。 Array.prototype.slice.call(arguments) |
| 定时器/事件回调 | bind |
需要保存 this 上下文供未来回调使用。 setTimeout(this.handler.bind(this), 1000) |
| 函数柯里化 | bind |
固定部分参数,生成新函数。 const add5 = add.bind(null, 5) |
| 求最大/最小值 | apply |
将数组展开为参数列表传给 Math.max/min。 Math.max.apply(null, arr) |
记忆口诀
- Call :C omma (逗号) -> 参数用逗号 分隔,立即调用。
- Apply :A rray (数组) -> 参数用数组 包裹,立即调用。
- Bind :B inding (绑定) -> 绑定 后返回新函数,以后再调。
注意事项
- 箭头函数 :这三个方法对箭头函数内部的
this无效 。箭头函数的this在定义时已确定,无法被call/apply/bind改变(虽然可以调用它们,但第一个参数会被忽略)。 null或undefined:在非严格模式下,如果传入null或undefined,this会指向全局对象(浏览器为window);在严格模式下,this就是null或undefined。