一拿撩、問題的由來
學(xué)懂 JavaScript 語言衣厘,一個標(biāo)志就是理解下面兩種寫法,可能有不一樣的結(jié)果压恒。
var????obj={foo:function(){
????????};
var????foo????=????obj.foo;
// 寫法一
obj.foo()
// 寫法二foo()
上面代碼中影暴,雖然obj.foo和foo指向同一個函數(shù)怖亭,但是執(zhí)行結(jié)果可能不一樣。請看下面的例子坤检。
var obj={foo:function(){
????console.log(this.bar)},
????bar:1};
var foo=obj.foo;
varbar=2;
obj.foo() // 1
foo() // 2
這種差異的原因兴猩,就在于函數(shù)體內(nèi)部使用了this關(guān)鍵字。很多教科書會告訴你早歇,this指的是函數(shù)運行時所在的環(huán)境。對于obj.foo()來說箭跳,foo運行在obj環(huán)境,所以this指向obj谱姓;對于foo()來說借尿,foo運行在全局環(huán)境,所以this指向全局環(huán)境屉来。所以路翻,兩者的運行結(jié)果不一樣。
這種解釋沒錯茄靠,但是教科書往往不告訴你茂契,為什么會這樣?也就是說慨绳,函數(shù)的運行環(huán)境到底是怎么決定的掉冶?舉例來說脐雪,為什么obj.foo()就是在obj環(huán)境執(zhí)行,而一旦var foo = obj.foo战秋,foo()就變成在全局環(huán)境執(zhí)行?
本文就來解釋 JavaScript 這樣處理的原理获询。理解了這一點涨岁,你就會徹底理解this的作用吉嚣。
二、內(nèi)存的數(shù)據(jù)結(jié)構(gòu)
JavaScript 語言之所以有this的設(shè)計尝哆,跟內(nèi)存里面的數(shù)據(jù)結(jié)構(gòu)有關(guān)系秉撇。
varobj={foo:5};
上面的代碼將一個對象賦值給變量obj琐馆。JavaScript 引擎會先在內(nèi)存里面,生成一個對象{ foo: 5 }瘦麸,然后把這個對象的內(nèi)存地址賦值給變量obj谁撼。
也就是說厉碟,變量obj是一個地址(reference)。后面如果要讀取obj.foo屠缭,引擎先從obj拿到內(nèi)存地址,然后再從該地址讀出原始的對象呵曹,返回它的foo屬性。
原始的對象以字典結(jié)構(gòu)保存奄喂,每一個屬性名都對應(yīng)一個屬性描述對象。舉例來說砍聊,上面例子的foo屬性背稼,實際上是以下面的形式保存的。
{foo:{[[value]]:5[[writable]]:true[[enumerable]]:true[[configurable]]:true}}
注意词疼,foo屬性的值保存在屬性描述對象的value屬性里面。
三贰盗、函數(shù)
這樣的結(jié)構(gòu)是很清晰的许饿,問題在于屬性的值可能是一個函數(shù)舵盈。
varobj={foo:function(){}};
這時,引擎會將函數(shù)單獨保存在內(nèi)存中秽晚,然后再將函數(shù)的地址賦值給foo屬性的value屬性。
{foo:{[[value]]:函數(shù)的地址...}}
由于函數(shù)是一個單獨的值赴蝇,所以它可以在不同的環(huán)境(上下文)執(zhí)行。
varf=function(){};varobj={f:f};// 單獨執(zhí)行f()// obj 環(huán)境執(zhí)行obj.f()
四、環(huán)境變量
JavaScript 允許在函數(shù)體內(nèi)部劲蜻,引用當(dāng)前環(huán)境的其他變量。
varf=function(){console.log(x);};
上面代碼中先嬉,函數(shù)體里面使用了變量x。該變量由運行環(huán)境提供疫蔓。
現(xiàn)在問題就來了,由于函數(shù)可以在不同的運行環(huán)境執(zhí)行鳄袍,所以需要有一種機制绢要,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運行環(huán)境(context)拗小。所以,this就出現(xiàn)了哀九,它的設(shè)計目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運行環(huán)境阅束。
varf=function(){console.log(this.x);}
上面代碼中,函數(shù)體里面的this.x就是指當(dāng)前運行環(huán)境的x息裸。
varf=function(){console.log(this.x);}varx=1;varobj={f:f,x:2,};// 單獨執(zhí)行f() // 1// obj 環(huán)境執(zhí)行obj.f() // 2
上面代碼中蝇更,函數(shù)f在全局環(huán)境執(zhí)行呼盆,this.x指向全局環(huán)境的x。
在obj環(huán)境執(zhí)行访圃,this.x指向obj.x。
回到本文開頭提出的問題腿时,obj.foo()是通過obj找到foo,所以就是在obj環(huán)境執(zhí)行批糟。一旦var foo = obj.foo,變量foo就直接指向函數(shù)本身跃赚,所以foo()就變成在全局環(huán)境執(zhí)行性湿。