關於eval和數組計算的一些小問題?

因為之前看you dont know JS的時候有提到,因為`eval()`方法的特殊性,它可能會影響js引擎分析詞法時對於作用域的優化,而且也可能會帶來一些意料之外的問題,所以應該在代碼中避免使用……

不過如果只考慮性能,不考慮其他的隱患,如果說`eval`帶來的危害是作用域上的,那給它單獨分出一塊作用域,像這樣:

function myEvil(string) {
eval(string)
}

可行嗎?或是有其他的方法?

其實這個問題主要是因為如果是要對一組數字組成的數組進行求和的話,用這種感覺會不會比用`reduce`要好些?比如:

//數組是嚴格的全數字
var longArray = array.filter(function(value) {
return typeof value === "number"
})

var sumByReduce = longArray.reduce(function(prev, curr) {
return prev + curr
})

function evilSum(string) {
return eval(string)
}

var sumByEvil = evilSum(longArray.join("+"))
//倒是真的能用……

請無視我炸筆的英語……所以說……有沒有什麼比較好的方案呢?,大概就這個意思

最後本人野路子程序員不懂演算法……學了一些簡單的也感覺難以入門,不知道有什麼比較好的學習路徑呢?

題目較臭長,所以先謝過各位答題的大大了

============補充一下題目===========

先感謝各位的回答,抱歉我沒有說清楚……

其實是在做leetcode之類相似的題目的時候遇到的(不懂演算法做這個確實有點以卵擊石了……

大概會用到計算一個數組的和的函數,不過想了很久用了一些方法都還是超時,也許有一些更優的解法不過我目前確實沒想到……

下面@Keaton大大的回答里幾個測試里的數組的確會引出`eval`的問題(有的連reduce都會返回錯誤值),不過這裡不考慮數組本身有問題的情況……僅僅是想問下關於性能的問題……

不過答案也幾乎確定是`eval`應該完全不如內置方法……總之感謝回答了有點莫名的這問題的各位……


嗯先不說eval性能如何。簡單說題主比較的是:

longArray.reduce(function (acc, e) { return acc + e })

eval(longArray.join("+"))

的性能,那恐怕第二種寫法光是執行完join()的時間就已經夠第一種整個跑完了。所以還沒eval()什麼事,雖然eval()也是慢。

現代JavaScript引擎普遍會儘可能優化字元串拼接,但是無論如何,在題主說的數組元素都是Number的情況下,有些運算是無可避免的:

  • 每個元素從Number轉換為String
  • 分配拼接結果的String的內存
  • 將每個元素轉換出來的String的內容拷貝到拼接結果的String里

例如說,如果題主說的longArray是密集的、元素為Number的數組,V8在此情況下要做的正是這些事情:

  1. 分配一個臨時的內部用數組(InternalArray類型),跟輸入的數組同length

  2. 將輸入數組的每個元素轉換為String,填到第1步分配的數組裡。V8里的NumberToString()實現會先查詢一個cache,記住之前轉換過的數字對應的字元串對象的引用,如果cache里沒有再分Smi和double的情況來轉換。
  3. 把第1步分配數組拼接成一個字元串。實現在Runtime_StringBuilderJoin()。它大致上會先遍歷一遍數組來計算最終字元串的長度,根據這個長度分配拼接結果的字元串對象,然後逐個元素和分隔符複製到這個字元串對象里。

目測跑完這裡說的第2步的時候,題主要比較的第一種寫法(reduce())都已經跑完了orz


用new Function不就好了?

var sum=(new Function("return "+longArray.join("+")))()

上面這段答你原來的問題。

然後補充幾點:

第一呢,在你的用例里數組求和用reduce其實會更易理解,因為reduce本身的含義就是「歸併」,而且時間複雜度的方面,R大也已經提到了。

var sum=longArray.reduce( (pre,cur) =&> pre+cur ) //es6

第二呢,無論是eval還是new Function 都應該盡量避免使用,除非是出於某些特殊目的並且在這種情況下native coding又比較難實現時,比如Template,或者MVVM Framework里的expression binding這樣的功能(不用 new Function 就要自己寫parser)。

最後第三點,作為第一點的補充(High is better, Array Length:100000, Ops/sec, Chrome.42):

最快和最慢,這是一種什麼樣的差距:

實踐出真知


join是O(n)的,至少跑完一遍數組對不……

reduce也是O(n)的

然後join要做一次toString()和字元串拼接對不,字元串拼接的代價是很高很高的啊,itoa的代價也真心不低

然後eval還要對那大一串代碼進行編譯執行

這樣下來怎麼可能會更快啊……

array length = 4
-----------
loop
1000 tests in 0.19ms.
-----------
reduce
1000 tests in 0.81ms.
-----------
evil
1000 tests in 1.65ms.
========================
array length = 32
-----------
loop
1000 tests in 1.24ms.
-----------
reduce
1000 tests in 2.75ms.
-----------
evil
1000 tests in 3.40ms.
========================
array length = 256
-----------
loop
1000 tests in 0.34ms.
-----------
reduce
1000 tests in 14.05ms.
-----------
evil
1000 tests in 16.27ms.
========================
array length = 2048
-----------
loop
1000 tests in 1.87ms.
-----------
reduce
1000 tests in 95.72ms.
-----------
evil
1000 tests in 147.06ms.
========================
array length = 16384
-----------
loop
1000 tests in 14.88ms.
-----------
reduce
1000 tests in 750.64ms.
-----------
evil
1000 tests in 4512.27ms.
========================


親測,eval寫法在longArray很長的情況下性能級低


樓上說的都正確,Eval在把編譯時會先把字元串解析成對應底層語言,數組少的時候不會什麼大影響,但是長了解析過程也多了


鑒於前面 @Saviio已經給出了解決方案,目測他在這個領域比我懂得多,我就不詳細解釋作用域的問題。這裡只簡單說說為什麼不推薦使用題主用eval求和的方法。

題主可以去試試以下幾個測試用例,分別用兩種方法求和,看看結果是什麼

longArray = [1, 1, , 1, 1];

longArray = [1, 1, null, 1, 1];

longArray = [1, 1, "1", 1, 1];

longArray = [1, 1, "", 1, 1];

然後仔細分析一下為什麼是這些結果,應該能對eval方法有更進一步的認識


推薦閱讀:

編寫瀏覽器插件如何入門?
為什麼es6里的object不可迭代?
作為一名前端開發工程師,哪門後端語言最適合掌握?
2016 年前端開發領域有什麼趨勢值得關注?
如何系統的學習nodejs?

TAG:前端開發 | JavaScript | eval |