多是最好的 this 解析了... ...

全文約 2000 字,讀完大約須要 5 分鐘git

今天咱們就來啃下這個你可能懼怕但又不得不去吃的瓜,this !github

找對象

首先, this 在大多數狀況下是一個對象,也有多是 undefined 或其餘值數組

什麼狀況下,thisundefined ?函數運行在嚴格模式下,應用默認綁定規則的時候:markdown

var a = 1;

function foo() {
 "use strict";
  console.log(this.a);
};

foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
複製代碼

原理其實很簡單,由於規範定義了嚴格模式下,不能將全局對象 Window 用於默認綁定。而大多數狀況下,咱們說的 this,其實就是一個對象,因此肯定 this 的指向,本質上就是要找到這個對象app

因此接下來我就來教你們如何 「找對象」 🤣 。函數

綁定規則

找對象最重要的是什麼?是否是得先經過各類途徑(社交,搭訕,相親...)去認識對象,途徑越多,咱們找到對象的概率就越大,對吧,這裏也是同樣,因此咱們須要儘量的瞭解 this 的綁定規則。oop

ECMAScript 5規範 定義的 this 的綁定規則,有 4 種。ui

默認綁定

教科書會告訴咱們,幾乎全部的規則都會有一個默認的狀況,this

this 綁定也不例外,默認綁定的規則爲:es5

非嚴格模式下,this 指向全局對象,嚴格模式下,this 會綁定到 undefined

var a = 1;

function foo() {
  console.log(this.a);
};

function bar() {
 "use strict";
  console.log(this.a);
};

foo(); // 1,非嚴格模式下,this 指向全局對象 Window,這裏至關於 Window.a

bar(); // Uncaught TypeError: Cannot read property 'a' of undefined,嚴格模式下,this 會綁定到 undefined,嘗試從 undefined 讀取屬性會報錯
複製代碼

隱式綁定

若是函數在調用位置有上下文對象,this 就會隱式地綁定到這個對象上

提及來有點晦澀,直接看例子:

var a  = 1;

function foo() {
  console.log(this.a);
};

var obj = {
  a: 2,
  foo: foo, // <-- foo 的調用位置
};

obj.foo(); // 2,foo 在調用位置有上下文對象 obj,this 會隱式地綁定到 obj,this.a 至關於 obj.a
複製代碼

這個規則可能會讓你想起關於 this 常常聽到的一句話,this 依賴於調用函數前的對象

須要注意的是,隱式綁定在某些狀況下可能會致使綁定丟失,具體來講有兩種狀況,

第一種是使用函數別名調用時:

var a = 1;

function foo() {
  console.log(this.a);
};

var obj = {
  a: 2,
  foo: foo,
};

var bar = obj.foo;

bar(); // 1,賦值並不會改變引用自己,使用函數別名調用時,bar 雖然是 obj.foo 的一個引用,可是實際上引用的仍是 foo 函數自己,因此這裏隱式綁定並無生效, this 應用的是默認綁定
複製代碼

第二種是函數做爲參數傳遞時:

function foo() {
  console.log(this.a);
};

function bar(fn) {
  fn(); // <-- 調用位置
};

var a = 1;

var obj = {
  a: 2,
  foo: foo,
};

bar(obj.foo); // 1, 參數傳遞也是一種隱式賦值,即便傳入的是函數,這裏至關於 fn = obj.foo,因此 fn 實際上引用的仍是 foo 函數自己,this 應用默認綁定
複製代碼

顯式綁定

咱們知道 callapplybind 等方法能夠改變 this 的指向,經過傳入參數就能夠指定 this 的綁定值,夠不夠顯式 ?這種明目張膽的綁定 this 的規則就叫顯式綁定。

callapply 的區別只是接受的參數格式不一樣,call 接受一個參數列表,apply 接受一個參數數組,但二者的第一個參數都是相同的,都是 綁定的 this 值

function foo() {
  console.log(this.a);
};

var a = 1;

var obj = { a: 2 };

foo.call(obj); // 2,調用時顯式地將 foo 的 this 綁定爲 obj 對象,因此這裏的 this.a 至關於 obj.a

