JavaScript丨 为初学者介绍 this 关键字

JavaScript丨 为初学者介绍 this 关键字

理解 JavaScript 中的关键字 this,以及它指向什么,有时候可能是有点复杂。幸好有五个通用规则可以用来确定 this 绑定到什么。更多互联网资讯请去公众号锁定"全阶魔方"每天都有更新哦虽然这些规则在某些情况下并不适用,不过绝大多数情况应该能帮你搞定。所以,下面我们开始吧!

1. this 的值通常是由一个函数的执行上下文决定的。所谓执行上下文就是指一个函数如何被调用。

2. 必须要知道的是,每次函数被调用时,this 都有可能是不同的(即指向不同的东西)。

3. 如果依然搞不清楚第一条和第二条的含义,没有关系,本文结尾的时候会搞清楚的。

#1 全局对象

好吧,我们现在把理论定义放一边,下面先从实践来寻找支撑。打开你的 Chrome 开发者控制台(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),并键入如下代码:

1. console.log(this);

会得到什么呢?

1. // Window {...}

window 对象!这是因为在全局作用域中,this 指向全局对象。而在浏览器中,全局对象就是 window 对象。

为帮助更好地理解为什么 this 指向 window 对象,下面我们更深入来了解一下。在控制台中,创建一个新变量 myName,把它赋值为你的姓名:

1. var myName = 'Brandon';

我们可以通过调用 myName,再次访问这个新变量:

1. myName

2. // returns -> 'Brandon'

不过,你知道在全局作用域中声明的每个变量都是绑定到 window 对象吗?好吧,我们来试一试:

1. window.myName

2. // returns -> 'Brandon'

3.

4. window.myName === myName

5. // returns -> true

酷。所以,之前在全局上下文中执行 console.log(this) 时,我们知道 this 是在全局对象上被调用。因为浏览器中的全局对象是 window对象,所以这样的结果是讲得通的:

1. console.log(this)

2. // returns -> window{...}

现在我们把 this 放在一个函数内。回忆我们之前的定义:this 的值通常是由函数如何被调用来决定的。记住这一点后,你期望这个函数返回什么?在浏览器控制台中,复制如下代码,按回车。

1. function test() {

2. return this;

3. }

4. test()

关键字 this 再次返回全局对象(window)。这是因为 this 不在一个已声明的对象内,所以它默认等于全局对象(window)。这个概念可能现在理解起来有点难,不过随着进一步阅读,应该会越来越清晰。要指出的一件事是,如果你使用严格模式,那么上例中的 this 将会是 undefined。

当关键字 this 被用在一个已声明的对象内时,this 的值被设置为被调用的方法所在的最近的父对象。看看如下代码,这里我声明了一个对象 person,并在方法 full 内使用 this:

1. var person = {

2. first: 'John',

3. last: 'Smith',

4. full: function() {

5. console.log(this.first + ' ' + this.last);

6. }

7. };

8.

9. person.full();

10. // logs => 'John Smith'

为更好阐述 this 实际上就是引用 person 对象,请将如下代码复制到浏览器控制台中。它与上面的代码大致相同,只是用 console.log(this) 替换了一下,这样我们就能看看会返回什么。

1. var person = {

2. first: 'John',

3. last: 'Smith',

4. full: function() {

5. console.log(this);

6. }

7. };

8.

9. person.full();

10. // logs => Object {first: "John", last: "Smith", full: function}

如你所见,控制台会返回 person 对象,证明 this 采用的是 person 的值。

在继续之前,还有最后一件事。还记得我们说过 this 的值被设置为被调用的方法所在的

最近的父对象吧?如果有嵌套的对象,你会期望发生什么?看看下面的示例代码。我们已经有了一个 person 对象,该对象像以前一样有同样的 first、last 和 full 键。不过这一次,我们还嵌套进了一个 personTwo 对象。personTwo 包含了同样的三个键。

1. var person = {

2. first: 'John',

3. last: 'Smith',

4. full: function() {

5. console.log(this.first + ' ' + this.last);

6. },

7. personTwo: {

8. first: 'Allison',

9. last: 'Jones',

10. full: function() {

11. console.log(this.first + ' ' + this.last);

12. }

13. }

14. };

当调用这两个 full 方法时,会发生什么呢?下面我们来探个究竟。

1. person.full();

2. // logs => 'John Smith'

3.

4. person.personTwo.full();

5. // logs => 'Allison Jones'

this 的值再次被设置为方法调用所在的最近的父对象。当调用 person.full() 时,函数内的 this 是被绑定到 person 对象。与此同时,当调用 person.personTwo.full() 时,在 full 函数内,this 是被绑定到 personTwo 对象!

#3 New 关键字

当使用 new 关键字(构造器)时,this 被绑定到正在新创建的对象。

我们来看一个示例:

1. function Car(make, model) {

2. this.make = make;

3. this.model = model;

4. };

