函數是一種將代碼作為數據結構存儲的便利方式,代碼之後可以被執行。這使得富有表現力的高階函數抽象如map和forEach成為可能。它也是js非同步I/O方法的核心。與此同時,也可以將代碼表示為字元串的形式傳遞給eval函數以達到同樣的功能。 程式員面臨一個選擇:應該將代碼表示為函數還是字元串? 毫無疑問... ...
函數是一種將代碼作為數據結構存儲的便利方式,代碼之後可以被執行。這使得富有表現力的高階函數抽象如map和forEach成為可能。它也是js非同步I/O方法的核心。與此同時,也可以將代碼表示為字元串的形式傳遞給eval函數以達到同樣的功能。
程式員面臨一個選擇:應該將代碼表示為函數還是字元串?
毫無疑問,應該將代碼表示為函數。字元串表示代碼不夠靈活的一個重要原因是:它們不是閉包。
閉包回顧
js的函數值包含了比調用它們時執行所需要的代碼還要多的信息。而且js函數值還在內部存儲它們可能會引用的定義在其封閉作用域的變數。那些在其所涵蓋的作用域內跟蹤變數的函數被稱為閉包。
詳細的信息到之前《[Effective JavaScript 筆記] 第11條:熟練掌握閉包》查看
字元串封裝代碼
假設有一個簡單的多次重覆用戶提供的動作的函數。
function repeat(n,action){ for(var i=0;i<n;i++){ eval(action); } }
該函數在全局作用域會不作得很好,因為eval函數會將出現的字元串中的所有變數引用作為全局變數來解釋。例如,一個測試函數基準執行速度的腳本可能恰好使用全局的start和end變數來存儲時間。
var start=[],end=[],timings=[]; repeat(1000,"start.push(Date.now());f();end.push(Date.now())"); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; }
但腳本很脆弱。如果我們簡單地將代碼移動到一個函數中,那麼start和end變數將不再是全局變數。
function benchmark(){ var start=[],end=[],timings=[]; repeat(1000,"start.push(Date.now());f();end.push(Date.now())"); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; }
這個時候repeat函數並不能訪問benchmark函數的內部變數start,end。還是在全局空間查找start,end變數,如果沒有還是較好的情況,可以根據錯誤提示,完成錯誤定位。如果這個時候全局中恰好有start,end變數,這個時候就會對全局變數進行修改,產生的行為無法進行預測。
閉包封裝代碼
還是使用上面的例子,但這一些我們使用閉包來對代碼進行處理。
改寫repeat函數,參數action是一個函數,而不是字元串
function repeat(n,action){ for(var i=0;i<n;i++){ action(); } }
改寫benchmark函數,腳本能安全地引用閉包中的局部變數start,end,該閉包以repeat函數的回調函數傳遞進來。
function benchmark(){ var start=[],end=[],timings=[]; repeat(1000,function(){ start.push(Date.now()); f(); end.push(Date.now()); }); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; }
eval函數的另一個問題是,通常一些高性能的引擎難優化字元串中的代碼,因為編譯器能不能儘可能早地獲得源代碼來及時 優化代碼。函數表達式在其代碼出現的同時就能被編譯,這使得它更適合標準化編譯。
其它eval相關內容可查看:
《[Effective JavaScript 筆記]第16條:避免使用eval創建局部變數》
《[Effective JavaScript 筆記]第17條:間接調用eval函數優於直接調用》
提示
-
當將字元串傳遞給eval函數以執行它們的API時,絕不要在字元串中包含局部變數引用
-
接受函數調用的API優於使用eval函數執行字元串的API
附錄:代碼完整版
把上面的代碼整理一下,生成一個可以測試任何函數執行時間的代碼
function repeat(n,action){ for(var i=0;i<n;i++){ action(); } } function benchmark(n,fn){ var start=[],end=[],timings=[]; repeat(n,function(){ start.push(Date.now()); fn(); end.push(Date.now()); }); for(var i=0,n=start.length;i<n;i++){ timings[i]=end[i]-start[i]; } return timings; } //測試代碼function concatString(a,b){ return a+b; } benchmark(10000,function(){concatString('1','2');});//常規調用 benchmark(10000,concatString.bind(null,'1','2'));//利於bind方法來產生新函數