JavaScript 中的 this 指向

一、JavaScript 的高级

1. 函数中的 this 指向

1)this 的案例

this 的三种调用方式:

  • 直接调用:函数独立调用时,this 默认指向 window 对象
  • 对象方法调用:函数作为对象方法调用时,this 指向调用它的对象
  • call/apply 调用:通过 call/apply 显式指定 this 绑定

this 的绑定规则:

  • 运行时绑定:this 的值在函数执行时才确定,类似于函数参数
  • 绑定无关性:与函数定义位置无关,只与调用方式有关
  • 四种绑定规则
    • 默认绑定(独立函数调用)
    • 隐式绑定(对象方法调用)
    • 显式绑定(call/apply/bind)
    • new 绑定(构造函数调用)

默认绑定示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 1.1、定义函数(foo 默认 this 指向的是 window)
function foo() {
console.log("foo: ", this);
}
foo();

// 1.2、函数定义在对象中(但是独立调用,bar 的 this 指向依旧是 window)
const obj = {
name: "kuchen",
bar: function () {
console.log("bar: ", this);
}
}
var baz = obj.bar;
baz();

// 1.3、高阶函数,将函数作为参数传入(调用者依旧是 window,独立调用,fn 函数内 this 指向 window)
function test(fn) {
console.log('test的this指向:', this);
fn();
}
test(foo);

// 1.4、立即执行函数(IIFE)(调用的依旧是 window)
(function () {
console.log("立即执行函数的this:", this);
})();

// 1.5、定时器中的回调函数(调用的依旧是 window)
setTimeout(function () {
console.log("定时器中的this: ", this);
}, 1000);

// 1.6、数组方法中的回调函数(指向的依旧是 window)
const array = [1, 2, 3];
array.forEach(item => {
console.log("forEach中的this指向: ", this);
});

// 1.7、箭头函数中的 this(指向的依旧是 window)
const arrowFn = () => {
console.log("箭头函数中的this指向: ", this);
}
arrowFn();

// 1.8、函数表达式中的 this(指向的依旧是 window)
const funExpr = function () {
console.log("函数表达式中的this指向: ", this);
}
funExpr();

// 1.9、严格模式下的独立调用
function strictMode() {
// 严格模式下,独立调用的 this 指向的是 undefined
// 非严格模式下,独立调用的 this 指向的才是 window
'use strict';
console.log("严格模式下的独立调用的this指向: ", this);
}
strictMode();

默认绑定说明:

  • 独立函数调用:函数未被绑定到任何对象上直接调用
  • 对象方法独立调用:即使函数定义在对象中,独立调用时仍指向 window

严格模式下的 this:

  • 严格模式特性:使用 "use strict" 声明后,独立函数调用的 this 变为 undefined
  • 实际应用注意
    • 打包工具(如 webpack)默认使用严格模式
    • 避免直接依赖 this 指向 window 的特性

2)默认绑定的介绍

例题:默认绑定调用示例

  • 独立函数调用:当函数没有被绑定到任何对象上独立调用时,使用默认绑定规则
  • 非严格模式下的 this 指向:在非严格模式下,独立调用的函数中 this 指向 window 对象
  • 严格模式下的特殊处理:严格模式下独立调用的函数中 this 指向 undefined,这是一个需要特别注意的例外情况
  • 替代方案:若确实需要使用 window 对象,建议直接使用 window 而非通过 this 间接引用
  • 嵌套调用情况:函数 A 调用函数 B,函数 B 又调用函数 C,这种多层嵌套的独立函数调用中,this 依然指向 window
  • 本质判断标准:只要满足”函数没有被绑定到某个对象上进行调用”这一条件,就属于默认绑定情况

例题:函数定义对象独立调用

  • 对象方法赋值调用:将对象方法赋值给变量后独立调用,如 var baz = obj.bar; baz(),此时 this 指向 window
  • 高阶函数调用:当函数作为参数传递给高阶函数并在其内部被调用时,如 test(obj.bar),也属于独立函数调用
  • 调用本质分析:这种调用方式本质上与直接赋值调用相同,都是将函数引用传递给另一个变量/参数后再执行
  • 严格模式影响:在严格模式下,上述所有独立调用情况中的 this 都将指向 undefined 而非 window
  • 实际开发建议:在需要明确使用 window 对象时,建议直接引用 window 而非依赖 this 的默认绑定行为

3)隐式绑定的介绍

隐式绑定的概念:

  • 调用方式:通过某个对象发起函数调用,调用位置中由对象发起函数调用
  • 术语来源:源自《你不知道的 JavaScript》一书(上中下三册),是该书提出的专业术语
  • 绑定机制:JS 引擎会自动将发起调用的对象绑定到函数的 this 上

隐式绑定示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 2.1、对象方法调用
const person = {
name: "张三",
sayHello: function () {
console.log("你好,我是", this.name);
}
}
person.sayHello(); // this 指向 person 对象,最基本的隐式绑定,通过对象调用方法时,this 指向该对象。

// 2.2、多层对象嵌套
const student = {
name: "李四",
info: {
age: 18,
sayAge: function () {
console.log("我的年龄是", this.age);
}
}
}
student.info.sayAge(); // this 指向 info 对象,当对象嵌套时,this 指向直接调用该方法的对象。

// 2.3、对象方法赋值给变量后调用(隐式绑定丢失)
const teacher = {
name: "王五",
teach: function () {
console.log(this.name, "正在讲课");
}
}
const teachFn = teacher.teach;
teachFn(); // this 指向 window,因为当对象方法被赋值给变量后调用,会丢失隐式绑定,变成独立调用。

// 2.4、回调函数中的隐式绑定
const button = {
name: "按钮",
click: function () {
console.log(this.name, "被点击了");
}
}
setTimeout(button.click, 1000); // 在回调函数中,方法会失去隐式绑定,this 指向 window。

// 2.5、数组方法中的隐式绑定
const numbers = [1, 2, 3];
const objInfo = {
name: "数组对象",
print: function () {
console.log(this.name);
}
}
numbers.forEach(objInfo.print); // this 指向 window,因为在数组方法中传入对象方法作为回调,也会失去隐式绑定。

隐式绑定说明:

  • 赋值原理:将 foo 函数直接赋值给 obj 的 bar 属性,类似于变量赋值
  • 调用过程:通过 obj.bar() 调用时,JS 引擎隐式地将 obj 绑定为 this
  • 本质特征:函数调用时由哪个对象发起,this 就隐式绑定到该对象
  • 灵活特性:JavaScript 的这种绑定机制非常灵活,但也是一把双刃剑

隐式绑定总结:

  • 核心特点:调用函数时通过对象发起,this 自动绑定到该对象
  • 绑定过程:JS 引擎内部自动完成绑定,开发者无需显式操作
  • 术语解释:称为”隐式”是因为绑定过程对开发者不可见
  • 典型场景:对象方法调用是最常见的隐式绑定情况

例题:隐式绑定调用示例

  • 案例 1:直接通过对象调用方法,this 指向调用对象
  • 案例 2:将对象方法赋值给变量后调用,this 指向发生变化
  • 案例 3:多层对象调用时,this 指向直接调用该方法的对象
  • 共同特点:所有案例都遵循”谁调用就绑定谁”的基本原则

4)new 绑定的介绍

基本概念:

  • 构造函数调用:JavaScript 中的函数可以通过 new 关键字作为构造函数调用,此时会创建一个新的对象实例
  • 绑定规则:通过 new 操作符调用函数时,this 的绑定规则称为 new 绑定

new 操作符的执行过程:

  • 创建对象:首先创建一个全新的空对象
  • this 绑定:将这个新对象的 this 绑定到函数调用的 this 上
  • 执行代码:执行函数体中的代码
  • 默认返回:如果函数没有显式返回非空对象,则默认返回这个新创建的对象

示例分析:

  • 输出特性
    • 浏览器控制台会显示创建的对象,包括后续添加的属性
    • 这是浏览器为了方便调试而做的特殊处理,实际执行顺序仍是先打印后添加属性

完整执行流程:

  • 详细步骤
    • 创建一个全新的对象
    • 这个新对象会被执行 prototype 连接
    • 这个新对象会绑定到函数调用的 this 上
    • 如果函数没有返回其他对象,表达式会返回这个新对象

2. JavaScript 中的 this 绑定规则

1)new 绑定规则

new 绑定规则说明:

  • 创建对象:使用 new 操作符时,首先会创建一个新的空对象
  • this 指向:然后将 this 指向这个新创建的空对象
  • 执行代码:接着执行函数体中的代码
  • 默认返回:如果函数没有显式返回非空对象,则默认返回这个新创建的对象

new 绑定示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 3.1、基本构造函数
function Person(name) {
this.name = name;
console.log("构造函数中的this: ", this);
}
const p1 = new Person("张三"); // 使用 new 调用构造函数时,this 指向新创建的实例对象

// 3.2、构造函数返回对象
function Student(name) {
this.name = name;
return {
name: "李四",
sayHello: function () {
console.log("你好,我是", this.name);
}
}
}
const s1 = new Student("王五");
s1.sayHello(); // 如果构造函数返回一个对象,则 new 表达式会返回这个对象,而不是新创建的实例。

// 3.3、构造函数返回基本类型
function Teacher(name) {
this.name = name;
return "普通字符串"; // 返回基本类型会被忽略
}
const t1 = new Teacher("赵六");
console.log(t1.name); // 如果构造函数返回基本类型(如字符串、数字等),则返回会被忽略,仍然返回新创建的实例。

// 3.4、构造函数中的方法
function Car(brand) {
this.brand = brand;
this.run = function () {
console.log(this.brand, "正在行驶");
}
}
const car = new Car("宝马");
car.run(); // 构造函数中定义的方法,其 this 指向实例对象

// 3.5、构造函数中的箭头函数
function Animal(name) {
this.name = name;
this.sayName = () => {
console.log("我是", this.name);
}
}
const animal = new Animal("小狗");
const sayNameFn = animal.sayName;
sayNameFn(); // 箭头函数中的 this 继承自外层作用域,在这个例子中是构造函数的作用域,所以指向实例对象。

重要性说明:

  • 重要性:this 绑定规则是 JavaScript 中非常重要的概念,很多开发者如果没有系统学习过,在实际开发中会使用混乱
  • 常见问题:很多开发者在不理解 this 绑定规则的情况下,会随意使用 this,导致代码出现预期之外的行为

2)绑定规则总结

三种规则:

  1. 默认绑定
  2. 隐式绑定
  3. new 绑定