深入理解this機制系列第一篇——this的4種綁定規(guī)則
前面的話
如果要問javascript中哪兩個知識點容易混淆,作用域查詢和this機制絕對名列前茅。前面的作用域系列已經(jīng)詳細(xì)介紹過作用域的知識。本系列開始將介紹javascript的另一大山脈——this機制。本文是該系列的第一篇——this的4種綁定規(guī)則
默認(rèn)綁定
全局環(huán)境中,this默認(rèn)綁定到window
console.log(this === window);//true
函數(shù)獨立調(diào)用時,this默認(rèn)綁定到window
function foo(){ console.log(this === window); } foo(); //true
被嵌套的函數(shù)獨立調(diào)用時,this默認(rèn)綁定到window
//雖然test()函數(shù)被嵌套在obj.foo()函數(shù)中,但test()函數(shù)是獨立調(diào)用,而不是方法調(diào)用。所以this默認(rèn)綁定到window var a = 0; var obj = { a : 2, foo:function(){ function test(){ console.log(this.a); } test(); } } obj.foo();//0
【IIFE】
IIFE立即執(zhí)行函數(shù)實際上是函數(shù)聲明后直接調(diào)用執(zhí)行
var a = 0; function foo(){ (function test(){ console.log(this.a); })() }; var obj = { a : 2, foo:foo } obj.foo();//0
//等價于上例
var a = 0;
var obj = {
a : 2,
foo:function(){
function test(){
console.log(this.a);
}
test();
}
}
obj.foo();//0
【閉包】
類似地,test()函數(shù)是獨立調(diào)用,而不是方法調(diào)用,所以this默認(rèn)綁定到window
[注意]函數(shù)共有4種調(diào)用方式,函數(shù)調(diào)用相關(guān)內(nèi)容移步至此
var a = 0; function foo(){ function test(){ console.log(this.a); } return test; }; var obj = { a : 2, foo:foo } obj.foo()();//0
由于閉包的this默認(rèn)綁定到window對象,但又常常需要訪問嵌套函數(shù)的this,所以常常在嵌套函數(shù)中使用var that = this,然后在閉包中使用that替代this,使用作用域查找的方法來找到嵌套函數(shù)的this值
var a = 0; function foo(){ var that = this; function test(){ console.log(that.a); } return test; }; var obj = { a : 2, foo:foo } obj.foo()();//2
隱式綁定
一般地,被直接對象所包含的函數(shù)調(diào)用時,也稱為方法調(diào)用,this隱式綁定到該直接對象
function foo(){ console.log(this.a); }; var obj1 = { a:1, foo:foo, obj2:{ a:2, foo:foo } } //foo()函數(shù)的直接對象是obj1,this隱式綁定到obj1 obj1.foo();//1 //foo()函數(shù)的直接對象是obj2,this隱式綁定到obj2 obj1.obj2.foo();//2
隱式丟失
隱式丟失是指被隱式綁定的函數(shù)丟失綁定對象,從而默認(rèn)綁定到window。這種情況容易出錯卻又常見
【函數(shù)別名】
var a = 0; function foo(){ console.log(this.a); }; var obj = { a : 2, foo:foo } //把obj.foo賦予別名bar,造成了隱式丟失,因為只是把foo()函數(shù)賦給了bar,而bar與obj對象則毫無關(guān)系 var bar = obj.foo; bar();//0
//等價于 var a = 0; var bar = function foo(){ console.log(this.a); } bar();//0
【參數(shù)傳遞】
var a = 0; function foo(){ console.log(this.a); }; function bar(fn){ fn(); } var obj = { a : 2, foo:foo } //把obj.foo當(dāng)作參數(shù)傳遞給bar函數(shù)時,有隱式的函數(shù)賦值fn=obj.foo。與上例類似,只是把foo函數(shù)賦給了fn,而fn與obj對象則毫無關(guān)系 bar(obj.foo);//0
//等價于 var a = 0; function bar(fn){ fn(); } bar(function foo(){ console.log(this.a); });
【內(nèi)置函數(shù)】
內(nèi)置函數(shù)與上例類似,也會造成隱式丟失
var a = 0; function foo(){ console.log(this.a); }; var obj = { a : 2, foo:foo } setTimeout(obj.foo,100);//0
//等價于 var a = 0; setTimeout(function foo(){ console.log(this.a); },100);//0
【間接引用】
函數(shù)的"間接引用"一般都在無意間創(chuàng)建,最容易在賦值時發(fā)生,會造成隱式丟失
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 //將o.foo函數(shù)賦值給p.foo函數(shù),然后立即執(zhí)行。相當(dāng)于僅僅是foo()函數(shù)的立即執(zhí)行 (p.foo = o.foo)(); // 2
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 //將o.foo函數(shù)賦值給p.foo函數(shù),之后p.foo函數(shù)再執(zhí)行,是屬于p對象的foo函數(shù)的執(zhí)行 p.foo = o.foo; p.foo();//4
【其他情況】
在javascript引擎內(nèi)部,obj和obj.foo儲存在兩個內(nèi)存地址,簡稱為M1和M2。只有obj.foo()這樣調(diào)用時,是從M1調(diào)用M2,因此this指向obj。但是,下面三種情況,都是直接取出M2進(jìn)行運算,然后就在全局環(huán)境執(zhí)行運算結(jié)果(還是M2),因此this指向全局環(huán)境
var a = 0; var obj = { a : 2, foo:foo }; function foo() { console.log( this.a ); }; (obj.foo = obj.foo)();//0 (false || obj.foo)();//0 (1, obj.foo)();//0
顯式綁定
通過call()、apply()、bind()方法把對象綁定到this上,叫做顯式綁定。對于被調(diào)用的函數(shù)來說,叫做間接調(diào)用
var a = 0; function foo(){ console.log(this.a); } var obj = { a:2 }; foo();//0 foo.call(obj);//2
普通的顯式綁定無法解決隱式丟失問題
var a = 0; function foo(){ console.log(this.a); } var obj1 = { a:1 }; var obj2 = { a:2 }; foo.call(obj1);//1 foo.call(obj2);//2
【硬綁定】
硬綁定是顯式綁定的一個變種,使this不能再被修改
var a = 0; function foo(){ console.log(this.a); } var obj = { a:2 }; var bar= function(){ foo.call(obj); } //在bar函數(shù)內(nèi)部手動調(diào)用foo.call(obj)。因此,無論之后如何調(diào)用函數(shù)bar,它總會手動在obj上調(diào)用foo bar();//2 setTimeout(bar,100);//2 bar.call(window);//2
【API】
javascript中新增了許多內(nèi)置函數(shù),具有顯式綁定的功能,如數(shù)組的5個迭代方法:map()、forEach()、filter()、some()、every()
var id = 'window'; function foo(el){ console.log(el,this.id); } var obj = { id: 'fn' }; [1,2,3].forEach(foo);//1 "window" 2 "window" 3 "window" [1,2,3].forEach(foo,obj);//1 "fn" 2 "fn" 3 "fn"
new綁定
如果函數(shù)或者方法調(diào)用之前帶有關(guān)鍵字new,它就構(gòu)成構(gòu)造函數(shù)調(diào)用。對于this綁定來說,稱為new綁定
【1】構(gòu)造函數(shù)通常不使用return關(guān)鍵字,它們通常初始化新對象,當(dāng)構(gòu)造函數(shù)的函數(shù)體執(zhí)行完畢時,它會顯式返回。在這種情況下,構(gòu)造函數(shù)調(diào)用表達(dá)式的計算結(jié)果就是這個新對象的值
function fn(){ this.a = 2; } var test = new fn(); console.log(test);//{a:2}
【2】如果構(gòu)造函數(shù)使用return語句但沒有指定返回值,或者返回一個原始值,那么這時將忽略返回值,同時使用這個新對象作為調(diào)用結(jié)果
function fn(){ this.a = 2; return; } var test = new fn(); console.log(test);//{a:2}
【3】如果構(gòu)造函數(shù)顯式地使用return語句返回一個對象,那么調(diào)用表達(dá)式的值就是這個對象
var obj = {a:1}; function fn(){ this.a = 2; return obj; } var test = new fn(); console.log(test);//{a:1}
[注意]盡管有時候構(gòu)造函數(shù)看起來像一個方法調(diào)用,它依然會使用這個新對象作為this。也就是說,在表達(dá)式new o.m()中,this并不是o
var o = { m: function(){ return this; } } var obj = new o.m(); console.log(obj,obj === o);//{} false console.log(obj.constructor === o.m);//true
嚴(yán)格模式
【1】嚴(yán)格模式下,獨立調(diào)用的函數(shù)的this指向undefined
function fn(){ 'use strict'; console.log(this);//undefined } fn(); function fn(){ console.log(this);//window } fn();
【2】在非嚴(yán)格模式下,使用函數(shù)的call()或apply()方法時,null或undefined值會被轉(zhuǎn)換為全局對象。而在嚴(yán)格模式下,函數(shù)的this值始終是指定的值
var color = 'red'; function displayColor(){ console.log(this.color); } displayColor.call(null);//red var color = 'red'; function displayColor(){ 'use strict'; console.log(this.color); } displayColor.call(null);//TypeError: Cannot read property 'color' of null
最后
this的四種綁定規(guī)則:默認(rèn)綁定、隱式綁定、顯式綁定和new綁定,分別對應(yīng)函數(shù)的四種調(diào)用方式:獨立調(diào)用、方法調(diào)用、間接調(diào)用和構(gòu)造函數(shù)調(diào)用。
分清這四種綁定規(guī)則不算難,比較麻煩的是需要練就火眼金睛,識別出隱式丟失的情況
說到底,javascript如此復(fù)雜的原因是因為函數(shù)過于強大。因為,函數(shù)是對象,所以原型鏈比較復(fù)雜;因為函數(shù)可以作為值被傳遞,所以執(zhí)行環(huán)境棧比較復(fù)雜;同樣地,因為函數(shù)具有多種調(diào)用方式,所以this的綁定規(guī)則也比較復(fù)雜
只有理解了函數(shù),才算理解了javascript
以上
好的代碼像粥一樣,都是用時間熬出來的

浙公網(wǎng)安備 33010602011771號