js中的eval和with

js中有兩個方法可以改變詞法作用域,eval(...)和with(...)。前者可以接受一段字符串代碼來進行演算,並藉此來修改已存在的詞法作用域,後者本質上是通過將一個對象的引用當作詞法作用域,將對象的屬性當作作用域中的標識符來處理,從而創建一個新的詞法作用域。

首先我們來理解詞法作用域的概念:

作用域

圖中的1和2分別表示兩個作用域,這裏的b變量只能在作用域1中找到,一般來說,變量所在的作用域都與變量聲明的位置有關,但是在JS中的eval和with函數可以改變作用域,讓變量的作用域與聲明時所在的位置無關,我們再來看下面這個例子:

function test(str) {
    eval(str);
    console.log(a + b); //輸出3
}
test("var a = 1; var b = 2");

這裏按照我們的理解,console.log(a + b)應該報出ReferenceError,但是因爲eval的存在並且接受一個聲明a和b兩個變量的字符串導致程序不是我們預期的結果。而a和b的作用域也變成了test函數內。

我們再來看一下with函數:

//我們給一個對象賦值的方式
var obj = {
    a: 1,
    b: 2,
    c: 3
}
//或者
obj.a = 1;
obj.b = 2;
obj.c = 3;
//使用with函數
with(obj) {
    a = 1;
    b = 2;
    c = 3;
}

這種方式不僅僅可以用來方便的訪問屬性對象,也可以用來改變詞法作用域:

function test (obj) {
    with(obj) {
        a = 2;
    }
}
var o1 = {
    a: 3
}
var o2 = {
    b: 3         
}
test(o1);
console.log(o1.a); //2
test(o2);
console.log(o2.a); //undefined
//接下來我們輸出一下全局a的值,我們會發現a泄漏到了全局變量上
console.log(a);//2

首先,當將o1傳遞給test時,with函數創建了一個o1對象的作用域,並且在這個作用域中找到了a,然後將2賦給a。當o2傳遞給test時,with函數創建一個o2對象的作用域,然後在這個作用域中沒有找到a,接下來根據LHS查找規則,會向上層作用域查找a,這裏with創建的o2作用域的上層作用域是test函數,在test中也沒有找到,接下來再向上找,也就是全局作用域,在全局作用域中也沒找到,然後就會在最上層作用域中創建一個var a = 2。

上面就是eval和with函數的作用,現在我們一般都儘量不會去使用者兩個函數,因爲在JS的嚴格模式下會對這兩個函數有限制,並且,因爲編譯器無法知道eval函數和with函數接受到的參數會是什麼,所以編譯器在編譯時很難對代碼進行優化,在程序中出現很多eval和with時會使程序變得非常慢。

總之,eval和with可以在運行時修改或創建新的詞法作用域,但是我們要儘量避免使用它們。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章