嚴格的模板引擎的定義,輸入模板字符串 + 數據,獲得渲染過的字符串。實現上,從正則替換到拼 function 字符串到正經的 AST 解析各類各樣,但從定義上來講都是差很少的。字符串渲染的性能其實也就在後端比較有意義,畢竟每一次渲染都是在消耗服務器資源,但在前端,用戶只有一個,幾十毫秒的渲染時間跟請求延遲比起來根本不算瓶頸。卻是前端的後續更新是字符串模板引擎的軟肋,由於用渲染出來的字符串整個替換 innerHTML 是一個效率很低的更新方式。因此這樣的模板引擎現在在純前端情境下已經再也不是好的選擇,意義更可能是在於方便先後端共用模板。html
相比之下 Angular 是 DOM-based templating,直接解析 live DOM 來提取綁定,若是是字符串模板則是先轉化成 live DOM 再解析。數據更新的時候直接經過綁定作局部更新。其餘 MVVM 如 Knockout, Vue, Avalon 同理。缺點是沒有現成的服務端渲染,要作服務端渲染基本等於重寫一個字符串模板引擎。不過其實也不難,由於 DOM-based 的模板都是合法的 HTML,直接用現成的 HTML parser 預處理一下,後面的工做就相對簡單了。前端
框架裏面也有在前端解析字符串模板到靜態 AST 再生成 live DOM 作局部更新的,好比 Ractive 和 Regular。這一類的實現由於解析到 AST 的這步已經在框架內部完成了,因此作服務端渲染幾乎是現成的,另外也能夠在構建時進行預編譯。vue
而後說說 React,JSX 根本就不是模板,它就是帶語法糖的手寫 AST,而且把語法糖的處理放到了構建階段。由於運行時不須要解析,因此 virtual DOM 能夠每次渲染都從新生成整個 AST,在客戶端用 diff + patch,在服務端則直接 serialize 成字符串。全部其餘 virtual DOM 類方案同理。像 virtual-dom,mithril 之類的連語法糖都不帶。html5
最後說下個人見解:若是是靜態內容爲主,那就直接服務端渲染好了,首屏加載速度快。若是是動態的應用界面,那就不該該用拼模板的思路去作,而是用作應用的架構(MV*,組件樹)思路去作。3. MVVM vs. Virtual DOMnode
相比起 React,其餘 MVVM 系框架好比 Angular, Knockout 以及 Vue、Avalon 採用的都是數據綁定:經過 Directive/Binding 對象,觀察數據變化並保留對實際 DOM 元素的引用,當有數據變化時進行對應的操做。MVVM 的變化檢查是數據層面的,而 React 的檢查是 DOM 結構層面的。MVVM 的性能也根據變更檢測的實現原理有所不一樣:Angular 的髒檢查使得任何變更都有固定的 O(watcher count) 的代價;Knockout/Vue/Avalon 都採用了依賴收集,在 js 和 DOM 層面都是 O(change):能夠看到,Angular 最不效率的地方在於任何小變更都有的和 watcher 數量相關的性能代價。可是!當全部數據都變了的時候,Angular 其實並不吃虧。依賴收集在初始化和數據變化的時候都須要從新收集依賴,這個代價在小量更新的時候幾乎能夠忽略,但在數據量龐大的時候也會產生必定的消耗。git
MVVM 渲染列表的時候,因爲每一行都有本身的數據做用域,因此一般都是每一行有一個對應的 ViewModel 實例,或者是一個稍微輕量一些的利用原型繼承的 "scope" 對象,但也有必定的代價。因此,MVVM 列表渲染的初始化幾乎必定比 React 慢,由於建立 ViewModel / scope 實例比起 Virtual DOM 來講要昂貴不少。這裏全部 MVVM 實現的一個共同問題就是在列表渲染的數據源變更時,尤爲是當數據是全新的對象時,如何有效地複用已經建立的 ViewModel 實例和 DOM 元素。假如沒有任何複用方面的優化,因爲數據是 「全新」 的,MVVM 實際上須要銷燬以前的全部實例,從新建立全部實例,最後再進行一次渲染!這就是爲何題目裏連接的 angular/knockout 實現都相對比較慢。相比之下,React 的變更檢查因爲是 DOM 結構層面的,即便是全新的數據,只要最後渲染結果沒變,那麼就不須要作無用功。es6
Angular 和 Vue 都提供了列表重繪的優化機制,也就是 「提示」 框架如何有效地複用實例和 DOM 元素。好比數據庫裏的同一個對象,在兩次前端 API 調用裏面會成爲不一樣的對象,可是它們依然有同樣的 uid。這時候你就能夠提示 track by uid 來讓 Angular 知道,這兩個對象實際上是同一份數據。那麼原來這份數據對應的實例和 DOM 元素均可以複用,只須要更新變更了的部分。或者,你也能夠直接 track by $index 來進行 「原地複用」:直接根據在數組裏的位置進行復用。在題目給出的例子裏,若是 angular 實現加上 track by $index 的話,後續重繪是不會比 React 慢多少的。甚至在 dbmonster 測試中,Angular 和 Vue 用了 track by $index 之後都比 React 快: dbmon (注意 Angular 默認版本無優化,優化過的在下面)github
順道說一句,React 渲染列表的時候也須要提供 key 這個特殊 prop,本質上和 track-by 是一回事。shell
4. 性能比較也要看場合數據庫
在比較性能的時候,要分清楚初始渲染、小量數據更新、大量數據更新這些不一樣的場合。Virtual DOM、髒檢查 MVVM、數據收集 MVVM 在不一樣場合各有不一樣的表現和不一樣的優化需求。Virtual DOM 爲了提高小量數據更新時的性能,也須要針對性的優化,好比 shouldComponentUpdate 或是 immutable data。
不要天真地覺得 Virtual DOM 就是快,diff 不是免費的,batching 麼 MVVM 也能作,並且最終 patch 的時候還不是要用原生 API。在我看來 Virtual DOM 真正的價值歷來都不是性能,而是它 1) 爲函數式的 UI 編程方式打開了大門;2) 能夠渲染到 DOM 之外的 backend,好比 ReactNative。
5. 總結
以上這些比較,更多的是對於框架開發研究者提供一些參考。主流的框架 + 合理的優化,足以應對絕大部分應用的性能需求。若是是對性能有極致需求的特殊狀況,其實應該犧牲一些可維護性採起手動優化:好比 Atom 編輯器在文件渲染的實現上放棄了 React 而採用了本身實現的 tile-based rendering;又好比在移動端須要 DOM-pooling 的虛擬滾動,不須要考慮順序變化,能夠繞過框架的內置實現本身搞一個。任何狀況下你問『咱們應不該該用框架 X 換掉框架 Y』,這都不是單純的比較 X 和 Y 的問題,而得先問如下問題:
1. 現有的項目已經開發了多久?代碼量多大? 2. 現有的項目是否已經投入生產環境? 3. 現有的項目是否遇到了框架相關的問題,好比開發效率、可維護性、性能?換框架是否能解決這些問題?
(1) 事關替換的成本,(2) 事關替換的風險,(3) 事關替換的收益。把這些具體信息放在臺面上比較,纔有可能得出一個相對靠譜的結論。
--- (1) 跟 (2) 要具體狀況具體分析,因此就不談了。至於 (3),如下是 Vue 有而 ko 沒有的:
更好的性能,CLI,Webpack 深度整合,熱更新,模板預編譯,中文文檔,官方路由方案,官方大規模狀態管理方案,服務端渲染,render function / JSX 支持,Chrome 開發者插件,更多的社區組件和教程,尤爲是中文內容。
這裏沒有什麼說 ko 很差的意思。做爲前端 mvvm 的鼻祖,ko 對 Vue 的設計顯然有不少啓發,可是今天的 Vue 在各方面都實實在在地比 ko 強。若是上新項目,我想不出什麼繼續用 ko 的理由。
people.forEach(function (dude) {
dude.pickUpSoap();
});
複製代碼
var wallets = people.map(function (dude) {
return dude.wallet;
});
複製代碼
var totalMoney = wallets.reduce(function (countedMoney, wallet) {
return countedMoney + wallet.money;
}, 0);
複製代碼
var fatWallets = wallets.filter(function (wallet) {
return wallet.money > 100;
});
複製代碼
要說簡單,async 是最簡單的,只是在 callback 上加了一些語法糖而已。在不是很複雜的用例下夠用了,前提是你已經習慣了 callback 風格的寫法。
then.js 上手也是比較簡單的,由於也是基於 callback 和 continuation passing,並不引入額外的概念,比起 async,鏈式 API 更流暢,我的挺喜歡的。我挺久之前寫過一個在 Node 裏面跑 shell 命令的小工具,思路差很少:www.npmjs.org/package/she…
Callback-based 方案的最大問題在於異常處理,每一個 callback 都得額外接受一個異常參數,發生異常就得一個一個日後傳,異常發生後的定位很麻煩。
ES6 Promise, Q, Bluebird 核心都是 Promise,缺點嘛就是必須引入這個新概念而且要用就得全部的地方都用 Promise。對於 Node 的原生 API,須要進行二次封裝。Q 和 Bluebird 都是在實現 Promise A+ 標準的基礎上提供了一些封裝和幫助方法,好比 Promise.map 來進行並行操做等等。Promise 的一個問題就是性能,而 Bluebird 號稱速度是全部 Promise 庫裏最快的。ES6 Promise 則是把 Promise 的包括進 js 標準庫裏,這樣你就不須要依賴第三方實現了。
關於 Promise 可以如何改進異步流程,建議閱讀:www.html5rocks.com/en/tutorial…
co 是 TJ 大神基於 ES6 generator 的異步解決方案。要理解 co 你得先理解 ES6 generator,這裏就不贅述了。co 最大的好處就是能讓你把異步的代碼流程用同步的方式寫出來,而且能夠用 try/catch:
co(function *(){
try {
var res = yield get('http://badhost.invalid');
console.log(res);
} catch(e) {
console.log(e.code) // ENOTFOUND
}
})()
複製代碼
但用 co 的一個代價是 yield 後面的函數必須返回一個 Thunk 或者一個 Promise,對於現有的 API 也得進行必定程度的二次封裝。另外,因爲 ES6 generator 的支持狀況,並非因此地方都能用。想用的話有兩個選擇:
1. 用支持 ES6 generator 的引擎。好比 Node 0.11+ 開啓 --harmony flag,或者直接上 iojs;
2. 用預編譯器。好比 Babel (babeljs.io/) , Traceur (github.com/google/trac…) 或是 Regenerator (github.com/facebook/re…) 把帶有 generator 的 ES6 代碼編譯成 ES5 代碼。
(延伸閱讀:基於 ES6 generator 還能夠模擬 go 風格的、基於 channel 的異步協做:Taming the Asynchronous Beast with CSP in JavaScript)
可是 generator 的本意畢竟是爲了能夠在循環過程當中 yield 進程的控制權,用 yield 來表示 「等待異步返回的值」 始終不太直觀。所以 ES7 中可能會包含相似 C# 的 async/await :
async function showStuff () {
var data = await loadData() // loadData 返回一個 Promise
console.log(data) // data 已經加載完畢
}
async function () {
await showStuff() // async 函數默認返回一個 Promise, 因此能夠 await 另外一個 async 函數
// 這裏 showStuff 已經執行完畢
}
複製代碼
能夠看到,和用 co 寫出來的代碼很像,但語意上更清晰。由於本質上 ES7 async/await 就是基於 Promise + generator 的一套語法糖。深刻閱讀:ES7 async functions
想要今天就用 ES7 async/await 也是能夠的!Babel 的話能夠用配套的 asyncToGenerator transform: babeljs.io/docs/usage/…
Traceur 和 Regenerator 對其也已經有實驗性的支持了。
#!/usr/bin/env node
複製代碼
"bin": {
"mytool": "main.js"
}
複製代碼
牢記:站高一個維度去理解問題 !
爲了理解DOM,咱們至少須要站在瀏覽器的角度來思考。
DOM概念自己很簡單,請先徹底跟着個人思路來:
抽象一下:
再抽象一下:
最後:
再回顧下整個過程,每一個步驟均可以問本身幾個問題,好比:DOM究竟是建模過程,仍是最後建的那個模型,仍是指操做節點的API接口呢,仍是...?
以上是站在瀏覽器的角度思考DOM,你還能夠站在瀏覽器設計人員、網頁編碼人員等角度考慮:
做者:futeng連接:https://www.zhihu.com/question/34219998/answer/268568438