10分钟了解this指向
2024-07-06 05:05:44

面试官:既然您熟悉JS,那您能简单说说JS中的this指向问题吗?

我:好的,正常浏览器环境中,this是指向window对象,node环境中指向global对象,对象中,谁调用它,this就指向了谁,箭头函数中,指向外层对象,大概是这样。

面试官:那继续详细说说?

我:这还能怎么详细?已经够详细了。

面试官:嗯,似乎也确实,一般确实没必要考的更深了。

正文

JavaScript 中的 “this” 是一个令人迷惑但又十分重要的概念。

它的行为可能会随着代码的不同而变化,因此深入理解 “this” 对于成为一名优秀的 JavaScript 开发者至关重要。

本文将带您深入探讨 “this” 的工作原理以及如何在实际应用中正确使用它。

基础概念

在 JavaScript 中,”this” 表示当前执行代码的对象。

但是,它的具体指向取决于代码的上下文。

在全局作用域下,”this” 指向全局对象(在浏览器中通常是 “window” 对象)。在函数内部,”this” 的值取决于函数的调用方式。

为什么要有this

为了让对象中的函数有能力访问对象自己的属性,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
const obj = {
name: "John",
greet: function() {
console.log("Hello, " + name);
}
};

obj.greet(); // 输出 name is not defined

const obj = {
name: "John",
greet: function() {
console.log("Hello, " + obj.name);
}
};

obj.greet(); // 输出 Hello,John

const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};

obj.greet(); // 输出 Hello,John
  • 第一二段代码对比,为了让对象中的函数有能力访问对象自己的属性。
  • 第二三段代码对比,this可以显著的提升代码质量,减少上下文参数的传递。

全局上下文下的 “this”

在全局作用域下,”this” 指向全局对象。

这意味着当在全局作用域下调用 “this” 时,它将指向 “window” 对象(或 在node环境中,指向”global” 对象,取决于执行环境)。

1
console.log(this === window); // 输出 true

用了严格模式 “use strict”,严格模式下无法再意外创建全局变量,所以 this 不为 window 而为 undefined。

注意:babel 转成 ES6 的,babel 会自动给 js 文件上加上严格模式。

1
2
3
4
5
"use strict"
function f1 () {
console.log(this)
}
f1() // undefined

函数上下文中的 “this”

在函数内部,”this” 的值取决于函数的调用方式。

如果函数是作为对象的方法调用的,则 “this” 将指向调用该方法的对象。

这就是那句经典的回答:“谁调用了它,它就指向谁”。

1
2
3
4
5
6
7
8
const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};

obj.greet(); // 输出 "Hello, John"

如果函数中的 this 是被上一级的对象所调用的,那么 this 指向的就是上一级的对象,如下示例。

1
2
3
4
5
6
7
8
9
10
11
const animal = {
name: "coboy",
age: 18,
dog: {
name: 'cobyte',
getName: function() {
console.log(this.name)
}
}
};
animal.dog.getName() // 'cobyte'

构造函数中的 “this”

在构造函数中,”this” 用于引用将要创建的对象实例。

当使用 “new” 关键字调用构造函数时,”this” 将指向新创建的对象。

1
2
3
4
5
6
function Person(name) {
this.name = name;
}

const person1 = new Person("Alice");
console.log(person1.name); // 输出 "Alice"

箭头函数中的 “this”

与常规函数不同,箭头函数中的 “this” 指向定义时的外部上下文,而不是调用时的上下文。

这使得箭头函数在访问外部作用域的 “this” 值时非常方便。

严格模式对箭头函数没有效果。

1
2
3
4
5
6
7
8
9
10
11
12
"use strict"
const obj = {
name: "Jane",
greet: function() {
const func = () => {
console.log("Hello, " + this.name);
};
func();
}
};

obj.greet(); // 输出 "Hello, Jane"

说明:箭头函数没有this这个机制,写在箭头函数中的this也是它外层非箭头函数的this

this 的绑定规则

在 JavaScript 中,”this” 的值是在函数执行时确定的,根据调用函数的方式不同,”this” 可以绑定到不同的值上。

下面是几种常见的绑定规则:

1. 默认绑定

