前端面試題:如何使字符串可執行

這是一道今年騰訊春招的面試題。當考官一開始問我這道題的時候,我沒有反應過來,什麼叫使字符串可執行。好吧,請原諒我的無知。考官耐心的解釋,字符串可執行的意思是將字符串轉爲立即執行的代碼,如字符串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

由於本人才疏學淺,暫時只知道這四種,如果您還有其他方法,歡迎指教。如您發現錯誤,請不吝指正。

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