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是詞法綁定的(即,基於原始上下文)


分享到:


相關文章: