虛擬DOM

本文原連接:https://blog.csdn.net/qq_33050575/article/details/82885788css

http://www.javashuo.com/article/p-ybghuplm-k.htmlhtml

虛擬DOM解析及其在框架裏的應用
瀏覽器是怎樣解析HTML而且繪出整個頁面的前端


上圖爲webkit引擎瀏覽器的處理流程,如上圖大體分爲4大步:vue

第一步,HTML解析器分析html,構建一顆DOM樹;react

第二步,CSS解析器會分析外聯的css文件和內聯的一些樣式,建立一個頁面的樣式表;jquery

第三步,將DOM樹和樣式表關聯起來,建立一顆Render樹。這一過程又被稱爲Attachment,每一個DOM節點上都有一個attach方法,會接收對應的樣式表,返回一個render對象。這些render對象最終會結合成一個render tree;web

第四步,有了render tree後,瀏覽器就能夠爲render tree上的每一個節點在屏幕上分配一個精確的位置座標,而後各個節點會調用自身的paint方法結合座標信息,在瀏覽器中繪製中整個頁面了。算法

迴流(reflow)和重繪
迴流和重繪都是瀏覽器自身的行爲api

迴流:當render tree中的一部分(或所有)由於元素的規模尺寸,佈局,隱藏等改變而須要從新構建。這就稱爲迴流(reflow)。每一個頁面至少須要一次迴流,就是在頁面第一次加載的時候。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造這部分渲染樹,完成迴流後,瀏覽器會從新繪製受影響的部分到屏幕中,該過程成爲重繪。瀏覽器

重繪:當render tree中的一些元素須要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響佈局的,好比background-color。則就叫稱爲重繪。

減小回流的辦法(不是此次討論重點):好比cssText、避免使用會強制reflush隊列的屬性等等

手動操做DOM帶來的性能憂患
在使用原生的js api或者jquery等一些方法直接去操做dom的時候,可能會引發頁面的reflow,而頁面的迴流所帶來的代價是很是昂貴的。頻繁的去操做dom,會引發頁面的卡頓,影響用戶的體驗。
這裏打印一個最簡單的真實DOM節點裏面的屬性

虛擬DOM就是爲了解決頻繁操做DOM的性能問題創造出來的。
例如:
若是使用原生api去操做一個會致使迴流的DOM操做10次,那麼瀏覽器會每次都會從新走一次上面的全流程,包括一些沒變化的位置計算。
而虛擬DOM不會當即去操做DOM,而是將這10次更新的diff內容保存到本地的一個js對象中,最終將這個js對象一次性attach到DOM樹上,通知瀏覽器去執行繪製工做,這樣能夠避免大量的無謂的計算量。

virtual dom 是什麼
這是一段真實的DOM tree結構

<div id="container">
<p>Real DOM</p>
<ul>
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
</div>

若是用js對象來模擬上述的DOM Tree

let virtualDomTree = CreateElement('div', { id: 'container' }, [
CreateElement('p', {}, ['Virtual DOM']),
CreateElement('ul', {}, [
CreateElement('li', { class: 'item' }, ['Item 1']),
CreateElement('li', { class: 'item' }, ['Item 2']),
CreateElement('li', { class: 'item' }, ['Item 3']),
]),
]);

let root = virtualDomTree.render(); //轉換爲一個真正的dom結構或者dom fragment
document.getElementById('virtualDom').appendChild(root);

這樣的好處,避免了因直接去修改真實的dom而帶來的性能隱患。能夠先把頁面的一些改動反應到這個虛擬dom對象上,等更新完後再一次統一去把變化同步到真實的dom中。

下面是CreateElement的實現方法

function CreateElement(tagName, props, children) {
if (!(this instanceof CreateElement)) {
return new CreateElement(tagName, props, children);
}

this.tagName = tagName;
this.props = props || {};
this.children = children || [];
this.key = props ? props.key : undefined;
}
render方法