当一个函数独立调用时,并且没有任何修饰符,”this” 将默认绑定到全局对象(在浏览器中通常是 “window” 对象)上。

1
2
3
4
5
6
7
function greet() {
console.log("Hello, " + this.name);
}

// 默认绑定,this 指向全局对象
var name = "John";
greet(); // 输出 "Hello, John"

在全局,通过var声明的变量相当于是在window对象上添加了一个属性,即 window.name ,因此window.name == this.name

2. 隐式绑定

当函数被作为对象的方法调用时,”this” 将隐式绑定到调用该方法的对象上。

1
2
3
4
5
6
7
8
9
const obj = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};

// 隐式绑定,this 指向调用它的对象
obj.greet(); // 输出 "Hello, Alice"

3. 隐式丢失

隐式丢失:当一个函数被多个对象链式调用时,函数的this指向就近的那个对象(就近原则)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj1 = {
name: "Bob",
greet: sayHello // 引用
};

const obj2 = {
name: "Emily",
obj1: obj1
};

function sayHello() {
console.log("Hello, " + this.name);
}

obj2.obj1.greet(); // 输出 "Hello, Bob"

这里,根据就近原则,输出Bob

4. 显示绑定

通过调用函数的 call、apply 或 bind 方法,可以显示地指定函数内部的 “this” 值。

- call 方法

call 方法允许您显式调用函数,并且可以指定函数内部的 this 值。

它接受一个参数列表,在调用函数时,将传递给函数作为参数使用。

1
2
3
4
5
6
7
8
9
10
function greet(greeting) {
console.log(greeting + ", " + this.name);
}

const obj = {
name: "Alice"
};

// 使用 call 方法调用函数,并指定 this 指向 obj,同时传递参数
greet.call(obj, "Hello"); // 输出 "Hello, Alice"

在上面的示例中,我们使用 call 方法将函数 greetthis 绑定到对象 obj 上,并且传递了一个额外的参数 "Hello"

- apply 方法

apply 方法与 call 方法类似,但是它接受一个参数数组而不是参数列表。

这意味着您可以将参数作为数组传递给函数。

1
2
3
4
5
6
7
8
9
10
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}

const obj = {
name: "Bob"
};

// 使用 apply 方法调用函数,并指定 this 指向 obj,同时传递参数数组
greet.apply(obj, ["Hi", "!"]); // 输出 "Hi, Bob!"

在这个示例中,我们使用 apply 方法将函数 greetthis 绑定到对象 obj 上,并传递了一个包含两个参数的数组 ["Hi", "!"]

- bind 方法

bind 方法创建一个新的函数,并将指定的 this 值绑定到函数内部。

callapply 不同,bind 并不会立即调用函数,而是返回一个新的函数,您可以稍后调用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(greeting) {
console.log(greeting + ", " + this.name);
}

const obj = {
name: "Charlie"
};

// 使用 bind 方法创建一个新的函数,指定 this 指向 obj
const boundGreet = greet.bind(obj);

// 调用新的函数
boundGreet("Hey"); // 输出 "Hey, Charlie"

在上述示例中,我们使用 bind 方法创建了一个新的函数 boundGreet,并将其 this 绑定到对象 obj 上。

然后,我们可以随时调用 boundGreet 函数,它将使用预先绑定的 this 值。

上述三个方法也可不传参数使用。

5. new 绑定

当一个函数被使用 “new” 关键字调用时,它会创建一个新的对象,并将该对象绑定到函数内部的 “this” 上。

1
2
3
4
5
6
function Person(name) {
this.name = name;
}

const person = new Person("David");
console.log(person.name); // 输出 "David"

结语

“this” 是 JavaScript 中一个重要且经常被误解的概念。

通过深入理解 “this” 的工作原理,并根据不同的上下文正确使用它,您可以写出更加清晰、健壮的 JavaScript 代码。

希望本文能够帮助您更好地理解 “this”,并在实际项目中更加灵活地运用它。

参考

搞懂this指向和规则,只需这一篇

JavaScript 中 call()、apply()、bind() 的用法 | 菜鸟教程 (runoob.com)

JavaScript 中的 this 实战例题总结分析