收集一些觀點


嚴格的模板引擎的定義,輸入模板字符串 + 數據,獲得渲染過的字符串。實現上,從正則替換到拼 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)
  • 髒檢查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)
  • 依賴收集:從新收集依賴 O(data change) + 必要 DOM 更新 O(DOM 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 > 髒檢查 >= 依賴收集
  • 小量數據更新:依賴收集 >> Virtual DOM + 優化 > 髒檢查(沒法優化) > Virtual DOM 無優化
  • 大量數據更新:髒檢查 + 優化 >= 依賴收集 + 優化 > Virtual DOM(沒法/無需優化)>> MVVM 無優化

不要天真地覺得 Virtual DOM 就是快,diff 不是免費的,batching 麼 MVVM 也能作,並且最終 patch 的時候還不是要用原生 API。在我看來 Virtual DOM 真正的價值歷來都不是性能,而是它 1) 爲函數式的 UI 編程方式打開了大門;2) 能夠渲染到 DOM 之外的 backend,好比 ReactNative。

5. 總結

以上這些比較,更多的是對於框架開發研究者提供一些參考。主流的框架 + 合理的優化,足以應對絕大部分應用的性能需求。若是是對性能有極致需求的特殊狀況,其實應該犧牲一些可維護性採起手動優化:好比 Atom 編輯器在文件渲染的實現上放棄了 React 而採用了本身實現的 tile-based rendering;又好比在移動端須要 DOM-pooling 的虛擬滾動,不須要考慮順序變化,能夠繞過框架的內置實現本身搞一個。


從風格上來講 new 不是必要的,可是在主流的 JS 引擎裏 new 出來的對象更容易優化,由於 constructor + prototype 能夠映射到 hidden class。實際應用中 new 出來的對象相比 Object.create() 可能會有數倍的性能差別。


任何狀況下你問『咱們應不該該用框架 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 的理由。


假設咱們有一個數組,每一個元素是一我的。你面前站了一排人。
foreach 就是你按順序一個一個跟他們作點什麼,具體作什麼,隨便:
people.forEach(function (dude) {
  dude.pickUpSoap();
});
複製代碼
map 就是你手裏拿一個盒子(一個新的數組),一個一個叫他們把錢包扔進去。結束的時候你得到了一個新的數組,裏面是你們的錢包,錢包的順序和人的順序一一對應。
var wallets = people.map(function (dude) {
  return dude.wallet;
});
複製代碼
reduce 就是你拿着錢包,一個一個數過去看裏面有多少錢啊?每檢查一個,你就和前面的總和加一塊兒來。這樣結束的時候你就知道你們總共有多少錢了。
var totalMoney = wallets.reduce(function (countedMoney, wallet) {
  return countedMoney + wallet.money;
}, 0);
複製代碼

補充一個 filter 的:
你一個個錢包數過去的時候,裏面錢少於 100 塊的不要(留在原來的盒子裏),多於 100 塊的丟到一個新的盒子裏。這樣結束的時候你又有了一個新的數組,裏面是全部錢多於 100 塊的錢包:
var fatWallets = wallets.filter(function (wallet) {
  return wallet.money > 100;
});
複製代碼

最後要說明一點這個類比和實際代碼的一個區別,那就是 map 和 filter 都是 immutable methods,也就是說它們只會返回一個新數組,而不會改變原來的那個數。



要說簡單,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 對其也已經有實驗性的支持了。


nodejs寫的小工具,如何使用命令行直接執行?而不是先調用js文件,具體狀況往下看

  • 在 main.js 開頭加上
    #!/usr/bin/env node
    複製代碼

  • 在 package.json 裏面添加
    "bin": {
      "mytool": "main.js"
    }
    複製代碼

  • 若是你只是想本地使用,運行 npm link(至關於將一個本地包 install -g)
  • mytool 能夠直接做爲命令使用了。


  • 什麼是DOM

    牢記:站高一個維度去理解問題 !


    爲了理解DOM,咱們至少須要站在瀏覽器的角度來思考。


    DOM概念自己很簡單,請先徹底跟着個人思路來:

    1. 普通文檔(*.txt)和HTML/XML文檔(*.html/*.xml)的區別僅僅是由於後者是有組織的結構化文件;
    2. 瀏覽器將結構化的文檔以樹的數據結構讀入瀏覽器內存,並將每一個樹的子節點定義爲一個NODE(想象這顆樹,從根節點到葉子節點都被建模爲一個NODE對象);
    3. 這每一個節點(NODE)都有本身的屬性(名稱、類型、內容...);
    4. NODE之間有層級關係(parents、child、sibling...);
    5. 以上已經完成文檔的建模工做(將文檔內容以樹形結構寫入內存),此時再編寫一些方法來操做節點(屬性和位置信息),即爲NODE API。

    抽象一下:

    • DOM是一種將HTML/XML文檔組織成對象模型建模過程
    • DOM建模重點在於如何解析HTML/XML文檔和開放符合DOM接口規範的節點操做API接口

    再抽象一下:

    • 解析文檔,建模成對象模型,開放API接口。

    最後:

    • DOM:Document Object Model 文檔對象模型


    再回顧下整個過程,每一個步驟均可以問本身幾個問題,好比:DOM究竟是建模過程,仍是最後建的那個模型,仍是指操做節點的API接口呢,仍是...?


    以上是站在瀏覽器的角度思考DOM,你還能夠站在瀏覽器設計人員、網頁編碼人員等角度考慮:

    • DOM跟JavaScript什麼關係?
      • DOM很顯然誕生在瀏覽器,一開始是用JS實現的;
      • 但隨着DOM自己的發展,已經造成規範,你能夠用任何一種語言好比Python來解析文檔,生成對像樹,只要知足DOM標準,包括開放標準的操做接口,那你實現的就是一個DOM。
    • DOM開放的接口如何操做?
      • JS原生接口使用。
      • JQuery高緯度封裝如何使用。
    • ...

    做者:futeng連接:https://www.zhihu.com/question/34219998/answer/268568438

    相關文章
    相關標籤/搜索