var obj = {
foo: function() {console.log(this.bar)};
bar: 1
}
var foo = obj.foo;
var bar = 2;
obj.foo(); // 1
foo();// 2
上面這段代碼中,obj.foo 和foo指向的是同一個(gè)函數(shù),但是結(jié)果卻是不一樣的。
我們都知道,javascript中,this指的是函數(shù)運(yùn)行時(shí)所在的環(huán)境,obj.foo()運(yùn)行在obj環(huán)境,所以this指向的是obj;foo()運(yùn)行在全局環(huán)境,所以this指向全局環(huán)境。但是為什么就說(shuō)obj.foo()是在obj環(huán)境執(zhí)行,而一旦var foo = obj.foo,foo()就變成了全局環(huán)境執(zhí)行呢?也就是說(shuō)函數(shù)的運(yùn)行環(huán)境到底是由什么決定的。對(duì)于這個(gè)問(wèn)題,我們往往是模棱兩可的,下面就來(lái)解釋下javascript這樣處理的原理,本文主要參考了阮一峰老師的文章JavaScript 的 this 原理。
內(nèi)存的數(shù)據(jù)結(jié)構(gòu)
-
var obj = {foo: 5} 我們把一個(gè)對(duì)象賦值給了一個(gè)變量obj。javascript引擎會(huì)先在內(nèi)存里面,生成一個(gè)對(duì)象{foo: 5},然后把這個(gè)對(duì)象的內(nèi)存地址賦值給obj。這也就是說(shuō)obj實(shí)際上是一個(gè)地址,后面如果要讀取obj.foo,引擎會(huì)先從obj拿到內(nèi)存地址,然后再?gòu)脑摰刂纷x出原始的對(duì)象,返回它的foo屬性。
原始的對(duì)象以字典結(jié)構(gòu)保存,每一個(gè)屬性名都對(duì)象一個(gè)屬性描述對(duì)象,例如上述foo屬性實(shí)際上是以下面形式保存的。
image注意,foo屬性的值保存在屬性描述對(duì)象的value屬性里面
函數(shù)
-
如果屬性的值是一個(gè)函數(shù),例如 var obj = {foo:function(){}},這時(shí),引擎會(huì)將函數(shù)單獨(dú)保存在內(nèi)存中,然后再將函數(shù)的地址賦值給foo屬性的value屬性
image
由于函數(shù)是一個(gè)單獨(dú)的值,所以它可以在不同的環(huán)境(上下文)執(zhí)行
var f = function() {}
var obj = {f: f}
// 單獨(dú)執(zhí)行
f();
// obj 環(huán)境執(zhí)行
obj.f();
環(huán)境變量
javascript允許在函數(shù)體內(nèi)部,引用當(dāng)前環(huán)境的其他變量
var f = function() {
console.log(x);
}
上面代碼中,函數(shù)體里面使用了變量x,該變量是由運(yùn)行環(huán)境提供的。由于函數(shù)可以在不同的運(yùn)行環(huán)境中執(zhí)行,所以需要一種機(jī)制,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境,所以,this出現(xiàn)了,它的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境。
var f = function() {
console.log(this.x)
}
上面代碼中,函數(shù)體里面的this.x 就是當(dāng)前運(yùn)行環(huán)境的x
var f = function() {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2
}
// 單獨(dú)執(zhí)行
f(); // 1
// obj環(huán)境執(zhí)行
obj.f() // 2
上面代碼中,函數(shù)f在全局執(zhí)行,this.x指向全局環(huán)境x

在obj環(huán)境執(zhí)行,this.x指向obj.x

回到文章開頭的問(wèn)題,obj.foo()是通過(guò)obj找到foo,所以就是在obj環(huán)境執(zhí)行,一旦var foo = obj.foo,變量就直接指向函數(shù)本身,所以foo()就變成在全局環(huán)境執(zhí)行。

