嗨,this

本文原創:liruifangjavascript

你是否被this困擾過? 求職時你是否在筆試或者面試的時候被問起過? 你是否在代碼中寫過self = this?java

this,究竟是何方神聖?git

this是一個很特別的關鍵字,被自動定義在 全部函數的做用域中 this 關鍵字是 JavaScript 中最複雜的機制之一github

科幻小說家亞瑟·查理斯·克拉克說過一句話:任何足夠先進的技術都和魔法無異。面試

在缺少清晰認識的狀況下,this 徹底就是一種魔法segmentfault

歡迎來到this的魔法世界~

先看幾個例子~瀏覽器

1.png

2.png

3.png

4.png

  • 若是你能很快地獲得肯定的答案,那麼恭喜你,你已是this魔法世界的魔法師了~
  • 若是你不肯定或是無從下手,那麼請跟隨我來~

如何準確判斷this指向的是什麼?

正經解釋:bash

  1. this 就是一個指針,指向調用函數的對象
  2. this 是在運行時進行綁定的,並非在編寫時綁定,它的上下文取決於函數調用時的各類條件
  3. 當一個函數被調用時,會建立一個活動記錄(執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的其中一個屬性,會在函數執行的過程當中用到。
  4. 每一個函數的 this 是在調用時被綁定的,徹底取決於函數的調用位置
  5. 在函數執行過程當中調用位置決定 this 的綁定對象。

聽過不少道理 卻依然……app

不!融於意識、付諸實踐的道理,纔是你的道理。函數

透過現象看本質

1、調用位置

2、綁定規則

3、綁定優先級

4、凡事都有例外

1、調用位置

  • 調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)。
  • 最重要的是要分析調用棧(就是爲了到達當前執行位置所調用的全部函數)。調用位置就在當前正在執行的函數的前一個調用中。
    1-1.png

this 的綁定,使用開發者工具獲得調用棧,而後找到棧中第二個元素,這就是真正的調用位置。

1-2.png

  • this 在任何狀況下都不指向函數的詞法做用域。
  • 本例試圖用this 聯通 foo() 和 bar() 的詞法做用域,從而讓 bar() 能夠訪問 foo() 做用域裏的變量 a。這是不可能實現的,你不能使用 this 來引用一 個詞法做用域內部的東西。
  • 剛剛分析的只是調用位置,調用方式會決定this的指向,這須要經過具體的規則來判斷。

2、綁定規則

(一)默認綁定

2-1.png

  • foo() 是直接使用不帶任何修飾的函數引用進行調用的
  • 最經常使用的函數調用類型:獨立函數調用
  • 默認綁定
  • this指向全局對象(非嚴格模式下)
  • this指向undefined(嚴格模式下), undefined上沒有this對象,會拋出錯誤
  • 非嚴格模式下,在瀏覽器環境中運行,結果是 Hello,2019

(二)隱式綁定

2-2.png

  • 函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象
  • 典型的形式爲 XXX.fun()
  • 調用位置會使用 obj 上下文來引用函數
  • 隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象
  • 此例foo函數中的this綁定到obj,所以 this.a 和 obj.a 是同樣的

2-3.png

Bob.callPerson(John);
Bob called a person named John」
callPerson() 是 Bob 發起的,this 就指向 Bob。
複製代碼

2-4.png

- 答案:16
- 對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。 
- 無論有多少層,在判斷this的時候,咱們只關注最後一層。
複製代碼

2-5.png

- 雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己
- bar() 實際上是一個不帶任何修飾的函數調用 
- 重複強調:隱式綁定的形式 obj.fun()
複製代碼

(三)顯式綁定

經過call、apply、bind的方式,顯式地指定this所指向的對象。

2-6.png

  • call,apply和bind的第一個參數,就是對應函數的this所指向的對象。
  • 從 this 綁定的角度來講,call和apply的做用同樣,只是傳參方式不一樣。
  • call和apply都會執行對應的函數,bind不會。
    2-7.png
- 答案:number is  2
- 執行fn的時候,至關於直接調用了foo方法(記住: obj.foo已經被賦值給fn了,隱式綁定也丟了),沒有指定this的值,對應的是默認綁定。
- 這顯然不是咱們想要的,怎麼辦?
複製代碼

調用fn顯式的強制綁定,硬綁定

2-8.png

咱們建立了函數 Hi(),並在它的內部手動調用 了 fn.call(obj),所以強制把 fn 的 this 綁定到了 obj。不管以後如何調用函數 Hi,它總會手動在 obj 上調用 fn。 
複製代碼

(四)new綁定

思考: new 作了什麼?

1. 建立一個新對象
2. 將構造函數的做用域賦值給新對象,即this指向這個新對象
3. 執行構造函數中的代碼
4. 返回新對象
複製代碼

2-9.png

foo的this指向bar對象上
複製代碼

綁定規則

(一)默認綁定 var bar = foo()

嚴格模式下,綁定到undefined,不然綁定到 全局對象。 
複製代碼

(二)隱式綁定 var bar = obj1.foo()

在某個上下文對象中調用,this 綁定的是那個上下文對象。 
複製代碼

(三)顯式綁定 var bar = foo.call(obj2)

this綁定的是 指定的對象。 
複製代碼

(四)new綁定 var bar = new foo()

this綁定的是新建立的對象。
複製代碼

3、優先級

new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定

3-1.png

這樣就結束了嗎?

