整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

深度解析JavaScript的this关键字

深度解析JavaScript的this关键字

者|Austin Tackaberry

译者|无明

出处丨前端之巅

这篇文章通过简单的术语和一个真实的例子解释了 this 是什么以及为什么说它很有用。

你的 this

我发现,很多教程在解释 JavaScript 的 this 时,通常会假设你拥有 Java、C++ 或 Python 等面向对象编程语言的背景。这篇文章主要面向那些对 this 没有先入之见的人。我将尝试解释什么是 this 以及为什么它很有用。

或许你迟迟不肯深入探究 this,因为它看起来很奇怪,让你心生畏惧。你之所以使用它,有可能仅仅是因为 StackOverflow 说你需要在 React 用它来完成一些事情。

在我们深入了解它的真正含义以及为什么要使用它之前,我们首先需要了解函数式编程和面向对象编程之间的区别。

函数式编程与面向对象编程

你可能知道也可能不知道,JavaScript 具有函数和面向对象的构造,你可以选择关注其中一个或两者兼而有之。

在我的 JavaScript 之旅的早期,我一方面拥抱函数式编程,一方面像避免瘟疫一样排斥面向对象编程。我对面向对象关键字 this 不甚了解。其中的一个原因是我不明白它存在的必要性。在我看来,完全可以不依赖 this 就可以完成所有的事情。

在某种程度上,我的看法是对的。

你可能只关注其中一种范式而从来不去了解另外一种,作为一名 JavaScript 开发者,你的局限性就体现在这里。为了说明函数式编程和面向对象编程之间的差别,我将使用一组 Facebook 好友数据作为示例。

假设你正在构建一个用户登录 Facebook 的 Web 应用,在登录后显示一些 Facebook 好友的数据。你需要访问 Facebook 端点来获取好友的数据,可能包含一些信息,例如 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts。

const data=[
 {
 firstName: 'Bob',
 lastName: 'Ross',
 username: 'bob.ross', 
 numFriends: 125,
 birthday: '2/23/1985',
 lastTenPosts: ['What a nice day', 'I love Kanye West', ...],
 },
 ...
]

你从(臆造的)Facebook API 获得上面的数据。现在,你需要转换它们,让它们符合项目需要的格式。假设你要为每个用户的朋友显示以下内容:

  • 它们的名字,格式为$ {firstName} $ {lastName};
  • 三篇随机的帖子;
  • 从他们生日起到现在的天数。

函数式方法

如果使用函数式方法,就是将整个数组或数组的每个元素传给一个返回所需操作数据的函数:

const fullNames=getFullNames(data)
// ['Ross, Bob', 'Smith, Joanna', ...]

你从原始数据开始(来自 Facebook API),为了将它们转换为对你有用的数据,你将数据传给一个函数,这个函数将输出你可以在应用程序中显示给用户的数据。

你也可以通过类似的方式获取三个随机帖子并计算朋友生日至今的天数。

函数式方法就是指接受原始数据,将数据传给一个或多个函数,并输出对你有用的数据。

面向对象方法

对于那些刚接触编程和学习 JavaScript 的人来说,面向对象方法可能会更难掌握。面向对象是指你将每个朋友转换为对象,对象包含了用于生成你所需内容的一切。

你可以创建包含 fullName 属性的对象,以及 getThreeRandomPosts 和 getDaysUntilBirthday 函数。

function initializeFriend(data) {
 return {
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from data.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use data.birthday to get the num days until birthday
 }
 };
}
const objectFriends=data.map(initializeFriend)
objectFriends[0].getThreeRandomPosts() 
// Gets three of Bob Ross's posts

面向对象方法是为你的数据创建对象,这些对象包含了状态和用于生成对你和你的项目有用的数据的信息。

这与 this 有什么关系?

你可能没有想过会写出类似 initializeFriend 这样的东西,你可能会认为它很有用。你可能还会注意到,它其实并非真正的面向对象。

getThreeRandomPosts 或 getDaysUntilBirthday 方法之所以有用,主要是因为闭包。因为使用了闭包,所以在 initializeFriend 返回之后,它们仍然可以访问 data。

