[JS] 動態(tài)執(zhí)行JS與修改詞法作用域
相關(guān)可行的操作
eval: 同步執(zhí)行,當(dāng)前作用域;setTimeout: 異步執(zhí)行,全局作用域;
第1個參數(shù)可以傳入函數(shù)對象,也可以傳入字符串,即要執(zhí)行的代碼。
script: 同步執(zhí)行,全局作用域;
創(chuàng)建script標(biāo)簽,并設(shè)置
innerHTML為要執(zhí)行的代碼。
Function: 同步執(zhí)行,全局作用域。
Function構(gòu)造函數(shù)可以傳入字符串,生成一個函數(shù)對象。
對詞法作用域的影響
eval
eval可以通過動態(tài)地執(zhí)行JS代碼從而修改(欺騙)當(dāng)前的詞法作用域,觀察如下代碼:
function foo(str, a){
eval(str);
console.log(a, b);
}
var b = 2;
foo("var b=3;", 1); // 1, 3
在函數(shù)中,執(zhí)行eval(..)將var b=3;帶入該詞法作用域,導(dǎo)致console.log(..)中對b的右值引用找到的是3,而不會查詢到外部的b=2。
在默認(rèn)情況下,eval會對所處的詞法作用域進(jìn)行修改。
在嚴(yán)格模式下,eval在運(yùn)行時有其自己的詞法作用域,即其動態(tài)執(zhí)行的JS聲明語句不會影響到eval語句所處的詞法作用域。
function foo(str){
"use strict";
eval(str);
console.log(a); // ReferenceError: a is not defined
}
foo("var a = 2;");
with
除了eval另外可以修改詞法作用域的語法是with關(guān)鍵字。
function foo(obj){
with(obj){
a = 2;
}
}
var o1 = {a: 3};
var o2 = {b: 3};
foo(o1);
console.log(o1.a); // 2
foo(o2);
console.log(o2.a); // undefined
console.log(a); // 2 (a被泄露到全局作用域了)
with接受一個對象,并將這個對象處理為一個完全隔離的詞法作用域,這個對象的屬性會被處理為定義在這個作用域內(nèi)的詞法標(biāo)識符。
所以當(dāng)o1傳遞給with時,with聲明的作用域是o1,包含了同o1.a對應(yīng)的標(biāo)識符a,這個左值引用可以找到目標(biāo),并成功完成賦值操作。
當(dāng)o2傳遞給with時,with聲明的詞法作用域會包含同o2.b對應(yīng)的標(biāo)識符b,但是沒有標(biāo)識符a,此時賦值操作會進(jìn)行LHS標(biāo)識符查詢,向外層作用域查找。
由于在o2的作用域、foo的函數(shù)作用域、全局作用域都沒有找到標(biāo)識符a,因此當(dāng)a = 2執(zhí)行時,會在全局作用域自動創(chuàng)建一個全局變量(如果是嚴(yán)格模式則不會)。
性能問題
JS引擎會在編譯階段進(jìn)行性能優(yōu)化,其中部分優(yōu)化依賴于根據(jù)代碼的詞法進(jìn)行靜態(tài)分析,并預(yù)先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標(biāo)識符。
eval和with這種可能動態(tài)更改詞法作用域的操作會導(dǎo)致JS引擎無法在詞法分析階段明確標(biāo)識符的位置,因此所有優(yōu)化可能都是無意義的,甚至JS引擎可能在讀取代碼中使用了eval和with,就放棄優(yōu)化了。
因此,在開發(fā)中應(yīng)該避免使用eval和with。
引用
[1] Scope and Closures, Kyle Simpson著(O'Reilly, 2014) 978-1-491-33558-8。

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