JS 中你不知道的 this

this 誤區(qū)

this 既不指向函數(shù)自身也不指向函數(shù)的詞法作用域。this 實際上是在函數(shù)被調(diào)用時發(fā)生的綁定,它指向什么完全取決于函數(shù)在哪里被調(diào)用。

調(diào)用位置

在理解 this 的綁定過程之前,首先要理解調(diào)用位置:調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)。

function baz() {
     // 當(dāng)前調(diào)用棧是:baz
     // 因此,當(dāng)前調(diào)用位置是全局作用域
     console.log( "baz" );
     bar(); // <-- bar 的調(diào)用位置 
}
function bar() {
    // 當(dāng)前調(diào)用棧是 baz -> bar
    // 因此,當(dāng)前調(diào)用位置在 baz 中
     console.log( "bar" );
     foo(); // <-- foo 的調(diào)用位置 
}
function foo() {
    // 當(dāng)前調(diào)用棧是 baz -> bar -> foo 
    // 因此,當(dāng)前調(diào)用位置在 bar 中
     console.log( "foo" );
}
baz(); // <-- baz 的調(diào)用位置
四種綁定規(guī)則
  1. 默認(rèn)綁定

獨立函數(shù)內(nèi)調(diào)用??梢园堰@條規(guī)則看作是無法應(yīng)用其他規(guī)則時的默認(rèn)規(guī)則。

非嚴(yán)格模式

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

聲明在全局作用域中的變量就是全局對象的一個同名屬性。調(diào)用 foo() 時,this.a 被解析成了全局變量 a。

嚴(yán)格模式

"use strict";
function foo() { 
    console.log( this.b ); 
}
var b = 2;
foo(); // TypeError: this is undefined

如果使用嚴(yán)格模式(strict mode),那么全局對象將無法使用默認(rèn)綁定,因此 this 會綁定到 undefined。

總結(jié):this 的默認(rèn)綁定規(guī)則在非嚴(yán)格模式下綁定到全局對象,嚴(yán)格模式下綁定到 undefined。

  1. 隱式綁定

需要考慮的規(guī)則是調(diào)用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。

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

foo 函數(shù)被當(dāng)作引用屬性添加到 obj 中,因此你可以說函數(shù)被調(diào)用時 obj 對象擁有或者包含它。當(dāng)函數(shù)引用有上下文對象時,隱式綁定規(guī)則會把函數(shù)調(diào)用中的 this 綁定到這個上下文對象。因為調(diào)用 foo() 時 this 被綁定到 obj,因此 this.a 和 obj.a 是一樣的。

對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置。

function foo() { 
    console.log( this.a );
}
var obj2 = { 
    a: 666,
    foo: foo 
};
var obj1 = { 
    a: 999,
    obj2: obj2 
};
obj1.obj2.foo(); // 666

隱式丟失情況

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

雖然 bar 是 obj.foo 的一個引用,但是實際上,它引用的是 foo 函數(shù)本身,因此此時的 bar() 其實是一個不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定。

function foo() { 
    console.log( this.a );
}
function doFoo(fn) {
    // fn 其實引用的是 foo 
    fn(); // <-- 調(diào)用位置!
}
var obj = { 
    a: 2,
    foo: foo 
};
var a = "oops, global"; // a 是全局對象的屬性 
doFoo( obj.foo ); // "oops, global"

參數(shù)傳遞其實就是一種隱式賦值,因此我們傳入函數(shù)時也會被隱式賦值。

  1. 顯式綁定

JavaScript 提供的絕大多數(shù)函數(shù)以及你自己創(chuàng)建的所有函數(shù)都可以使用 call(..) ,apply(..) 和 bind(..) 方法,用來直接指定 this 的綁定對象。

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

通過 foo.call(..),我們可以在調(diào)用 foo 時強制把它的 this 綁定到 obj 上。

  1. new綁定

使用 new 來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時,會自動執(zhí)行下面的操作。

  • 創(chuàng)建(或者說構(gòu)造)一個全新的對象。
  • 這個新對象會被執(zhí)行[[原型]]連接。
  • 這個新對象會綁定到函數(shù)調(diào)用的this。
  • 如果函數(shù)沒有返回其他對象,那么 new 表達式中的函數(shù)調(diào)用會自動返回這個新對象。
function foo(a) { 
    this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 來調(diào)用 foo(..) 時,我們會構(gòu)造一個新對象并把它綁定到 foo(..) 調(diào)用中的 this 上。new 是最后一種可以影響函數(shù)調(diào)用時 this 綁定行為的方法,我們稱之為 new 綁定。

優(yōu)先級
function foo() { 
    console.log( this.a );
}
var obj1 = { 
    a: 2,
    foo: foo 
};
var obj2 = { 
    a: 3,
    foo: foo 
};
obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2

顯式綁定優(yōu)先級高于隱式綁定。

function foo(something) { 
    this.a = something;
}
var obj1 = { 
    foo: foo
};
var obj2 = {};
     
obj1.foo( 2 );
console.log( obj1.a ); // 2
     
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 ); 
console.log( obj1.a ); // 2
console.log( bar.a ); // 4

new 綁定比隱式綁定優(yōu)先級高。

由于 new 和 call/apply 無法一起使用,因此我們不用關(guān)心他們優(yōu)先級關(guān)系。

best practice

如果要判斷一個運行中函數(shù)的 this 綁定,就需要找到這個函數(shù)的直接調(diào)用位置。找到之后
就可以順序應(yīng)用下面這四條規(guī)則來判斷 this 的綁定對象。

  • 由 new 調(diào)用則綁定到新創(chuàng)建的對象。
  • 由 cal l或者 apply 或者 bind 調(diào)用則綁定到指定的對象。
  • 由上下文對象調(diào)用(隱式綁定)則綁定到那個上下文對象。
  • 默認(rèn)綁定:在嚴(yán)格模式下綁定到 undefined,否則綁定到全局對象。

優(yōu)先級關(guān)系:
顯示綁定 > 隱式綁定 > 默認(rèn)綁定
new綁定 > 隱式綁定 > 默認(rèn)綁定

箭頭函數(shù)

ES6 中的箭頭函數(shù)并不會使用四條標(biāo)準(zhǔn)的綁定規(guī)則,而是根據(jù)當(dāng)前的詞法作用域來決定 this,具體來說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)。

function foo() {
    // 返回一個箭頭函數(shù) 
    return (a) => {
        //this 繼承自 foo()
        console.log( this.a ); 
    };
}
var obj1 = { 
    a:2
};
var obj2 = { 
    a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會捕獲調(diào)用時 foo() 的 this。由于 foo() 的 this 綁定到 obj1, bar(引用箭頭函數(shù))的 this 也會綁定到 obj1,箭頭函數(shù)的綁定無法被修改。

全局上下文

在全局執(zhí)行上下文中(在任何函數(shù)之外),this 指的是全局對象,無論它是否處于嚴(yán)格模式。

console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"
參考鏈接

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

你不知道的JavaScript(上卷)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容