假设你写了另一个方法,叫 greeting。请注意,在 JavaScript 中,方法只是对象的一个属性,这个属性的值是一个函数。我们希望 greeting 可以做这些事情:

function initializeFriend(data) {
 return {
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from data.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use data.birthday to get the num days until birthday
 },
 greeting: function() {
 return `Hello, this is ${fullName}'s data!`
 }
 };
}

这样可以吗?

不行!

新创建对象的所有东西都可以访问 initializeFriend 的变量,但对象本身的属性或方法不行。当然,你可能会问:

难道你不能用 data.firstName 和 data.lastName 来返回 greeting 吗?

当然可以。但如果我们还想在 greeting 中包含朋友生日至今的天数,该怎么办?我们必须以某种方式从 greeting 中调用 getDaysUntilBirthday 方法。

是时候让 this 上场了!

那么,this 是什么

在不同的情况下,this 代表的东西也不一样。默认情况下,this 指向全局对象(在浏览器中,就是 window 对象)。但光知道这点对我们并没有太大帮助,对我来说有用的是 this 的这条规则:

如果 this 被用在一个对象的方法中,并且这个方法在对象的上下文中调用,那么 this 就指向这个对象本身。

你会问:“在对象的上下文中调用……这又是什么意思”?

别担心,稍后我们会解释这个。

因此,如果我们想在 greeting 中调用 getDaysUntilBirthday,可以直接调用 this.getDaysUntilBirthday,因为在这种情况下,this 指向对象本身。

注意:不要在全局作用域或在另一个函数作用域内的常规 ole 函数中使用 this!this 是一个面向对象的构造。因此,它只在对象(或类)的上下文中有意义!

让我们重构 initializeFriend,让它使用 this:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const numDays=this.getDaysUntilBirthday() 
 return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
 }
 };
}

现在,在执行完 intializeFriend 后,这个对象的所有东西都限定在对象本身。我们的方法不再依赖于闭包,它们将使用对象本身包含的信息。

这是 this 的一种使用方式,现在回到之前的问题:为什么说 this 因上下文不同而不同?

有时候,你希望 this 可以指向不一样的东西,比如事件处理程序就是一个很好的例子。假设我们想在用户点击链接时打开朋友的 Facebook 页面。我们可能会在对象中添加一个 onClick 方法:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const numDays=this.getDaysUntilBirthday() 
 return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

请注意,我们向对象添加了 username,让 onFriendClick 可以访问它,这样我们就可以在新窗口中打开朋友的 Facebook 页面。现在编写 HTML:

<button id="Bob_Ross">
 <!-- A bunch of info associated with Bob Ross -->
</button> 

然后是 JavaScript:

const bobRossObj=initializeFriend(data[0])
const bobRossDOMEl=document.getElementById('Bob_Ross')
bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)

在上面的代码中,我们为 Bob Ross 创建了一个对象。我们获得与 Bob Ross 相关的 DOM 元素。现在我们想要调用 onFriendClick 方法来打开 Bob 的 Facebook 页面。应该没问题吧?

不行!

什么地方出了问题?

请注意,我们为 onclick 处理程序选择的函数是 bobRossObj.onFriendClick。看到问题所在了吗?如果我们像这样重写它:

bobRossDOMEl.addEventListener("onclick", function() {
 window.open(`https://facebook.com/${this.username}`)
})

现在你看到问题所在了吗?当我们将 onclick 处理程序指定为 bobRossObj.onFriendClick 时,我们实际上是将 bobRossObj.onFriendClick 的函数作为参数传给了处理程序。它不再“属于”bobRossObj,也就是说 this 不再指向 bobRossObj。这个时候 this 实际上指向的是全局对象,所以 this.username 是 undefined 的。

是时候让 bind 上场了!

显式绑定 this

我们需要做的是将 this 显式绑定到 bobRossObj。我们可以使用 bind 来实现:

const bobRossObj=initializeFriend(data[0])
const bobRossDOMEl=document.getElementById('Bob_Ross')
bobRossObj.onFriendClick=bobRossObj.onFriendClick.bind(bobRossObj)
bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)

之前,this 是基于默认规则设置的。通过使用 bind,我们在 bobRossObj.onFriendClick 中将 this 的值显式设置为对象本身,也就是 bobRossObj。

到目前为止,我们已经知道为什么 this 很有用以及为什么有时候需要显式绑定 this。接下来我们要讨论的最后一个主题是箭头函数。

箭头函数

你可能已经注意到,箭头函数像是一个时髦的新事物。人们似乎很喜欢它们,因为它们简洁而优雅。你可能已经知道它们与一般函数略有不同,但不一定非常清楚这些区别究竟是什么。

或许箭头函数的不同之处在于:

在箭头函数内部,无论 this 处于什么位置,它指的都是相同的东西。

让我们用 initializeFriend 示例解释一下。假设我们想在 greeting 中添加一个辅助函数:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 function getLastPost() {
 return this.lastTenPosts[0]
 }
 const lastPost=getLastPost() 
 return `Hello, this is ${this.fullName}'s data!
 ${this.fullName}'s last post was ${lastPost}.`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

这样可以吗?如果不行,要怎样修改才行?

这样当然是不行的。因为 getLastPost 不是在对象的上下文中调用的,所以 getLastPost 中的 this 会回退到默认规则,即指向全局对象。

“在对象的上下文中调用”可能是一个比较含糊的概念。要确定一个函数是否是在“对象的上下文中”被调用,最好的办法是看一下函数是如何被调用的,以及是否有对象“附加”在函数上。

让我们来看看执行 bobRossObj.onFriendClick() 时会发生什么:“找到 bobRossObj 对象的 onFriendClick 属性,调用分配给这个属性的函数”。

再让我们来看看执行 getLastPost() 时会发生什么:”调用一个叫作 getLastPost 的函数”。有没有注意到,这里并没有提及任何对象?

现在来测试一下。假设有一个叫作 functionCaller 的函数,它所做的事情就是调用其他函数:

functionCaller(fn) {
 fn()
}

如果我们这样做会怎样:functionCaller(bobRossObj.onFriendClick)?可不可以说 onFriendClick 是“在对象的上下文中”被调用的?this.username 的定义存在吗?

让我们来看一下:“找到 bobRossObj 对象的 onFriendClick 属性。找到这个属性的值(恰好是一个函数),将它传给 functionCaller,并命名为 fn。现在,执行名为 fn 的函数”。请注意,函数在被调用之前已经从 bobRossObj 对象中“分离”,因此不是“在对象 bobRossObj 的上下文中”调用,所以 this.username 是 undefined 的。

让箭头函数来救场:

function initializeFriend(data) {
 return {
 lastTenPosts: data.lastTenPosts,
 birthday: data.birthday,
 username: data.username, 
 fullName: `${data.firstName} ${data.lastName}`,
 getThreeRandomPosts: function() {
 // get three random posts from this.lastTenPosts
 },
 getDaysUntilBirthday: function() {
 // use this.birthday to get the num days until birthday
 },
 greeting: function() {
 const getLastPost=()=> {
 return this.lastTenPosts[0]
 }
 const lastPost=getLastPost() 
 return `Hello, this is ${this.fullName}'s data!
 ${this.fullName}'s last post was ${lastPost}.`
 },
 onFriendClick: function() {
 window.open(`https://facebook.com/${this.username}`)
 }
 };
}

箭头函数是在 greeting 中声明的。我们知道,当我们在 greeting 中使用 this 时,它指向对象本身。因此,箭头函数中的 this 指向的对象就是我们想要的。

英文原文:

https://medium.freecodecamp.org/a-deep-dive-into-this-in-javascript-why-its-critical-to-writing-good-code-7dca7eb489e7

箭头类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u21E0

E0

?

\u21E2

E2

?

\u21E1

E1

?

\u21E3

E3

?

\u219E

9E

?

\u21A0

A0

?

\u219F

9F

?

\u21A1

