歡迎關注個人公衆號睿Talk
,獲取我最新的文章:
javascript
目前最流行的兩大前端框架,React和Vue,都不約而同的藉助Virtual DOM技術提升頁面的渲染效率。那麼,什麼是Virtual DOM?它是經過什麼方式去提高頁面渲染效率的呢?本系列文章會詳細講解Virtual DOM的建立過程,並實現一個簡單的Diff算法來更新頁面。本文的內容脫離於任何的前端框架,只講最純粹的Virtual DOM。敲單詞太累了,下文Virtual DOM一概用VD表示。前端
這是VD系列文章的開篇,如下是本系列其它文章的傳送門:
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優化
你不知道的Virtual DOM(四):key的做用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新java
本質上來講,VD只是一個簡單的JS對象,而且最少包含tag、props和children三個屬性。不一樣的框架對這三個屬性的命名會有點差異,但表達的意思是一致的。它們分別是標籤名(tag)、屬性(props)和子元素對象(children)。下面是一個典型的VD對象例子:react
{ tag: "div", props: {}, children: [ "Hello World", { tag: "ul", props: {}, children: [{ tag: "li", props: { id: 1, class: "li-1" }, children: ["第", 1] }] } ] }
VD跟dom對象有一一對應的關係,上面的VD是由如下的HTML生成的git
<div> Hello World <ul> <li id="1" class="li-1"> 第1 </li> </ul> </div>
一個dom對象,好比li
,由tag(li)
, props({id: 1, class: "li-1"})
和children(["第", 1])
三個屬性來描述。github
VD 最大的特色是將頁面的狀態抽象爲 JS 對象的形式,配合不一樣的渲染工具,使跨平臺渲染成爲可能。如 React 就藉助 VD 實現了服務端渲染、瀏覽器渲染和移動端渲染等功能。算法
此外,在進行頁面更新的時候,藉助VD,DOM 元素的改變能夠在內存中進行比較,再結合框架的事務機制將屢次比較的結果合併後一次性更新到頁面,從而有效地減小頁面渲染的次數,提升渲染效率。咱們先來看下頁面的更新通常會通過幾個階段。
segmentfault
從上面的例子中,能夠看出頁面的呈現會分如下3個階段:數組
這個例子裏面,JS計算用了691
毫秒,生成渲染樹578
毫秒,繪製73
毫秒。若是能有效的減小生成渲染樹和繪製所花的時間,更新頁面的效率也會隨之提升。
經過VD的比較,咱們能夠將多個操做合併成一個批量的操做,從而減小dom重排的次數,進而縮短了生成渲染樹和繪製所花的時間。至於如何基於VD更有效率的更新dom,是一個頗有趣的話題,往後有機會將另寫一篇文章介紹。瀏覽器
咱們先從如何生成VD提及。藉助JSX編譯器,能夠將文件中的HTML轉化成函數的形式,而後再利用這個函數生成VD。看下面這個例子:
function render() { return ( <div> Hello World <ul> <li id="1" class="li-1"> 第1 </li> </ul> </div> ); }
這個函數通過JSX編譯後,會輸出下面的內容:
function render() { return h( 'div', null, 'Hello World', h( 'ul', null, h( 'li', { id: '1', 'class': 'li-1' }, '\u7B2C1' ) ) ); }
這裏的h是一個函數,能夠起任意的名字。這個名字經過babel進行配置:
// .babelrc文件 { "plugins": [ ["transform-react-jsx", { "pragma": "h" // 這裏可配置任意的名稱 }] ] }
接下來,咱們只須要定義h函數,就能構造出VD
function flatten(arr) { return [].concat.apply([], arr); } function h(tag, props, ...children) { return { tag, props: props || {}, children: flatten(children) || [] }; }
h函數會傳入三個或以上的參數,前兩個參數一個是標籤名,一個是屬性對象,從第三個參數開始的其它參數都是children。children元素有多是數組的形式,須要將數組解構一層。好比:
function render() { return ( <ul> <li>0</li> { [1, 2, 3].map( i => ( <li>{i}</li> )) } </ul> ); } // JSX編譯後 function render() { return h( 'ul', null, h( 'li', null, '0' ), /* * 須要將下面這個數組解構出來再放到children數組中 */ [1, 2, 3].map(i => h( 'li', null, i )) ); }
繼續以前的例子。執行h函數後,最終會獲得以下的VD對象:
{ tag: "div", props: {}, children: [ "Hello World", { tag: "ul", props: {}, children: [{ tag: "li", props: { id: 1, class: "li-1" }, children: ["第", 1] }] } ] }
下一步,經過遍歷VD對象,生成真實的dom
// 建立dom元素 function createElement(vdom) { // 若是vdom是字符串或者數字類型,則建立文本節點,好比「Hello World」 if (typeof vdom === 'string' || typeof vdom === 'number') { return doc.createTextNode(vdom); } const {tag, props, children} = vdom; // 1. 建立元素 const element = doc.createElement(tag); // 2. 屬性賦值 setProps(element, props); // 3. 建立子元素 // appendChild在執行的時候,會檢查當前的this是否是dom對象,所以要bind一下 children.map(createElement) .forEach(element.appendChild.bind(element)); return element; } // 屬性賦值 function setProps(element, props) { for (let key in props) { element.setAttribute(key, props[key]); } }
createElement
函數執行完後,dom元素就建立完並展現到頁面上了(頁面比較醜,不要介意...)。
本文介紹了VD的基本概念,並講解了如何利用JSX編譯HTML標籤,而後生成VD,進而建立真實dom的過程。下一篇文章將會實現一個簡單的VD Diff算法,找出2個VD的差別並將更新的元素映射到dom中去:你不知道的Virtual DOM(二):Virtual Dom的更新
P.S.: 想看完整代碼見這裏,若是有必要建一個倉庫的話請留言給我:代碼