CreateElement.prototype.render = function() {
let el = document.createElement(this.tagName);
let props = this.props;

for (let propName in props) {
setAttr(el, propName, props[propName]);
}

this.children.forEach((child) => {
let childEl = (child instanceof Element) ? child.render() : document.createTextNode(child);
el.appendChild(childEl);
});

return el;
};
直到如今,已經能夠在頁面中建立一個真實的DOM結構了。

diff算法
上面已經完成了虛擬DOM -> 真實DOM的一個轉換工做,如今只須要把頁面上全部的改動都更新到虛擬DOM上。這就是一個diff過程。

兩棵樹若是徹底比較時間複雜度是O(n^3),但參照《深刻淺出React和Redux》一書中的介紹,React的Diff算法的時間複雜度是O(n)。要實現這麼低的時間複雜度,意味着只能平層地比較兩棵樹的節點,放棄了深度遍歷。這樣作,彷佛犧牲了必定的精確性來換取速度,但考慮到現實中前端頁面一般也不會跨層級移動DOM元素,因此這樣作是最優的。

只考慮相同等級diff,能夠分爲下面4中狀況:

第一種。若是節點類型變了,好比下面的p標籤變成了h3標籤,則直接卸載舊節點裝載新節點,這個過程稱爲REPLACE。


第二種狀況。節點類型同樣,僅僅是屬性變化了,這一過程叫PROPS。好比

renderA: <ul>
renderB: <ul class: 'marginLeft10'>
=> [addAttribute class "marginLeft10"]
這一過程只會執行節點的更新操做,不會觸發節點的卸載和裝載操做。

第三種。只是文本變化了,TEXT過程。該過程只會替換文本。

第四種。節點發生了移動,增長,或者刪除操做。該過程稱爲REOREDR。虛擬DOM Diff算法解析

若是在一些節點中間插入一個F節點,簡單粗暴的作法是:卸載C,裝載F,卸載D,裝載C,卸載E,裝載D,裝載E。以下圖:


這種方法顯然是不高效的。

而若是給每一個節點惟一的標識(key),那麼就能找到正確的位置去插入新的節點。


這也就是爲何vue/react等框架,會要求咱們在寫循環遍歷結構的時候要寫key值

在vue2.0中是如何使用虛擬dom來綁定和渲染模板的


監聽數據的變化
vue內部是經過數據劫持的方式來作到數據綁定的,其中最核心的方法就是經過Object.defineProperty()的getter和setter來實現對數據劫持,達到監聽數據變更的目的。

vue中虛擬DOM生成DOM的過程


DocumentFragment和vue異步更新隊列
DocumentFragment是允許把一些DOM操做先應用到一個dom片斷裏,而後再將這個片斷append到DOM樹裏,從而來減小頁面的reflow次數。

var fragment = document.createDocumentFragment();

//add DOM to fragment

for(var i = 0; i < 10; i++) {
var spanNode = document.createElement("span");
spanNode.innerHTML = "number:" + i;
fragment.appendChild(spanNode);
}

//add this DOM to body
document.body.appendChild(spanNode);

vue裏面的一些dom的一些變化,都是在DocumentFragment容器中去操做的,最後將這個更新片斷append到el的根dom中。

走到這裏,你會發現還有一個問題。就算vue使用了虛擬dom,將一些改動先同步到虛擬對象上,而後去改動真實DOM。這其中,去改動真實DOM仍是使用了原生的api去操做DOM,仍是會不可避免的去reflow整個頁面,若是不能把這些更新操做打包起來集中去更新真實DOM,那其實徹底散失了虛擬DOM的做用性,反而變得更加冗餘。

這時候框架的價值就體現出來了,vue中,若是在同一次事件循環中若是觀察到有多個數據變化,vue會開啓一個異步更新隊列,並緩衝在同一事件循環中發生的全部數據改變。而後在下一個的事件循環‘tick’中,vue刷新隊列並執行實際工做。這樣就能夠批量的去更新屢次數據變化到虛擬dom對象中,diff差別,同步到頁面中的真實dom裏。

相關文章
相關標籤/搜索