最近在讀凱爾辛普森的《你不知道的JavaScript》,感覺挺有意思的,在理解作用域之後,看到了一個有意思的東西:欺騙詞法作用域。
首先來看看作用域是什麼吧。
作用域
簡而言之就是一套儲存變量並規定如何訪問並修改變量的規則,是幾乎所有編程語言最基本的功能之一。
作為JavaScript引擎的首席檢察官,他也會被自己人給騙了。
少廢話來看東西
欺騙方法一 :eval()函數
原理:JavaScript中的eval(str)函數可以接受一個字符串為參數,並將字符串內容視為好像在書寫時就存在於eval()函數所在位置的代碼。
function foo(str,a){
eval(str); //欺騙代碼
console.log(a,b);
}
var b = 2;
foo("var b = 3;" , 1);
猜猜運行結果是什麼呢?
是1, 3
乍一看,foo()中console.log(a,b)裏的b會會去外層訪問我們定義的全局變量b,作用域也是這麼想的,所以他只是一如往常的去檢查。
但是在執行eval(..)之後的代碼時,引擎並不“知道”或“在意”前面的代碼是以動態形式插入進來。
eval(..)調用中的"var b = 3; "這段代碼會被當作本來就在那裏一樣來處理。由於那段代碼聲明瞭一個新的變量b,因此它對已經存在的foo(..)的詞法作用域進行了修改。事實上,和前面提到的原理一樣,這段代碼實際上在foo(..)內部創建了一個變量b,並遮蔽了外部(全局)作用域中的同名變量。
當console.log(..)被執行時,會在foo(..)的內部同時找到a和b但是永遠也無法找到外部的b。因此會輸出“1, 3”而不是正常情況下會輸出的“1, 2”。
注意
嚴格模式的程序中,eval(..)在運行時有其自己的詞法作用域,意味着其中的聲明無法修改所在的作用域。
如下: function foo(str) { "use strict"; eval(str); console.log(a);
// ReferenceError: a is not defined
}
foo("var a = 2");`
其中eval(str);運行時無法修改所在的作用域了,此時a便無法找到。
JavaScript中還有其他一些功能效果和eval(..)很相似。setTimeout(..)和setInterval(..)的第一個參數可以是字符串,字符串的內容可以被解釋為一段動態生成的函數代碼。這些功能已經過時且並不被提倡。不要使用它們!
詞法作用域的"欺騙",eval()只是其中一個,更多詳情推薦看看凱爾辛普森的《你不知道的JavaScript》,他會帶你深入的瞭解JavaScript。