new Function 和 eval 的區別可以搜尋到蠻多解釋,但總覺得還不夠具體,尋思著補補刀
一、從簡易模板引擎說起
模板引擎可以怎樣理解呢? 在一段 Html 文件裡面有許多佔位符,同時現在還有另一份 Data 資料,將 Data 注入到 Html 中填充佔位符的方法,就是模板引擎了(簡單得一批)
既然已經知道目標,那就來簡單實現一個不帶if
、else if
、else
、for
的(要不我說簡易版嘛),模板定義如下:
<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 可以傳入引數,可以有更好的作用域壁壘。
不過,應該看著不無聊...