foo.apply(obj); // 2,同理
複製代碼

前文咱們提到隱式綁定可能會致使綁定丟失,顯式綁定也不例外,

思考一下,如何才能解決綁定丟失的問題?

答案其實很簡單,只須要在調用函數的內部使用顯式綁定,強制地將 this 綁定到對象:

function foo() {
  console.log(this.a);
};

var obj = {
  a: 2,
  foo: foo,
};

function bar(fn) {
  fn.call(obj);
};

var a = 1;

bar(obj.foo); // 2,
複製代碼

這其實就是 bind 的實現原理,與 callapply 不一樣,bind 調用後不會執行,而是會返回一個硬綁定的函數,因此經過 bind 能夠解決綁定丟失的問題。bind 也是顯式綁定,咱們來回顧下 bind 的用法:

function foo() {
  console.log(this.a);
};

var obj = { a: 2 };

var a = 1;

var bar = foo.bind(obj);

bar(); // 2,bar 是經過 bind 返回後的一個硬綁定函數,其內部應用了顯式綁定
複製代碼

此外,還須要注意的是,將 nullundefined 做爲第一個參數傳入 callapplybind調用時會被忽略,實際應用的是默認綁定規則,即嚴格模式下,thisundefined,非嚴格模式下爲全局對象。

new綁定

先來回顧下 new 的實現原理,

function _new() {
  let obj = new Object(); // 1. 建立一個空對象
  let Con = [].shift.call(arguments); // 2. 得到構造函數
  obj.__proto__ = Con.prototype; // 3. 連接到原型
  let result = Con.apply(obj, arguments); // 4. 綁定 this,執行構造函數
  return typeof result === 'object' ? result : obj; // 5. 返回 new 出來的對象
}
複製代碼

在使用 new 來調用函數時,會建立一個連接到函數原型的對象,並把它綁定到函數調用的 this,因此應用了 new 綁定規則後,不會被任何方式修改 this 指向:

function foo(a) {
  this.a = a;
};

var bar = new foo(2);

bar.a; // 2,new 會返回一個對象,這個對象綁定到構造函數的 this
複製代碼

【特殊】箭頭函數中的this

ES6 中新增了一種函數類型,箭頭函數,箭頭函數中 this 不會應用上述規則,而是根據最外層的詞法做用域來肯定 this,簡單來講,箭頭函數的 this 就是它外面第一個不是箭頭函數的函數的 this

function foo() {
  return () => {
    return () => {
      console.log(this.a);
    };
  };
};

foo()(); // undefined,箭頭函數調用時,this 取決於最外層的第一個不是箭頭函數的函數,這裏就是 foo 函數,非嚴格模式下,默認綁定全局對象 Window,this.a 至關於 Window.a,輸出 undefined
複製代碼

優先級

this 綁定的優先級爲:new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定

判斷模式

根據綁定規則和優先級,咱們能夠總結出 this 判斷的一些通用模式,

  1. 函數是否經過 new 調用?
  2. 是否經過 callapplybind 調用?
  3. 函數的調用位置是否在某個上下文對象中?
  4. 是不是箭頭函數?
  5. 函數調用是在嚴格模式仍是非嚴格模式下?

總結

  • this 的綁定規則有四種:默認綁定,隱式綁定,顯式綁定,new綁定
  • 沒法應用其餘 3 種規則時就是默認綁定,嚴格模式下 this 爲 undefined,非嚴格模式下爲全局對象
  • 函數在調用位置有上下文對象時,this 會隱式綁定到這個對象
  • 能夠經過 call,apply,bind 顯式地改變 this 的指向
  • 經過 new 調用時,this 會綁定到調用函數,new 綁定是優先級最高的綁定
  • 箭頭函數中的 this 繼承至它外層第一個不是箭頭函數的函數

寫在最後

本文首發於個人 博客,才疏學淺,不免有錯誤,文章有誤之處還望不吝指正!

若是有疑問或者發現錯誤,能夠在相應的 issues 進行提問或勘誤

若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵

(完)

相關文章
相關標籤/搜索