深刻理解this綁定原理

前言

前段時間,看了《你不知道的JavaScript》中this的全面解析,講的特別好,還沒看過的小夥伴抓緊去學習,在這邊特地整理總結了一波,一塊兒分享學習。在這以前能夠先理解一下關於this、call、applay和bind這篇文章。git

調用棧

在瞭解this綁定原理以前,首頁要先了解JavaScript的執行棧。(執行棧也叫作調用棧)若是對調用棧還不瞭解,能夠先詳細瞭解深刻淺出JavaScript執行上下文和執行棧github

JavaScript是單線程的,全部這決定了同一時間只能作一件事情,其餘的活動或事情只能排隊等候了,因而就生成出一個等候隊列的執行棧(Execution Stack)。簡單來講,執行棧就是爲了到達當前執行位置所調用的全部函數。而調用位置就在當前正在執行的函數的前一個調用中。app

function baz() {
    // 當前執行棧是:baz
    // 調用位置是baz前一個調用中,所以是全局做用域
    console.log('baz');
    bar();
}

function bar() {
    // 當前執行棧是:bar
    // 調用位置是bar前一個調用中,所以是baz
    onsole.log('bar');
}

baz()  // baz的調用位置

根據上面的規則,則不難判斷出上述代碼的調用棧和調用位置。而調用棧和調用位置則決定了this的綁定對象。其綁定規則有四種,只有找到調用位置,才能判斷須要應用四條規則中的哪一條。函數

默認綁定

在非嚴格模式下,this指向全局對象,嚴格模式下this則會指向undefinedpost

function foo() {
  console.log(this.a); // this指向全局對象
}
var a = 2;
foo(); // 2
function foo2() {
  "use strict"; // 嚴格模式this綁定到undefined
  console.log(this.a); 
}
foo2(); // TypeError:a undefined

可是若是在嚴格模式下調用其餘函數,則不影響默認綁定。學習

function foo() {
  console.log(this.a); // foo函數不是嚴格模式 默認綁定全局對象
}
var a = 2;
function foo2(){
  "use strict";
  foo(); // 嚴格模式下調用其餘函數,不影響默認綁定
}
foo2();

上述爲獨立的函數調用,其調位位置爲全局做用域,全部this綁定在全局做用域上。this

隱式綁定

函數在調用位置,是否有上下文對象,若是有,那麼this就會隱式綁定到這個對象上。
也能夠簡單理解爲是否被某個對象包含了。線程

function foo() {
  console.log(this.a);
}
var a = "Oops, global";
let obj2 = {
  a: 2,
  foo: foo
};
let obj1 = {
  a: 22,
  obj2: obj2
};
obj2.foo(); // 2 this指向調用函數的對象
obj1.obj2.foo(); // 2 this指向最後一層調用函數的對象

// 隱式綁定丟失
let bar = obj2.foo; // bar只是一個函數別名 是obj2.foo的一個引用
bar(); // "Oops, global" - 指向全局

上述代碼中函數foo調用位置在obj2上下文中,全部this綁定在obj2做用域中,因此obj2.foo()obj1.obj2.foo()最終都爲2,而let bar = obj2.foo實際上就是函數的引用賦給變量bar,調用時,並無上下文對象,因此會致使隱式綁定丟失。3d

顯式綁定

經過applycallbind將函數中的this強制綁定到指定對象上。code

function foo() {
    console.log(this.a);
}
let obj = {
    a: 2
};
foo.call(obj); // 2

須要注意的是:

  • 若是傳入了一個原始值(字符串,布爾類型,數字類型),來當作this的綁定對象,這個原始值會轉換成它的對象形式。
  • 若是把null或者undefined做爲this的綁定對象傳入callapplybind,這些值會在調用時被忽略,實際應用的是默認綁定規則。

new綁定

若是對 new關鍵字不太瞭解,能夠先看這篇 關於new命令

使用構造調用的時候,this會自動綁定在new期間建立的對象上。

function foo(a) {
  this.a = a; // this綁定到bar上
}
let bar = new foo(2);
console.log(bar.a); // 2

四種綁定規則的優先級

  • 顯式綁定 > 隱式綁定 > 默認綁定
  • new綁定 > 隱式綁定 > 默認綁定
function foo() {
    this.a = 100;
}
var obj1 ={
    foo: foo;
}
var obj2 = {}
obj1.foo.call(obj2, 2); // 2  this指向obj2 顯式綁定比隱式綁定優先級高。

new obj1.foo(4); // thsi指向new新建立的對象 new綁定比隱式綁定優先級高。

顯式綁定和new綁定沒法直接比較(會報錯),默認綁定是不該用其餘規則以後的兜底綁定因此優先級最低。

箭頭函數this指向

箭頭函數this不會使用這四條綁定規則。

function foo() {
  return (a) => {
    // this繼承自foo
    console.log(this.a);
  };
}
let obj1 = {
  a: 2
};
let obj2 = {
  a: 3
};
let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 輸出2 這裏執行箭頭函數 並試圖綁定this指向到obj2

從上述能夠得出,箭頭函數的this規則:

  • 箭頭函數中的this繼承於它外面第一個不是箭頭函數的函數的this指向。若是沒有則指向全局。
  • 箭頭函數的 this 一旦綁定了上下文,就不會被任何代碼改變。

小結

若是要判斷一個運行中函數this綁定,就須要找到這個函數直接調用位置。找到以後就能夠順序應用下面四條規則來判讀this綁定對象。

  1. new調用,綁定到新建立對象。
  2. call或者applybind調用,綁定到指定對象。
  3. 由上下文對象調用,綁定到那個上下文對象。
  4. 默認:在嚴格模式綁定到undefined,不然綁定到全局

箭頭並不適用與上述四條規則,而是由他的外層函數繼承而來。

更多優質文章能夠訪問GitHub博客,歡迎帥哥美女前來Star!!!

相關文章
相關標籤/搜索