JS中this的綁定規則

前言

咱們明白每一個函數的 this 是在調用 時被綁定的,徹底取決於函數的調用位置(也就是函數的調用方法)。 在理解 this 的綁定過程以前,首先要理解調用位置:調用位置就是函數在代碼中被調用的 位置(而不是聲明的位置)。 最重要的是要分析調用棧(就是爲了到達當前執行位置所調用的全部函數)。咱們關心的 調用位置就在當前正在執行的函數的前一個調用中。javascript

調用位置

下面咱們來看看到底什麼是調用棧和調用位置:java

function baz() {
// 當前調用棧是:baz
// 所以,當前調用位置是全局做用域
console.log( "baz" );
bar(); // <-- bar 的調用位置
}
function bar() {
// 當前調用棧是 baz -> bar
// 所以,當前調用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的調用位置
}
function foo() {
// 當前調用棧是 baz -> bar -> foo
// 所以,當前調用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的調用位置
複製代碼

綁定規則 (注:皆不考慮嚴格模式)

1. 默認綁定

首先要介紹的是最經常使用的函數調用類型:獨立函數調用。能夠把這條規則看做是沒法應用 其餘規則時的默認規則。app

思考一下下面的代碼:函數

function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
複製代碼

咱們能夠看到當調用 foo() 時,this.a 被解析成了全局變量 a。爲何?由於在本 例中,函數調用時應用了 this 的默認綁定,所以 this 指向全局對象oop

2. 隱式綁定

另外一條須要考慮的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包 含,不過這種說法可能會形成一些誤導。ui

思考下面的代碼:this

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
複製代碼

當函數引 用(注:foo是引用函數,既指向function foo(){···}這個函數,而foo()纔是調用函數)有上下文對象時,隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象。由於調 用 foo() 時 this 被綁定到 obj,所以 this.a 和 obj.a 是同樣的。spa

對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。舉例來講:code

function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
複製代碼

隱式丟失 (若是到這有點暈能夠先跳過這個)

這裏咱們還要談到隱式丟失這個問題。一個最多見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把 this 綁定到全局對象。對象

思考下面的代碼:

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數別名!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"
複製代碼

雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己(注:var bar = obj.foo;至關於給這個函數又取了一個名字,因此bar();=foo();),所以此時的 bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。

3. 顯式綁定

JavaScript 提供的絕大多數函數以及你自 己建立的全部函數均可以使用 call(..) 和 apply(..) 方法。 這兩個方法是如何工做的呢?它們的第一個參數是一個對象,它們會把這個對象綁定到 this,接着在調用函數時指定這個 this。由於你能夠直接指定 this 的綁定對象,所以我 們稱之爲顯式綁定。

思考下面的代碼:

function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
複製代碼

經過 foo.call(..),咱們能夠在調用 foo 時強制把它的 this 綁定到 obj 上。 (注:從 this 綁定的角度來講,call(..) 和 apply(..)還有bind(...) 是同樣的,它們的區別體現 在其餘的參數上,可是如今咱們不用考慮這些。)

4. new綁定

在 JavaScript 中,構造函數只是一些 使用 new 操做符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上, 它們甚至都不能說是一種特殊的函數類型,它們只是被 new 操做符調用的普通函數而已

使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。(這裏咱們能夠不用管第二步)

  1. 建立(或者說構造)一個全新的對象。
  2. 這個新對象會被執行 [[ 原型 ]] 鏈接。
  3. 這個新對象會綁定到函數調用的 this。
  4. 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。

思考下面的代碼:

function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
複製代碼

使用 new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this 上。new 是最後一種能夠影響函數調用時 this 綁定行爲的方法,咱們稱之爲 new 綁定。

最後:優先級

如今咱們已經瞭解了函數調用中 this 綁定的四條規則,你須要作的就是找到函數的調用位 置並判斷應當應用哪條規則。可是,若是某個調用位置能夠應用多條規則該怎麼辦?爲了 解決這個問題就必須給這些規則設定優先級。

這裏我就不作討論了,直接給出結論:

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

有興趣的同窗能夠自行去驗證。

參考書籍:《你不知道的JavaScript(上卷)》

相關文章
相關標籤/搜索