上面,你可能会猜 this 被绑定到全局对象 - 如果不用关键字 new 的话,你就是正确的。当我们使用 new 时,this 的值被设置为一个空对象,在本例中是 myCar。

1. var myCar = new Car('Ford', 'Escape');

2.

3. console.log(myCar);

4. // logs => Car {make: "Ford", model: "Escape"}

要把上面的代码搞清楚,需要理解 new 关键字到底做了什么。不过这本身是一个全新的话题。所以现在,如果你不确定的话,只要看到关键字 new,就当 this 是正指向一个全新的空对象好了。

#4 Call、Bind 和 Apply

最后一个要点,我们实际上是可以用 call()、bind() 和 apply() 显式设置 this 的值的。这三个方法很相似,不过理解它们之间的细微差别很重要。

call() 和 apply() 都是立即被调用的。call() 可以带任意数量的参数:this,后跟其它的参数。apply() 只带两个参数:this,后跟一个其它参数的数组。

还跟得上我的思路吧?用一个例子应该会解释得更清楚一些。看看下面的代码。我们正试着对数字求和。复制如下代码到浏览器控制台,然后调用该函数。

1. function add(c, d) {

2. console.log(this.a + this.b + c + d);

3. }

4.

5. add(3,4);

6. // logs => NaN

add 函数输出的是 NaN(不是一个数字)。这是因为 this.a 和 this.b 都是 undefined,二者都是不存在的。而我们不能把数字与未定义的东西相加。

下面我们给等式引入一个对象。我们可以用 call() 和 apply() 在我们的对象上调用 add 函数:

1. function add(c, d) {

2. console.log(this.a + this.b + c + d);

3. }

4.

5. var ten = {a: 1, b: 2};

6.

7. add.call(ten, 3, 4);

8. // logs => 10

9.

10. add.apply(ten, [3,4]);

11. // logs => 10

当用 add.call() 时,第一个参数就是 this 要绑定的对象。后续的参数被传递进我们调用的函数。因此,在 add() 中,this.a 指向 ten.a,this.b 指向 ten.b,我们得到返回的 1+2+3+4,或者 10。

add.apply() 差不多。第一个参数就是 this 应该绑定的对象。后续的参数是要用在函数中的参数数组。

那么 bind() 又是怎么样的呢?bind() 中的参数与 call() 中的是一样的,不过 bind() 不是马上被调用,而是返回一个 this 上下文已经绑定好的函数。因此,如果预先不知道所有参数的话,bind() 就很有用。下面再用一个示例来帮助理解:

1. var small = {

2. a: 1,

3. go: function(b,c,d){

4. console.log(this.a+b+c+d);

5. }

6. }

7.

8. var large = {

9. a: 100

10. }

将上述代码复制到控制台。然后调用:

1. small.go(2,3,4);

2. // logs 1+2+3+4 => 10

很棒。这里并没有啥新东西。但是,如果我们想用 large.a 的值来替换,该怎么办呢?我们可以用 call() 或者 apply():

1. small.go.call(large,2,3,4);

2. // logs 100+2+3+4 => 109

现在,如果还不知道所有 3 个参数,该怎么办呢?我们可以用 bind():

1. var bindTest = small.go.bind(large,2);

如果在控制台输出上面的变量 bindTest,就可以看到结果:

1. console.log(bindTest);

2. // logs => function (b,c,d){console.log(this.a+b+c+d);}

记住,用 bind() 的话,会返回一个已经绑定了 this 的函数。所以 this 已经成功绑定到 large 对象。我们还传递进了第二个参数 2。之后,当知道其余参数时,我们可以将它们传递进来:

1. bindTest(3,4);

2. // logs 100+2+3+4 => 109

为清晰起见,我们将所有代码放在一个块中。仔细看一遍,然后将其复制到控制台中,真正了解一下发生了什么!

1. var small = {

2. a: 1,

3. go: function(b,c,d){

4. console.log(this.a+b+c+d);

5. }

6. }

7.

8. var large = {

9. a: 100

10. }

11.

12. small.go(2,3,4);

13. // logs 1+2+3+4 => 10

14.

15. var bindTest = small.go.bind(large,2);

16.

17. console.log(bindTest);

18. // logs => function (b,c,d){console.log(this.a+b+c+d);}

19.

20. bindTest(3,4);

21. // logs 100+2+3+4 => 109

#5 箭头函数

箭头函数本身就是一个很大的主题,为此我写了一整篇文章来讲解它:。

总结

你成功了!现在,在大多数情况下,你应该能推断出 this 指向什么!请记住如下几件事:

1. this 的值通常是由函数的执行上下文决定的

2. 在全局作用域中,this 指向全局对象(window 对象)

3. 当使用 new 关键字(一个构造器)时,this 被绑定到正创建的新对象

4. 可以显式用 call()、bind() 和 apply() 设置 this 的值

5. 箭头函数不会绑定 this - this是词法绑定的(即,基于原始上下文)


分享到:


相關文章: