如何看待Vue.js 2.0 的模板編譯使用了`with(this)`的語法?

比如這樣的 Vue 組件

&
&

納尼?你再說一次.{{msg}}& &

&
export default {
data: {
msg: "我練功發自真心"
},
methods: {
sayHello: function (msg) {
alert("hello")
}
}
}
&

最終會被編譯成這樣的JS

module.exports={render:function (){with(this) { // &<- with return _h("p", { on: { "click": function($event) { alert(123) }, "hee": function($event) { sayHello(msg) } } }, ["納尼?你再說一次." + _s(msg)]) }},staticRenderFns: []}


為啥呢,因為沒有什麼太明顯的壞處(經測試性能影響幾乎可以忽略),但是 with 的作用域和模板的作用域正好契合,可以極大地簡化模板編譯過程。Vue 1.x 使用的正則替換 identifier path 是一個本質上 unsound 的方案,不能涵蓋所有的 edge case;而走正經的 parse 到 AST 的路線會使得編譯器代碼量爆炸。雖然 Vue 2 的編譯器是可以分離的,但凡是可能跑在瀏覽器里的部分,還是要考慮到尺寸問題。用 with 代碼量可以很少,而且把作用域的處理交給 js 引擎來做也更可靠。

用 with 的主要副作用是生成的代碼不能在 strict mode / ES module 中運行,但直接在瀏覽器里編譯的時候因為用了 new Function(),等同於 eval,不受這一點影響。

當然,最理想的情況還是可以把 with 去掉,所以在使用預編譯的時候(vue-loader 或 vueify),會自動把第一遍編譯生成的代碼進行一次額外處理,用完整的 AST 分析來處理作用域,把 with 拿掉,順便支持模板中的 ES2015 語法。也就是說如果用 webpack + vue 的時候,最終生成的代碼是沒有 with 的。


為什麼vue2做了這樣的選擇,請看小右自己的答案。我只是來賣萌的……

其實html里的event handler content attribute也可以認為用了with。

比如

&

&
...

相當於:

with (window) {
with (document) {
with ($("#myForm")) {
with ($("#myInput")) {
$("#myInput").onclick = function (event) {
console.log(event, id, test, open)
}
}
}
}
}

這樣比較的話,還是Vue2的handler scope更簡單一點啊!


補充下,with(this) 可以在某種程度上實現對於作用域的動態注入,如果走 AST 編譯的路線的話需要事先準備變數名列表,然後把綁定到特定名字的變數名替換成屬性讀寫,這個你看著簡單,實際上麻煩的要死,得在分析出了解析鏈之後才能做


使用 `with`, 就不需要在模板裡面寫 `this.` 了。如何看待?為了更方便的寫法犧牲部分性能唄。

為了進行測試,我 fork 了一份 Vue 源碼 GitHub - meowtec/vue at next-no-with,去掉了 with(使用模板時需要加 this).

我用項目目錄裡面的 /benchmarks/big-table 跑了一下,結果大致如圖:

使用 with:

不使用 with:

圖片裡面的時間是 Virtual DOM render 的時間,不包括真實 DOM 變動的時間。可以看到使用 with 語的確會對性能造成一些下降。

兩種情況下 initial render took 都在 550 左右徘徊,區別不大。因為真實 DOM 的渲染時間比 Virtual DOM 要長,而是否使用 with 只是影響了 Virtual DOM 的渲染,對真實 DOM 的渲染沒有影響。

對於普通需求來說,這種性能的影響比較小。如果比較介意,又不在乎多寫一個 `this.`, 可以自己搞 loader 進行編譯。


不用 with,真的沒有太好的辦法。你自己實現一下就知道了。

根據模板實現數據綁定,無非就是三條路可以走:

一,通過技巧在 js 內部解決問題,比如使用 with 關鍵字。

二,設計一套 DSL,編寫語法解析器轉譯 js 代碼。

三,設計一套 DSL,使用正則表達式通過查找和替換生成 js 代碼。

首先,毫無疑問第一種解決方案最容易實現,計算量也不大。並且,這個編譯功能是要支持在前端執行的,如果 vue 內部自帶一個 DSL transpiler,代碼體積會增大不少,所以第二條路不划算。

第三條路 vue 1.0 用過,我也試著實現了一下,不大靠譜。編寫一大堆回頭自己都看不懂的正則,還要輔以一些拼接代碼,還是無法避免 bug,因為邊界情況太多。最終還是妥協,規定訪問 model 數據必須以 this 開頭,非常醜陋。

所以 vue 2.0 的選擇是正確的。究其本質,with 關鍵字作為一個作用域隔離器,完美契合數據綁定這個場景。手寫不建議使用是因為容易出錯,但是代碼生成器一旦完成就不會變動,不存在出錯的風險。

vue 中還有一處使用聰明的技巧避免語法解析的例子,就是確定計算屬性依賴項那裡。沒有解析計算屬性的讀寫函數代碼,而是執行讀寫函數,把訪問到的屬性都記下來,作為依賴項。這裡如果用 AST 去分析依賴,無疑是非常麻煩的,將 js 解析器打包到框架中在工程上也是不可接受的。而用正則表達式,根本就沒法實現,除非增加限制(比如屬性名不能是變數)。

前端項目不比 c++/java,對輕量的要求要嚴格的多,有簡潔的方案肯定優先選擇。


不相關的,這讓我想起了以前老趙實現某庫時使用了eval被噴到放棄的事兒(手動白眼)


當年累死累活幹掉with的理由是啥,還是都沒有理由?


不建議手寫js時用with,因為坑多,但編譯結果有with應該不稀奇,生成的往往是模版代碼,不容易踩坑


早期 JS 模板語法流行的時候,大家在使用比如 ejs, baiduTemplate 等模板的時候,確實庫不是很大,大家去看源碼就知道大多都實現了 with ,如果沒有用 with 的也是強調模板里自己添加 this 來指向渲染的數據對象。

我們實現一個功能不僅是考慮性能的問題,也需要從實現成本和代碼執行的可預估性出發。如果不用 with, 模板標籤裡面的 JS 不可控,不單單只是所謂的給變數添加 this 這麼簡單,你可以嘗試一個個字元進行掃描,然後在進行變數值的添加,誰試誰知道。

當然也有黑魔法,那就是提前在 new function 的時候定義好所有變數,然後在把那個對象的需要用的數據,拆分成成數組,有興趣可以嘗試一下。


strict mode


理論與實踐的區別,去看jade,當然現在不叫jade了,同樣也是這麼做的。


想當年用 Backbone 的 underscore _.template() 的實現不也用到 with 么


推薦閱讀:

如何評價台灣人說大陸鋼年產量8萬噸,建一座橋用掉1/4?
如何評價《美國恐怖故事》第五季?
如何評價拉文和戈登在 2016 年 NBA 全明星扣籃大賽中的表現?

TAG:前端開發框架和庫 | JavaScript語言精粹 | 如何看待評價X | X編程語言有什麼奇技淫巧 | Vuejs |