A1

\u2190

90

\u2192

92

\u2191

91

\u2193

93

?

\u2194

94

?

\u2195

95

?

\u21C4

C4

?

\u21C5

C5

?

\u21A2

A2

?

\u21A3

A3

?

\u21DE

DE

?

\u21DF

DF

?

\u21AB

AB

?

\u21AC

AC

?

\u21DC

DC

?

\u21DD

DD

?

\u219A

9A

?

\u219B

9B

?

\u21AE

AE

?

\u21AD

AD

?

\u21E6

E6

?

\u21E8

E8

?

\u21E7

E7

?

\u21E9

E9

\u25B2

B2

?

\u25BA

BA

\u25BC

BC

?

\u25C4

C4

?

\u2794

94

?

\u2799

99

?

\u27A8

A8

?

\u27B2

B2

?

\u279C

9C

?

\u279E

9E

?

\u279F

9F

?

\u27A0

A0

?

\u27A4

A4

?

\u27A5

A5

?

\u27A6

A6

?

\u27A7

A7

?

\u27B5

B5

?

\u27B8

B8

?

\u27BC

BC

?

\u27BD

BD

?

\u27BA

BA

?

\u27B3

B3

?

\u21B7

B7

?

\u21B6

B6

?

\u21BB

BB

?

\u21BA

BA

?

\u21B5

B5

?

\u21AF

AF

?

\u27BE

BE





? 基本形状类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u2764

64

?

\u2708

08

\u2605

05

?

\u2726

26

?

\u2600

00

\u25C6

C6

?

\u25C8

C8

?

\u25A3

A3

?

\u263B

3B

?

\u263A

3A

?

\u2639

39

?

\u2709

09

?

\u260E

0E

?

\u260F

0F

?

\u2706

06

?

\uFFFD

\FFFD

?

\u2601

01

?

\u2602

02

?

\u2744

44

?

\u2603

03

?

\u2748

48

?

\u273F

3F

?

\u2740

40

?

\u2741

41

?

\u2618

18

?

\u2766

66

?

\u9749

49

?

\u2742

42

?

\u2625

25

?

\u262E

2E

?

\u262F

2F

?

\u262A

2A

?

\u2624

24

?

\u2704

04

?

\u2702

02

?

\u2638

38

?

\u2693

93

?

\u2623

23

?

\u26A0

A0

?

\u26A1

A1

?

\u2622

22

?

\u267B

7B

?

\u267F

7F

?

\u2620

20

¥ 货币类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



$

$

\u0024

>\0024<24

¢

\u00A2

>\00A2<>

£

\u00A3

>\00A3<>

¤

¤

\u00A4

>\00A4<>

\u20AC

AC

¥

\u00A5

>\00A5<>

?

\u20B1

B1

?

\u20B9

B9

? 数学类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

½

\u00BD

>\00BD<>

?

¼

\u00BC

>\00BC<>

?

¾

\u00BE

>\00BE<>

?

\u2153

53

?

\u2154

54

?

\u215B

5B

?

\u215C

5C

?

\u215D

5D

\u2030

30

%

%

\u0025

>\0025<25

<

<

\u003C

>\003C<3C

>

>

\u003E

>\003E<3E

? 音乐符号类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u2669

69

?

\u266A

6A

?

\u266B

6B

?

\u266C

6C

?

\u266D

6D

?

\u266F

6F

? 对错号

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS




 

\u00A0

>\00A0<>

?

\u2610

10

?

\u2611

11

?

\u2612

12

?

\u2713

13

?

\u2714

14

?

\u10005

005

?

\u2716

16

?

\u2717

17

?

\u2718

18

★ 全都是星星

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



\u2605

05

?

\u272D

2D

?

\u272E

2E

\u2606

06

?

\u272A

2A

?

\u2721

21

?

\u272F

2F

?

\u2735

35

?

\u2736

36

?

\u2738

38

?

\u2739

39

?

\u273A

3A

?

\u2731

31

?

\u2732

32

?

\u2734

34

?

\u2733

33

?

\u273B

