new 了一個 Function

小蟲巨蟹發表於2018-06-22

new Function 和 eval 的區別可以搜尋到蠻多解釋,但總覺得還不夠具體,尋思著補補刀

一、從簡易模板引擎說起

模板引擎可以怎樣理解呢? 在一段 Html 文件裡面有許多佔位符,同時現在還有另一份 Data 資料,將 Data 注入到 Html 中填充佔位符的方法,就是模板引擎了(簡單得一批)

既然已經知道目標,那就來簡單實現一個不帶ifelse ifelsefor的(要不我說簡易版嘛),模板定義如下:

<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
     <p>{{hello + world}}</p>
  </body>
</html>
複製程式碼

這裡只有一個關鍵點,就是使用 {{}} 雙花括號作為模板的佔位符,其中可以讀取 Data 中的變數,也可以進行一些簡單合法的 Js 表示式,對應的,準備這樣一份 Data:

{
  "title": "facemagic",
  "hello": "hello",
  "world": "world"
}
複製程式碼

Now,發揮你的想象,如何才可以將 Data 注入到 Tpl 當中,輸出目標 Html 字串?注意到,模板佔位符 {{}} 中的內容至少是一個合法的 Js 表示式,使用正規表示式找到所有佔位符裡的表示式,再通過 eval 來執行,完了把執行結果替換掉佔位符,完美!!!來,走一個:

  // node 環境

  // data 解構到 global 下
  for (const key in data) {
    global[key] = data[key]
  }
  const regex = /{{([^}]*)}}/g;
  const source = source.replace(regex, (m, n) => {
    let result = m;
    try {
      result = eval(n)
    } catch (e) {
    }
    return result;
  });
複製程式碼

Wait a minute !!! 為毛要將 data 中的元素全部賦值到 global 下? 注意模板表示式的寫法是 {{title}} 而不是 {{data.title}},省略了根索引有木有,為了執行 eval 不報錯,必須把 data 解構到 global 中。

這樣就有瑕疵了,如果程式中不小心定義了一個變數剛好跟 data 的某個鍵重名了。那麼可怕的事情將會發生

二、使用 new Function

在上述的情況中,eval 的方式會造成變數的全域性汙染,幸運的是,使用 new Function 可以有效的解決這個問題。奧妙就在於,雖然 new Function 跟 eval 的執行效果類似(前者應該封裝了後者),但是 new Function 是可以傳參的,是這樣定義的

  new Function(arg1, arg2, ..., code)
複製程式碼

其中,code 可以直接使用 arg1、arg2、... 例如:

  const func = new Function(a, b, 'return a+b');
  func(1, 2); // 3
複製程式碼

基於此,我們只需要將 data 解構,作為引數傳入構造好的 func,就不會有全域性汙染了:

function excute(keys, values, statement) {
  const caller = new Function(...keys, `return (${statement})`);
  return caller(...values);
}

function parse(source, data) {
  const keys = Object.keys(data);
  const values = [];
  for (let i = 0; i < keys.length; i++) {
    values.push(data[keys[i]])
  }
  const regex = /{{([^}]*)}}/g;
  source = source.replace(regex, (m, n) => {
    let result = m;
    try {
      result = excute(keys, values, n);
    } catch (e) {
    }
    return result;
  })
}
複製程式碼

嗯嗯,可以收工了~~

三、總結

舉了一個這麼大大咧咧的栗子,其實想表達的不過是,new Function 相比於 eval 可以傳入引數,可以有更好的作用域壁壘。

不過,應該看著不無聊...

相關文章