Babel的ES6翻譯存在明顯的缺陷,為什麼沒怎麼見人提過這種大坑?
例如:
var a;
var b="a";
{
let a=1;
alert(eval(b));
}
被翻譯成了:
"use strict";
var a;
var b="a";
{
var _a=1;
alert(eval(b));
}
而事實上,顯然希望翻譯成:
var a;
var b="a";
(function(){
var a=1;
alert(eval(b));
}).apply(this,typeof arguments==="undefined"?null:arguments);
當然,這又會引發別的問題,雖然可能性低很多。
所以我想通過這個針對的不是Babel不好,而是語法100%正確翻譯這件事是否可能?以及這麼明顯的問題為什麼沒有被廣泛討論。
會出問題的代碼在運行時會依賴你的源碼,這是問題的本質。
考慮這樣的代碼:function foo(bar) {
return bar;
}
console.log(foo.toString().includes("bar")); // true
function foo(o){return o}console.log(foo.toString().includes("bar"));
很明顯,壓縮工具也會讓這樣的代碼無法正常運行。所以這根本就不是 Babel 的問題,本質上只要你運行時依賴了源碼,不管是以什麼形式(依賴變數名叫 a、依賴函數參數名叫 bar,甚至是依賴我是空格縮進而不是 tab 縮進),在代碼進行任何形式的變換後都有可能產生錯誤。
只要所用的工具對你的這種依賴不感知,是不可能進行處理的。如果你的依賴是靜態可描述的,那麼你通過修改工具,就有可能使得你引入的額外邏輯仍然有效。
所以我覺得之所以沒有引起廣泛討論,是因為大家會避免寫這樣的代碼吧。這個話題挺有趣的。當然是有辦法在Babel里實現題主想要的那種翻譯方式——當發現作用域中有代碼可能捕獲當前作用域的binding信息(例如直接eval()),就採用IIFE而不是直接給變數改名的方式來翻譯。
實際的JavaScript引擎里的JIT編譯器里實現也會做類似的事情的:檢測函數體有沒有可能捕獲其binding信息,如果不可能的話就最大限度優化局部變數(例如說把它們放寄存器里之類),如果有可能被捕獲則乖乖的放在某種Context / Scope / Binding對象里。我知道有這樣的實現,所以覺得在Babel里做這個事情應該也能做到。
至於為啥Babel沒有做這個,大概就跟其它回答提到的一樣,需求不夠大吧。Babel畢竟跟壓縮JS代碼的工具不一樣,給變數改名並不是它的本意。那些通過給變數改名來實現縮短代碼並且混淆代碼的工具,自然是本質上跟題主所想像的場景相衝突的。這讓我想到那個「源碼里的注釋會不會影響運行時性能」的問題。開式 eval 和 Function::toString 幾乎是沒法處理的
前面已經說了很好了,補充一下。
無論是 Babel 還是其他編譯或壓縮工具是無法在「詞法分析」階段很明確的知道 eval(...) 會執行什麼代碼的,因此這確實不是 Babel 的問題。
當然為了模擬「塊級作用域」,翻譯器確實有其他不同的翻譯方式,比如:
iifevar a;
var b="a";
(function(){
var a=1;
alert(eval(b));
}).apply(this, typeof arguments === "undefined" ? null : arguments);
但基於這種方式會導致一些不好的問題,比如 this, arguments, return, break, continue 的行為都會發生變化,所以這並不是一個很好的解決方案。
try/catchvar a;
var b="a";
try {
throw 1;
} catch (a) {
alert(eval(b));
}
catch 分句和 with 是 ES6 之前真正可以創建塊級作用域的語句,因此很好的解決了這個問題,早期 Traceur 也是基於這種方式翻譯的,但後來考慮到 try/catch 的糟糕性能,這種方式也逐漸被取代掉。
BlockBindingTransform should not use try/catch always · Issue #6 · google/traceur-compiler · GitHublet =&> varvar a;
var b="a";
{
var _a=1;
alert(eval(b));
}
這是目前 Babel 和 Traceur 的翻譯方式,當然不是簡單的直接替換 let,還會根據詞法分析判斷當前代碼塊所包含的作用域鏈有沒定義或引用過同名變數,以及有沒被閉包所引用,如果有的話則改變變數命名(比如 Babel 會把 a 替換成 _a,Traceur 會把 a 替換成 a$__0)否則不做改變。
希望會有所幫助。嚴格模式eval根本不能訪問函數作用域好不啦,你們都錯了
竟然不見輪子哥跑來推銷M$的typescript?-----------更新---------------------題主可以看下,ts是怎麼避免的(#滑稽)
這個故事告訴我們 盡量不要用eval
還是不理解, 為什麼let a=1;用babel轉換為es6的時候就變成了 var _a=1 這樣有什麼意義?
我突然感覺寫好代碼是一件利人利己的事
EVAL 就是 邪惡,編譯器碰到EVAL 應該說編譯失敗
卧槽我之前好像就是翻譯成匿名函數的……
IIFE can lead to de-opt in perf-critical scenarios.
推薦閱讀:
※有了Babel的話還在使用TypeScript的優勢在哪?
※如何評價 Webpack 2 新引入的 Tree-shaking 代碼優化技術?
TAG:ECMAScript2015 | Babel |