3B

?

\u273D

3D

?

\u274B

4B

?

\u2746

46

?

\u2744

44

?

\u2745

45

? 星座类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u2648

48

?

\u2649

49

?

\u264A

4A

?

\u264B

4B

?

\u264C

4C

?

\u264D

4D

?

\u264E

4E

?

\u264F

4F

?

\u2650

50

?

\u2651

51

?

\u2652

52

?

\u2653

53

? 国际象棋类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u265A

5A

?

\u265B

5B

?

\u265C

5C

?

\u265D

5D

?

\u265E

5E

?

\u265F

5F

?

\u2654

54

?

\u2655

55

?

\u2656

56

?

\u2657

57

?

\u2658

58

?

\u2659

59

? 扑克牌类

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u2660

60

?

\u2663

63

?

\u2665

65

?

\u2666

66

?

\u2664

64

?

\u2667

67

?

\u2661

61

?

\u2662

62

Ω 希腊字母

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



Α

Α

\u0391

91

Β

Β

\u0392

92

Γ

Γ

\u0393

93

Δ

Δ

\u0394

94

Ε

Ε

\u0395

95

Ζ

Ζ

\u0396

96

Η

Η

\u0397

97

Θ

Θ

\u0398

98

Ι

Ι

\u0399

99

Κ

Κ

\u039A

9A

Λ

Λ

\u039B

9B

Μ

Μ

\u039C

9C

Ν

Ν

\u039D

9D

Ξ

Ξ

\u039E

9E

Ο

Ο

\u039F

9F

Π

Π

\u03A0

A0

Ρ

Ρ

\u03A1

A1

Σ

Σ

\u03A3

A3

Τ

Τ

\u03A4

A4

Υ

Υ

\u03A5

A5

Φ

Φ

\u03A6

A6

Χ

Χ

\u03A7

A7

Ψ

Ψ

\u03A8

A8

Ω

Ω

\u03A9

A9

? 十字

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

\u2628

28

?

\u2629

29

?

\u271D

1D

?

\u271E

1E

?

\u271F

1F

?

\u2720

20

?

\u271A

1A

?

\u2020

20

?

\u2722

22

?

\u2724

24

?

\u2723

23

?

\u2725

25

? 法律符号

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

®

\u00AE

>\00AE<>

?

©

\u00A9

>\00A9<>

?

\u2117

17

?

\u0099

>\0099<99

?

\u2120

20





@ 标点和符号

符号

UNICODE



符号

UNICODE



HTML

JS

CSS

HTML

JS

CSS



?

«

\u00AB

>\00AB<>

?

»

\u00BB

>\00BB<>

?

\u008B

>\008B<8B

?

\u009B

>\009B<9B

\u201C

1C

\u201D

1D

\u2018

18

\u2019

19

?

\u2022

22

?

\u25E6

E6

?

¡

\u00A1

>\00A1<>

?

¿

\u00BF

>\00BF<>

\u2105

05

\u2116

16

&

&

\u0026

>\0026<26

@

@

\u0040

>\0040<40

?

\u211E

1E

\u2103

03

\u2109

09

°

°

\u00B0

>\00B0<>



|

\u007C

>\007C<7C

|

¦

\u00A6

\u2013

13

\u2014

14

\u2026

26

?

\u00B6

>\00B6<>

\u223C

3C

\u2260


用法

三种用法都在里面了

、html()方法:如果想更改或者是设置 HTML 的内容,我们可以使用 html()方法,首先我们先使用这个方法获取元素里面的内容 var html=$("p").html()。如果需要设置某元素的 HTML 代码,那么我们就可以使用此方法加上一个参数。此方法只能应用于 XHTML 中,不能用于 xml。

2、text()方法,去设置某个元素中的文本内容,代码是 var text=$("p").text();如果想设置文本同样需要给它传一个参数。

3、val()方法,可以用来设置和获取元素的值,它不仅仅可以设置元素,同时也能获取元素,另外,它能是下拉列表框,多选框,和单选框相应的选项被选中,在表单操作中会经常用到。