我們知道JavaScript并不具有動(dòng)態(tài)作用域,它只有詞法作用域,什么是詞法作用域墓阀?
一、 詞法作用域
詞法作用域就是定義在詞法階段的作用域拓轻。換句話說斯撮,詞法作用域是由你在寫代碼時(shí)將變量和塊作用域?qū)懺谀睦飦頉Q定的。
無論函數(shù)在哪里被調(diào)用扶叉,也無論它如何被調(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í)行過程:
執(zhí)行 foo 函數(shù)溢十,先從 foo 函數(shù)內(nèi)部查找是否有局部變量 value,如果沒有达吞,就根據(jù)書寫的位置张弛,查找上面一層的代碼,也就是 value 等于 1酪劫,所以結(jié)果會(huì)打印 1吞鸭。
假設(shè)JavaScript采用動(dòng)態(tài)作用域,讓我們分析下執(zhí)行過程:
執(zhí)行 foo 函數(shù)覆糟,依然是從 foo 函數(shù)內(nèi)部查找是否有局部變量 value刻剥。如果沒有,就從調(diào)用函數(shù)的作用域滩字,也就是 bar 函數(shù)內(nèi)部查找 value 變量造虏,所以結(jié)果會(huì)打印 2盯滚。
前面我們已經(jīng)說了,JavaScript采用的是詞法作用域酗电,所以這個(gè)例子的結(jié)果是 1魄藕。
二、 but撵术,eval()和with可以通過其特殊性用來“欺騙”詞法作用域
欺騙詞法
JavaScript 中有兩種機(jī)制來實(shí)現(xiàn):在運(yùn)行時(shí)來“修 改”(也可以說欺騙)詞法作用域背率,欺騙詞法作用域會(huì)導(dǎo)致性能下降,所以不要使用eval和with嫩与。
(1)eval()
eval() 函數(shù)可以接受一個(gè)字符串為參數(shù)寝姿,對(duì)一段包含一個(gè)或多個(gè)聲明的“代碼”字符串進(jìn)行演算,并借此來修改已經(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)作本來就在那里一樣來處理处坪。
// 由于那段代 碼聲明了一個(gè)新的變量b根资,因此它對(duì)已經(jīng)存在的foo()的詞法作用域進(jìn)行了修改。
// 這段代碼實(shí)際上在 foo() 內(nèi)部創(chuàng)建了一個(gè)變量 b同窘,并遮蔽 了外部(全局)作用域中的同名變量玄帕。
但在嚴(yán)格模式的程序中,eval()在運(yùn)行時(shí)有其自己的詞法作用域想邦,意味著其中的聲明無法修改所在的作用域裤纹。
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 通過將一個(gè)對(duì)象的引用當(dāng)作作用域來處理鹰椒,將對(duì)象的屬性當(dāng)作作用域中的標(biāo)識(shí)符來處理,從而創(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 并沒有 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 被泄漏到全局作用域上了婴洼!