我們知道JavaScript并不具有動(dòng)態(tài)作用域,它只有詞法作用域,什么是詞法作用域?
一、 詞法作用域
詞法作用域就是定義在詞法階段的作用域。換句話說(shuō),詞法作用域是由你在寫(xiě)代碼時(shí)將變量和塊作用域?qū)懺谀睦飦?lái)決定的。
無(wú)論函數(shù)在哪里被調(diào)用,也無(wú)論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 結(jié)果是 ???
假設(shè)JavaScript采用靜態(tài)作用域,讓我們分析下執(zhí)行過(guò)程:
執(zhí)行 foo 函數(shù),先從 foo 函數(shù)內(nèi)部查找是否有局部變量 value,如果沒(méi)有,就根據(jù)書(shū)寫(xiě)的位置,查找上面一層的代碼,也就是 value 等于 1,所以結(jié)果會(huì)打印 1。
假設(shè)JavaScript采用動(dòng)態(tài)作用域,讓我們分析下執(zhí)行過(guò)程:
執(zhí)行 foo 函數(shù),依然是從 foo 函數(shù)內(nèi)部查找是否有局部變量 value。如果沒(méi)有,就從調(diào)用函數(shù)的作用域,也就是 bar 函數(shù)內(nèi)部查找 value 變量,所以結(jié)果會(huì)打印 2。
前面我們已經(jīng)說(shuō)了,JavaScript采用的是詞法作用域,所以這個(gè)例子的結(jié)果是 1。
二、 but,eval()和with可以通過(guò)其特殊性用來(lái)“欺騙”詞法作用域
欺騙詞法
JavaScript 中有兩種機(jī)制來(lái)實(shí)現(xiàn):在運(yùn)行時(shí)來(lái)“修 改”(也可以說(shuō)欺騙)詞法作用域,欺騙詞法作用域會(huì)導(dǎo)致性能下降,所以不要使用eval和with。
(1)eval()
eval() 函數(shù)可以接受一個(gè)字符串為參數(shù),對(duì)一段包含一個(gè)或多個(gè)聲明的“代碼”字符串進(jìn)行演算,并借此來(lái)修改已經(jīng)存在的詞法作用域(在運(yùn)行時(shí));
function foo(str, a) {
eval( str ); // 欺騙!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
// 上面代碼中,eval()調(diào)用中的"var b = 3;"這段代碼會(huì)被當(dāng)作本來(lái)就在那里一樣來(lái)處理。
// 由于那段代 碼聲明了一個(gè)新的變量b,因此它對(duì)已經(jīng)存在的foo()的詞法作用域進(jìn)行了修改。
// 這段代碼實(shí)際上在 foo() 內(nèi)部創(chuàng)建了一個(gè)變量 b,并遮蔽 了外部(全局)作用域中的同名變量。
但在嚴(yán)格模式的程序中,eval()在運(yùn)行時(shí)有其自己的詞法作用域,意味著其中的聲明無(wú)法修改所在的作用域。
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );
(2)with
with 通常被當(dāng)作重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式,可以不需要重復(fù)引用對(duì)象本身。
with 通過(guò)將一個(gè)對(duì)象的引用當(dāng)作作用域來(lái)處理,將對(duì)象的屬性當(dāng)作作用域中的標(biāo)識(shí)符來(lái)處理,從而創(chuàng)建了一個(gè)新的詞法作用域(在運(yùn)行時(shí))。
盡管with 塊可以將一個(gè)對(duì)象處理為詞法作用域,但是這個(gè)塊內(nèi)部正常的var聲明并不會(huì)被限制在這個(gè)塊的作用域中,而是被添加到with 所處的函數(shù)作用域中。
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {a: 3};
var o2 = {b: 3};
// 將 o1 傳遞進(jìn)去,a=2 賦值操作找到了 o1.a 并將 2 賦值給它
foo( o1 );
console.log( o1.a ); // 2
// 當(dāng) o2 傳遞進(jìn)去,o2 并沒(méi)有 a 屬性,因此不會(huì)創(chuàng)建這個(gè)屬性, o2.a 保持 undefined
// 此時(shí),a = 2 賦值操作創(chuàng)建了一個(gè)全局的變量 a
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!