世界沒你想象的那麼簡單~~

4、凡事都有例外

將null或undefined做爲this的綁定對象傳入call、apply或者是bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。

3-2.png

箭頭函數

  • ES6 中介紹了一種沒法使用 這些規則的特殊函數類型:箭頭函數。
  • 箭頭函數不使用this 的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 this。
  • 箭頭函數按詞法做用域來綁定它的上下文,因此 this 實際上會引用到原來的上下文。箭頭函數從包含它的詞法做用域中繼承到了 this 的值。
  • 箭頭函數沒有本身的this,因此不能用call()、apply()、bind()這些方法去改變this的指向。
  • 不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。

3-3.png

- 答案:2
- foo() 內部建立的箭頭函數會捕獲調用時 foo() 的 this。因爲 foo() 的 this 綁定到 obj1, bar(引用箭頭函數)的 this 也會綁定到 obj1,箭頭函數的綁定沒法被修改。 
複製代碼

總結

(一)默認綁定 var bar = foo()

嚴格模式下,綁定到undefined,不然綁定到 全局對象。 
複製代碼

(二)隱式綁定 var bar = obj1.foo()

在某個上下文對象中調用,this 綁定的是那個上下文對象。 
複製代碼

(三)顯式綁定 var bar = foo.call(obj2)

this綁定的是 指定的對象。 
複製代碼

(四)new綁定 var bar = new foo()

this綁定的是新建立的對象。
複製代碼

(五)箭頭函數

沒有本身的this,當前的詞法做用域覆蓋了 this 原本的值,this繼承於外層代碼庫中的this,且不可被修改。
複製代碼

歡迎再次來到this的魔法世界,讓咱們一塊兒升級打怪吧~

問題剖析,真相只有一個!

4-1.png

- 答案:
    1 2
- 分析:
    隱式綁定,this指向obj,num是1;
    默認綁定:foo直接指向了foo的引用,和obj無關,是不帶任何修飾的函數調用 。
複製代碼

4-2.png

- 答案:hi,jadfe
複製代碼

4-2-1.png

- 分析:
    setTimeout(fn,delay){ fn(); },至關因而將obj.foo賦值給了一個變量,最後執行了變量, foo是不帶任何修飾的函數調用這個時候,foo的this顯然和obj就沒有關係了。
複製代碼

4-3.png

- 答案:
    Hello, Wiliam
    Hello, Wiliam
    Hello, Christina
- 分析:
    ① setTimeout的回調函數中,this使用的是默認綁定,非嚴格模式下,執行的是全局對象
    ② 上個例子剛分析了,跟setTimeout實現機制有關。
    ③ 第三條雖然也是在setTimeout的回調中,可是咱們能夠看出,這是執行的是person2.sayHi()使用的是隱式綁定,所以這是this指向的是person2。
複製代碼

4-4.png

- 答案:
    2 2
- 分析:
    箭頭函數,this繼承於外層的this
    默認綁定:foo是不帶任何修飾的函數調用 
複製代碼

4-5.png

- 答案:
    2
- 分析:
    箭頭函數經常使用於回調函數中,this繼承於外層foo的this,foo中的this顯示綁定到obj上,所以至關於obj.a = a4-5
複製代碼

4-6.png

- 答案:
    Obj對象
    Obj對象
    Window對象
    Window對象
    Window對象
- 分析:
    ① obj.hi(); 隱式綁定,this綁定在obj上,輸出obj。
    ② hi(); 執行箭頭函數,繼承外層做用域的this。
    ③ sayHi(); 隱式綁定丟失的狀況,this執行的是默認綁定,指向全局對象window。
    ④ fun1(); 執行的是箭頭函數,this是繼承於外層代碼庫的this。外層代碼庫咱們剛剛分析了,this指向的是window,所以這兒的輸出結果是window。
    ⑤ obj.say(); 執行的是箭頭函數,當前的代碼塊obj中是不存在this的,只能往上找,就找到了全局的this,指向的是window。
複製代碼

4-7.png

- 答案:
    0 NaN
- 分析:
    看調用位置,顯示綁定,
    無心中建立了一個全局變量 count
    這個count值是什麼?
    有些地方稱值爲NaN是錯誤的,值爲undefined,++運算符,類型轉換NaN
複製代碼

4-8.png

- 答案:
    5
- 分析:
    將null或undefined做爲this的綁定對象傳入call、apply或者是bind,
    這些值在調用時會被忽略,實際應用的是默認綁定規則
複製代碼

如何準確判斷this指向的是什麼?

1. 函數是不是new綁定,若是是,那麼this綁定的是新建立的對象。
2. 函數是否經過call,apply,bind調用顯式綁定,若是是,那麼this綁定的就是指定的對象。
3. 函數是否在某個上下文對象中調用(隱式綁定),通常是obj.foo(),若是是的話,this綁定的是那個上下文對象[注意隱式丟失的狀況]。
4. 若是以上都不是,那麼使用默認綁定。在嚴格模式下,則綁定到undefined,不然綁定到全局對象。
5. 若是把null或者undefined做爲this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
6. 若是是箭頭函數,箭頭函數的this繼承的是外層代碼塊的this。
複製代碼

若是你已經很是清楚的知道怎麼判斷this的指向,那就試試這道題吧~

5-1.png

答案: 
    10 
    9 
    3 
    27 
    20
複製代碼

6.png

參考文獻:

相關文章
相關標籤/搜索