這是一道今年騰訊春招的面試題。當考官一開始問我這道題的時候,我沒有反應過來,什麼叫使字符串可執行。好吧,請原諒我的無知。考官耐心的解釋,字符串可執行的意思是將字符串轉爲立即執行的代碼,如字符串var a=23;
,使其經過某種操作後確實有個值爲23的a變量。聽了面試官的話後,我第一時間想到了eval
方法。然而面試官並不滿意,追問我還有其他方式嗎,我一時沒有想起來。面試過後我就想總結一下這個問題,不過因爲拖延症,拖到今天才寫,敬請見諒。本文內容主要借鑑《JavaScript忍者祕籍》中使字符串轉爲立即執行的代碼一節。下面將講述字符串可執行的四種方式。
1. eval()
方法
eval()
方法是一個很強大的方法,它接收一個字符串作爲參數,當解析器發現代碼中調用該方法時,會將傳入的參數當作ECMAScript語句來解析,然後把執行結果插入到原位置。如代碼eval('var str="hi"');
,執行了這個語句後,就可以直接使用變量str
了,如console.log(str)
將輸出”hi”。不過關於這個方法有以下幾點要注意:
(1). 通過該方法執行的代碼有與該執行環境相同的作用域鏈。這意味着它可以引用在包含環境中定義的變量。也意味着它可以形成閉包,這與下文的Function方法形成對比。
var str="hi, I'm student!";
eval('console.log(str)');
//output:hi, I'm student!
(2). 在eval()中創建的任何變量或函數都不會被提升。這很好理解,因爲它只有在解析之後纔會運行。
f1() //Uncaught ReferenceError: f1 is not defined
eval("function f1(){console.log('hi')}")
(3). 任何不是簡單變量、原始值、賦值語句的內容都需要在外面包裝一個括號。執行返回結果則是最後一個表達式的執行結果。eval("{name:1}")
的結果是1,驚不驚喜,意不意外?
var o1=eval("1");
console.log(o1);
//output:1
var o2=eval("{name:1}")
console.log(o2);
//output:1 why?
var o3=eval("({name:1})")
console.log(o3);
//output: Object {name: 1}
eval("3+4,5+6")//output:11
(4). eval()方法是全局方法,但eval和window.eval結果有時不同的。前者作用域頂端是當前上下文,後者作用域是window對象。
var color='red';
function test(){
var color='blue';
eval('console.log(color)');
window.eval('console.log(color)');
with(window){
eval('console.log(color)');
}
}
test()
//outputs:
//blue
//red
//red
2. 使用Function
構造器
我們都知道函數實際上是對象。每個函數都是Function類型的實例,而且都與其他引用類型一樣具有屬性和方法。函數除了使用函數聲明和函數表達式,第三種定義函數的方式就是使用Function構造函數。以下摘自《JavaScript高級程序設計》:
Function構造函數可以接受任意數量的參數,但最後一個參數始終都被看成是函數體,而前面的參數則枚舉出了新函數的參數。
我們不推薦讀者使用這種方法定義函數,因爲這種語法會導致解析兩次代碼(第一次是解析常規的ECMAScript代碼,第二次是解析傳入構造函數中的字符串),從而影響性能。
我相信大家平常也很少使用這種方式,不過這種方式在模板引擎中(如EJS)應用很廣泛。使用它確實能使字符串執行,只不過作用域是在函數裏。但有一點要注意,就是它不會創建閉包,請看如下代碼:
var color="red"
function test1(){
var color="blue";
eval("var f1=function(){console.log(color)}");
return f1;
}
test1()()
//output:blue
function test2(){
var color="blue";
var f2=new Function("console.log(color)");
return f2;
}
test2()()
//output:red no closure!
3. 使用setTimeout
setTimeout通常被用來執行異步代碼,如可以用setTimeout(function(){console.log(2)},1000)
來實現隔1s後輸出2。不過你也可以在第一個參數中傳入字符串,如下所示:
function print(num){
console.log(num);
}
setTimeout("print(2)",1000);
當傳入的第一個參數爲字符串時,感覺像是在使用eval方法處理似的,至於是不是,我也不知道。如果您知道,請不吝賜教。其實setTimeout很強大,我也收集過一篇講述setTimeout的好文章,就是它了:你應該知道的setTimeout祕密。
4. 巧用<script>
標籤
將要執行的代碼放在動態生成的script標籤內,並將標籤注入文檔中,同樣可實現字符串的執行。如下所示:
var str="console.log(2222)"
var script=document.createElement('script');
script.text=str;
document.body.appendChild(script);
document.body.removeChild(script);
//output:2222
由於本人才疏學淺,暫時只知道這四種,如果您還有其他方法,歡迎指教。如您發現錯誤,請不吝指正。