被嫌棄的eval和with

小火柴的藍色理想發表於2016-08-01

前面的話

  eval和with經常被嫌棄,好像它們的存在就是錯誤。在CSS中,表格被嫌棄,在網頁中只是用表格來展示資料,而不是做佈局,都可能被斥為不規範,矯枉過正。那關於eval和with到底是什麼情況呢?本文將詳細介紹eval()函式和with語句

 

eval

定義

  eval()是一個全域性函式,javascript通過eval()來解釋執行由javascript原始碼組成的字串

var result = eval('3+2');
console.log(result,typeof result);//5 'number'

用法

  eval()只有一個引數,如果傳入的引數不是字串,它直接返回這個引數。如果引數是字串,它會把字串當成javascript程式碼進行編譯。如果編譯失敗則丟擲一個語法錯誤(syntaxError)異常。如果編譯成功,則開始執行這段程式碼,並返回字串中的最後一個表示式或語句的值,如果最後一個表示式或語句沒有值,則最終返回undefined。如果字串丟擲一個異常,這個異常將把該呼叫傳遞給eval()

var num = 1;
var str = 'test';
console.log(eval(num));//1
console.log(eval(str));//ReferenceError: test is not defined
var strLong1 = 'var x = 1;var y = 2;';
console.log(eval(strLong1),x,y);//undefined 1 2

var strLong2 = 'var x = 1; x++;';
console.log(eval(strLong2),x);//1 2

作用域

  eval()使用了呼叫它的變數作用域環境。也就是說,它查詢變數的值和定義新變數和函式的操作和區域性作用域中的程式碼完全一樣

var b = 2;
function foo(str,a){
    eval(str);
    console.log(a,b);
}
foo('var b = 3;',1);//1 3

別名

  當通過別名呼叫時,eval()會將其字串當做頂層的全域性程式碼來執行。執行的程式碼可能會定義新的全域性變數和全域性函式,或者給全域性變數賦值,但卻不能使用或修改函式中的區域性變數

var geval = eval; 
var x = 'global',y = 'global'; 
function f(){
    var x = 'local';
    eval('x += "changed";');
    return x;
}
function g(){
    var y = 'local';
    geval('y += "changed";');
    return y;
}
console.log(f(),x);//localchanged global
console.log(g(),y);//local globalchanged

  [注意]IE8-瀏覽器通過別名呼叫eval()和正常呼叫eval()的結果相同

副作用

  javascript直譯器進行了大量的程式碼分析和優化。而eval()的問題在於,用於動態執行的程式碼通常不能分析,於是直譯器也無法對其進行優化,這會導致效能下降

  與eval()類似的有setTimeout()、setInterval()、new Function()等,這些函式都可以以字串作為引數,在程式執行時動態執行。這種執行機制帶來的好處無法抵消其效能上的損失,所以應該儘量避免使用

嚴格模式

  由於eval()函式過於強大,嚴格模式對其進行了嚴格的限制

  【1】不能通過eval()函式來建立變數或函式,但可以查詢和更改其值

'use strict';
eval('var x = 1;');
console.log(x);//ReferenceError: x is not defined

'use strict';
var x = 1;
eval('x = 2;');
console.log(x);//2

  【2】禁止使用eval作為識別符號

'use strict';
var eval = 10;//SyntaxError: Unexpected eval or arguments in strict mode

 

with

  定義with語句的目的主要是為了簡化多次編寫同一物件的工作

  with語句將object新增到作用域鏈的頭部,然後執行statement,最後把作用域鏈恢復到原始狀態

with(object){
  statement;
}

作用

  在物件巢狀層次很深的時候通常會使用with語句來簡化程式碼編寫。而本質上是通過將一個物件的引用當作作用域來處理,將物件的屬性當作作用域中的識別符號來處理,從而建立了一個新的詞法作用域

  在客戶端javascript中,可能會使用類似下面這種表示式來訪問一個HTML表單中的元素

document.forms[0].address.value

  如果這種表示式在程式碼中多次出現,則可以使用with語句將form物件新增到作用域鏈的頂層

with(document.forms[0]){
    name.value = '';
    address.value = '';
    emai.value = '';
}

  這種方法減少了大量的輸入,不用再為每個屬性名新增document.forms[0]字首。這個物件臨時掛載在作用域鏈上,當javascript需要解析諸如address的識別符號時,就會自動在這個物件中查詢 

  [注意]with語句提供了一種讀取物件的屬性的快捷方式,但它並不能建立物件的屬性

  如果物件o有一個屬性x,那麼下面程式碼給這個屬性賦值為1

var o  = {x:0};
with(o) x = 1;
console.log(o.x);//1

  如果o中沒有定義屬性x,下面程式碼和不使用with語句的程式碼x=1是一模一樣的。這是因為對變數x進行了LHS查詢,並將1賦值給它

var o  = {};
with(o) x = 1;
console.log(o.x);//undefined
console.log(x);//1

副作用

  與eval類似,with語句的javascript程式碼非常難於優化,同時也會給除錯程式碼造成困難,並且同沒有使用with語句的程式碼相比,它運算得更慢

  而且,如果with語句使用不當,還有可能造成變數洩漏,汙染全域性作用域的情況

var x = 1;
var o = {};
with(o){
    x = 2;
}
console.log(x);//2
console.log(o.x);//undefined

嚴格模式

  嚴格模式下,禁止使用with語句

//SyntaxError: Strict mode code may not include a with statement
'use strict';
var o = {};
with(o){
    x = 2;
}

 

最後

  使用eval和with會使得引擎無法在編譯時對作用域查詢進行優化,從而導致效能下降,程式碼執行變慢。因為eval和with在實際工作中很少使用,所以嚴格模式下的限制,對我們來說影響不大。就像比如外交部某一天釋出公告,我國不再發放去牙買加的簽證,牙買加雖然都聽過,但大多數人這輩子都可能不去一回,所以,無所謂了。同樣地,eval和with被嫌棄不嫌棄的,也是無所謂了

  但即使不去牙買加,也不妨礙我瞭解到牙買加是加勒比海的其中一個島國,它是英聯邦成員國,也就是原來的英國殖民地,後來獨立了。它的國旗上面是一個X

  類比於eval和with

  以上

相關文章