標籤:fine fun 其他 out ror 情況 間接 屬性 ict
this是什嗎?
this 是在運行時進行綁定的,並不是在編寫時綁定,它的上下文取決於函數調用時的各種條件。this 的綁定和函式宣告的位置沒有任何關係,只取決於函數的調用方式。當一個函數被調用時,會建立一個活動記錄(有時候也稱為執行內容)。這個記錄會包含函數在哪裡被調用(調用棧)、函數的調用方法、傳入的參數等資訊。this 就是記錄的其中一個屬性,會在函數執行的過程中用到。
調用位置與調用棧:
調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)。
下面我們來看看到底什麼是調用棧和調用位置:
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 的調用位置
this綁定規則1. 預設綁定:
function foo() { console.log( this.a );}var a = 2;foo(); // 2
上述代碼中,函數調用時應用了this 的預設綁定,因此this 指向全域對象(非strict 模式下)。
那麼我們怎麼知道這裡應用了預設綁定呢?可以通過分析調用位置來看看foo() 是如何調用的。在代碼中,foo() 是直接使用不帶任何修飾的函數引用進行調用的,因此只能使用預設綁定,無法應用其他規則。
如果使用strict 模式(strict mode),那麼全域對象將無法使用預設綁定,因此this 會綁定到undefined:
function foo() { "use strict"; console.log( this.a );}var a = 2;foo(); // TypeError: this is undefined
這裡有一個微妙但是非常重要的細節,雖然this 的綁定規則完全取決於調用位置,但是只有foo() 運行在非strict mode 下時,預設綁定才能綁定到全域對象;strict 模式下與foo()的調用位置無關:
function foo() { console.log( this.a );}var a = 2;(function(){ "use strict"; foo(); // 2})();
2. 隱式綁定:
function foo() { console.log( this.a );}var obj = { a: 2, foo: foo};obj.foo(); // 2
當foo() 被調用時,它的落腳點指向obj 對象。當函數引用有內容物件時,隱式綁定規則會把函數調用中的this 綁定到這個內容物件。因為調用foo() 時this 被綁定到obj,因此this.a 和obj.a 是一樣的。
對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置
隱式丟失
一個最常見的this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用預設綁定,從而把this 綁定到全域對象或者undefined 上,取決於是否是strict 模式。
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 函數本身,因此此時的bar() 其實是一個不帶任何修飾的函數調用,因此應用了預設綁定。
3. 顯式綁定:
就像我們剛才看到的那樣,在分析隱式綁定時,我們必須在一個對象內部包含一個指向函數的屬性,並通過這個屬性間接引用函數,從而把this 間接(隱式)綁定到這個對象上。那麼如果我們不想在對象內部包含函數引用,而想在某個對象上強制調用函數,該怎麼做呢?
JavaScript 中的“所有”函數都有一些有用的特性(這和它們的[[ 原型]] 有關——之後我們會詳細介紹原型),可以用來解決這個問題。具體點說,可以使用函數的call(..) 和apply(..) 方法。嚴格來說,JavaScript 的宿主環境有時會提供一些非常特殊的函數,它們並沒有這兩個方法。但是這樣的函數非常罕見,JavaScript 提供的絕大多數函數以及你自己建立的所有函數都可以使用call(..) 和apply(..) 方法。
function foo() { console.log( this.a );}var obj = { a:2};foo.call( obj ); // 2
通過foo.call(..),我們可以在調用foo 時強制把它的this 綁定到obj 上。
顯式綁定仍然無法解決我們之前提出的丟失綁定問題,通過以下方法可以解決:
硬綁定
function foo() { console.log( this.a ); } var obj = { a:2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // 硬綁定的bar 不可能再修改它的this bar.call( window ); // 2
4. new綁定:
使用new 來調用函數,或者說發生建構函式調用時,會自動執行下面的操作。
建立(或者說構造)一個全新的對象。
- 這個新對象會被執行[[ 原型]] 串連。
這個新對象會綁定到函數調用的this。
如果函數沒有返回其他對象,那麼new 運算式中的函數調用會自動返回這個新對象。
function foo(a) { this.a = a;}var bar = new foo(2);console.log( bar.a ); // 2
優先順序: new綁定>顯式綁定>隱式綁定判斷this:
函數是否在new 中調用(new 綁定)?如果是的話this 綁定的是新建立的對象。
var bar = new foo()
函數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this 綁定的是指定的對象。
var bar = foo.call(obj2)
函數是否在某個內容物件中調用(隱式綁定)?如果是的話,this 綁定的是那個內容物件。
var bar = obj1.foo()
如果都不是的話,使用預設綁定。如果在strict 模式下,就綁定到undefined,否則綁定到全域對象。
var bar = foo()
綁定例外1. 被忽略的this
把null 或者undefined 作為this 的綁定對象傳入call、apply 或者bind,這些值在調用時會被忽略,實際應用的是預設綁定規則。
function foo() { console.log( this.a );}var a = 2;foo.call( null ); // 2
更安全的ths
一種“更安全”的做法是傳入一個特殊的對象,把this 綁定到這個對象不會對你的程式產生任何副作用。。就像網路(以及軍隊)一樣,我們可以建立一個“DMZ”(demilitarized zone,非軍事區)對象——它就是一個空的非委託的對象。
function foo(a,b) { console.log( "a:" + a + ", b:" + b );}// 我們的DMZ Null 物件var ? = Object.create( null ); //此處的?不會建立Object.prototype 這個委託,所以它比{}“更空”// 把數組展開成參數foo.apply( ?, [2, 3] ); // a:2, b:3// 使用bind(..) 進行柯裡化var bar = foo.bind( ?, 2 );bar( 3 ); // a:2, b:3
2. 間接引用
另一個需要注意的是,你有可能(有意或者無意地)建立一個函數的“間接引用”,在這種情況下,調用這個函數會應用預設綁定規則。
function foo() { console.log( this.a );}var a = 2;var o = { a: 3, foo: foo };var p = { a: 4 };o.foo(); // 3(p.foo = o.foo)(); // 2
3. 軟綁定(此處不討論)this詞法(箭頭函數的this)
function foo() { // 返回一個箭頭函數 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() 內部建立的箭頭函數會捕獲調用時foo() 的this。由於foo() 的this 綁定到obj1,bar(引用箭頭函數)的this 也會綁定到obj1,箭頭函數的綁定無法被修改。(new 也不行!)
JavaScript中的this—你不知道的JavaScript上卷讀書筆記(三)