<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          全面理解虛擬DOM,實現虛擬DOM

          時間:?2017-12-20閱讀:?5453標簽:?dom

          最近一兩年前端最火的技術莫過于ReactJS,即便你沒用過也該聽過,ReactJS由業界頂尖的互聯網公司facebook提出,其本身有很多先進的設計思路,比如頁面UI組件化、虛擬DOM等。本文將帶你解開虛擬DOM的神秘面紗,不僅要理解其原理,而且要實現一個基本可用的虛擬DOM。


          1.為什么需要虛擬DOM

          DOM是很慢的,其元素非常龐大,頁面的性能問題鮮有由JS引起的,大部分都是由DOM操作引起的。如果對前端工作進行抽象的話,主要就是維護狀態和更新視圖;而更新視圖和維護狀態都需要DOM操作。其實近年來,前端的框架主要發展方向就是解放DOM操作的復雜性。

          在jQuery出現以前,我們直接操作DOM結構,這種方法復雜度高,兼容性也較差;有了jQuery強大的選擇器以及高度封裝的API,我們可以更方便的操作DOM,jQuery幫我們處理兼容性問題,同時也使DOM操作變得簡單;但是聰明的程序員不可能滿足于此,各種MVVM框架應運而生,有angularJS、avalon、vue.js等,MVVM使用數據雙向綁定,使得我們完全不需要操作DOM了,更新了狀態視圖會自動更新,更新了視圖數據狀態也會自動更新,可以說MMVM使得前端的開發效率大幅提升,但是其大量的事件綁定使得其在復雜場景下的執行性能堪憂;有沒有一種兼顧開發效率和執行效率的方案呢?ReactJS就是一種不錯的方案,雖然其將JS代碼和HTML代碼混合在一起的設計有不少爭議,但是其引入的Virtual DOM(虛擬DOM)卻是得到大家的一致認同的。


          2.理解虛擬DOM

          虛擬的DOM的核心思想是:對復雜的文檔DOM結構,提供一種方便的工具,進行最小化地DOM操作。這句話,也許過于抽象,卻基本概況了虛擬DOM的設計思想

          (1) 提供一種方便的工具,使得開發效率得到保證
          (2) 保證最小化的DOM操作,使得執行效率得到保證
          

          (1).用JS表示DOM結構

          DOM很慢,而javascript很快,用javascript對象可以很容易地表示DOM節點。DOM節點包括標簽、屬性和子節點,通過VElement表示如下。

          //虛擬dom,參數分別為標簽名、屬性對象、子DOM列表
          var VElement = function(tagName, props, children) {
              //保證只能通過如下方式調用:new VElement
              if (!(this instanceof VElement)) {
                  return new VElement(tagName, props, children);
              }
          
              //可以通過只傳遞tagName和children參數
              if (util.isArray(props)) {
                  children = props;
                  props = {};
              }
          
              //設置虛擬dom的相關屬性
              this.tagName = tagName;
              this.props = props || {};
              this.children = children || [];
              this.key = props ? props.key : void 666;
              var count = 0;
              util.each(this.children, function(child, i) {
                  if (child instanceof VElement) {
                      count += child.count;
                  } else {
                      children[i] = '' + child;
                  }
                  count++;
              });
              this.count = count;
          }
          
          

          通過VElement,我們可以很簡單地用javascript表示DOM結構。比如

          var vdom = velement('div', { 'id': 'container' }, [
              velement('h1', { style: 'color:red' }, ['simple virtual dom']),
              velement('p', ['hello world']),
              velement('ul', [velement('li', ['item #1']), velement('li', ['item #2'])]),
          ]);
          

          上面的javascript代碼可以表示如下DOM結構:

          <div id="container">
              <h1 style="color:red">simple virtual dom</h1>
              <p>hello world</p>
              <ul>
                  <li>item #1</li>
                  <li>item #2</li>
              </ul>   
          </div>
          

          同樣我們可以很方便地根據虛擬DOM樹構建出真實的DOM樹。具體思路:根據虛擬DOM節點的屬性和子節點遞歸地構建出真實的DOM樹。見如下代碼:

          VElement.prototype.render = function() {
              //創建標簽
              var el = document.createElement(this.tagName);
              //設置標簽的屬性
              var props = this.props;
              for (var propName in props) {
                  var propValue = props[propName]
                  util.setAttr(el, propName, propValue);
              }
          
              //依次創建子節點的標簽
              util.each(this.children, function(child) {
                  //如果子節點仍然為velement,則遞歸的創建子節點,否則直接創建文本類型節點
                  var childEl = (child instanceof VElement) ? child.render() : document.createTextNode(child);
                  el.appendChild(childEl);
              });
          
              return el;
          }
          

          對一個虛擬的DOM對象VElement,調用其原型的render方法,就可以產生一顆真實的DOM樹。

              vdom.render();
          

          既然我們可以用JS對象表示DOM結構,那么當數據狀態發生變化而需要改變DOM結構時,我們先通過JS對象表示的虛擬DOM計算出實際DOM需要做的最小變動,然后再操作實際DOM,從而避免了粗放式的DOM操作帶來的性能問題。

          (2).比較兩棵虛擬DOM樹的差異

          在用JS對象表示DOM結構后,當頁面狀態發生變化而需要操作DOM時,我們可以先通過虛擬DOM計算出對真實DOM的最小修改量,然后再修改真實DOM結構(因為真實DOM的操作代價太大)。

          如下圖所示,兩個虛擬DOM之間的差異已經標紅:


          為了便于說明問題,我當然選取了最簡單的DOM結構,兩個簡單DOM之間的差異似乎是顯而易見的,但是真實場景下的DOM結構很復雜,我們必須借助于一個有效的DOM樹比較算法。

          設計一個diff算法有兩個要點:

          如何比較兩個兩棵DOM樹
          如何記錄節點之間的差異
          

          <1> 如何比較兩個兩棵DOM樹

          計算兩棵樹之間差異的常規算法復雜度為O(n3),一個文檔的DOM結構有上百個節點是很正常的情況,這種復雜度無法應用于實際項目。針對前端的具體情況:我們很少跨級別的修改DOM節點,通常是修改節點的屬性、調整子節點的順序、添加子節點等。因此,我們只需要對同級別節點進行比較,避免了diff算法的復雜性。對同級別節點進行比較的常用方法是深度優先遍歷:

          function diff(oldTree, newTree) {
              //節點的遍歷順序
              var index = 0; 
              //在遍歷過程中記錄節點的差異
              var patches = {}; 
              //深度優先遍歷兩棵樹
              dfsWalk(oldTree, newTree, index, patches); 
              return patches; 
          }
          

          <2>如何記錄節點之間的差異

          由于我們對DOM樹采取的是同級比較,因此節點之間的差異可以歸結為4種類型:

          修改節點屬性, 用PROPS表示
          修改節點文本內容, 用TEXT表示
          替換原有節點, 用REPLACE表示
          調整子節點,包括移動、刪除等,用REORDER表示
          

          對于節點之間的差異,我們可以很方便地使用上述四種方式進行記錄,比如當舊節點被替換時:

          {type:REPLACE,node:newNode}
          

          而當舊節點的屬性被修改時:

          {type:PROPS,props: newProps}
          

          在深度優先遍歷的過程中,每個節點都有一個編號,如果對應的節點有變化,只需要把相應變化的類別記錄下來即可。下面是具體實現:

          function dfsWalk(oldNode, newNode, index, patches) {
              var currentPatch = [];
              if (newNode === null) {
                  //依賴listdiff算法進行標記為刪除
              } else if (util.isString(oldNode) && util.isString(newNode)) {
                  if (oldNode !== newNode) {
                      //如果是文本節點則直接替換文本
                      currentPatch.push({
                          type: patch.TEXT,
                          content: newNode
                      });
                  }
              } else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
                  //節點類型相同
                  //比較節點的屬性是否相同
                  var propsPatches = diffProps(oldNode, newNode);
                  if (propsPatches) {
                      currentPatch.push({
                          type: patch.PROPS,
                          props: propsPatches
                      });
                  }
                  //比較子節點是否相同
                  diffChildren(oldNode.children, newNode.children, index, patches, currentPatch);
              } else {
                  //節點的類型不同,直接替換
                  currentPatch.push({ type: patch.REPLACE, node: newNode });
              }
          
              if (currentPatch.length) {
                  patches[index] = currentPatch;
              }
          }
          

          比如對上文圖中的兩顆虛擬DOM樹,可以用如下數據結構記錄它們之間的變化:

          var patches = {
                  1:{type:REPLACE,node:newNode}, //h1節點變成h5
                  5:{type:REORDER,moves:changObj} //ul新增了子節點li
              }
          

          (3).對真實DOM進行最小化修改

          通過虛擬DOM計算出兩顆真實DOM樹之間的差異后,我們就可以修改真實的DOM結構了。上文深度優先遍歷過程產生了用于記錄兩棵樹之間差異的數據結構patches, 通過使用patches我們可以方便對真實DOM做最小化的修改。

          //將差異應用到真實DOM
          function applyPatches(node, currentPatches) {
              util.each(currentPatches, function(currentPatch) {
                  switch (currentPatch.type) {
                      //當修改類型為REPLACE時
                      case REPLACE:
                          var newNode = (typeof currentPatch.node === 'String')
                           ? document.createTextNode(currentPatch.node) 
                           : currentPatch.node.render();
                          node.parentNode.replaceChild(newNode, node);
                          break;
                      //當修改類型為REORDER時
                      case REORDER:
                          reoderChildren(node, currentPatch.moves);
                          break;
                      //當修改類型為PROPS時
                      case PROPS:
                          setProps(node, currentPatch.props);
                          break;
                      //當修改類型為TEXT時
                      case TEXT:
                          if (node.textContent) {
                              node.textContent = currentPatch.content;
                          } else {
                              node.nodeValue = currentPatch.content;
                          }
                          break;
                      default:
                          throw new Error('Unknow patch type ' + currentPatch.type);
                  }
              });
          }
          

          至此,虛擬DOM的基本原理已經基本講解完成了;我們也一起實現了一個基本可用的虛擬DOM。本文中只給出了關鍵的源代碼,全部源代碼請參考我的github。本文同時發表在我的博客積木村の研究所 

          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/265

          從 React 歷史的長河來聊如何理解虛擬DOM

          最近我發現很多面試題里面都有「如何理解虛擬 DOM」這個題,我覺得這個題應該沒有想象中那么好答,因為很多人沒有真正理解虛擬 DOM 它的價值所在,我這篇從虛擬 DOM 的誕生過程來引出它的價值以及歷史地位,幫助你深入的理解它。

          什么是DOM及DOM操作?

          DOM(文檔對象模型)是針對于xml但是擴展用于HTML的應用程序編程接口,定義了訪問和操作HTML的文檔的標準。W3C文檔對象模型是中立于平臺和語言之間的接口

          13個需要掌握的Js操作DOM方法

          DOM 或文檔對象模型是 web 頁面上所有對象的根。它表示文檔的結構,并將頁面連接到編程語言。它的結構是一個邏輯樹。每個分支結束于一個節點,每個節點包含子節點、對象。DOM API非常龐大

          DOM 高級工程師不完全指南

          前端框架真的太香了,香到我都不敢徒手撕 DOM 了!雖然絕大多數前端er都有這樣的困擾,但本著基礎為大的原則,手撕 DOM 應當是一個前端攻城獅的必備技能,這正是本文誕生的初衷

          javascript中DOM是什么?

          DOM(document object model)文檔對象模型,是一項W3C 標準,是針對HTML和XML的一個API(應用程序接口)。它允許程序和腳本動態地訪問、更新文檔的內容、結構和樣式。

          通過編寫簡易版本的虛擬DOM,來理解虛擬DOM 的原理

          要構建自己的虛擬DOM,需要知道兩件事。你甚至不需要深入 React 的源代碼或者深入任何其他虛擬DOM實現的源代碼,因為它們是如此龐大和復雜

          JavaScrip之DOM

          HTML 文檔的骨干是標簽。根據文檔對象模型(DOM),每個HTML標簽都是一個對象,同樣標簽內的文本也是一個對象。因此這些對象都可通過 JavaScript 操作,如果文檔中有空格(就像任何字符一樣)

          Vue 數據更新后調用nextTick更新DOM

          如果你想基于更新后的 DOM 狀態來做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員使用“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。為了在數據變化之后等待 Vue 完成更新 DOM

          你知道虛擬Dom是怎么生成的嗎?

          在經過初始化階段之后,即將開始組件的掛載,不過在掛載之前很有必要提一下虛擬Dom的概念。這個想必大家有所耳聞,我們知道vue@2.0開始引入了虛擬Dom,主要解決的問題是

          一段監視 DOM 的神奇代碼

          通過使用此模塊,只需將鼠標懸停在瀏覽器中,即可快速查看DOM元素的屬性。基本上它是一個即時檢查器。將鼠標懸停在 DOM 元素上會顯示其屬性!

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网