簡(jiǎn)易實(shí)現(xiàn)vue diff算法

本文主要介紹了vue的diff的簡(jiǎn)易實(shí)現(xiàn)過(guò)程丐怯,也就是兩個(gè)虛擬父節(jié)點(diǎn)都是同層級(jí)的喷好,且都不包含key屬性,當(dāng)然文章后面也會(huì)介紹帶有key屬性時(shí)读跷,diff的計(jì)算過(guò)程梗搅。
如果對(duì)簡(jiǎn)易實(shí)現(xiàn)代碼的h函數(shù)以及mount函數(shù)不懂的,可以去看我的另一篇文章:http://www.reibang.com/p/0cfca7d005cf

vue的diff的簡(jiǎn)易實(shí)現(xiàn)過(guò)程(patch函數(shù)):

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .clickDiv {
      width: 20px;
      height: 20px;
      border: 1px solid #000;
    }
  </style>
</head>
<body>

  <div id="app"></div>

  <script src="./render.js"></script>
  <script>
    let counter = 1
    // compiler編譯template后的結(jié)果
    const vnode = h("div", {class: 'black'}, [
      h("button", {onclick: function() {counter++; console.log(counter)}} , '+1'),
      h("h2", null, counter)
    ])

    mount(vnode, document.querySelector('#app'))

    setTimeout(() => {
      const vnode1 = h("div", {class: 'black'}, [
        h("div", {onclick: function() {counter--; console.log(counter)}, class: 'clickDiv'} , '-1'),
        h("h2", null, '呵呵呵'),
      ])
      patch(vnode, vnode1)
    }, 2000)
  </script>
</body>
</html>
// h函數(shù)的作用就是將compiler編譯后的模板轉(zhuǎn)為vnode(也就是js對(duì)象)
function h(tag, property, children) {
  return {
    tag,
    property,
    children
  }
}
// 虛擬DOM轉(zhuǎn)為真實(shí)DOM
function mount(vnode, container) {
  // 1. 將tag轉(zhuǎn)為標(biāo)簽
  const el = vnode.el = document.createElement(vnode.tag)
  // 2. 給標(biāo)簽設(shè)置對(duì)應(yīng)的屬性
  if (vnode.property) {
    for (const key in vnode.property) {
      const value = vnode.property[key]
      // 點(diǎn)擊事件
      if (key.startsWith("on")) {
        el.addEventListener(key.slice(2), value)
        console.log(el.click)
      } else {
        el.setAttribute(key, value)
      }
    }
  }
  // 3. 處理children
  if (vnode.children) {
    if (typeof vnode.children === 'string' || typeof vnode.children === 'number') {
      el.textContent = vnode.children
    } else {
      vnode.children.forEach(item => {
        mount(item, el)
      });
    }
  }
  // 4. 將節(jié)點(diǎn)掛載到父節(jié)點(diǎn)上
  container.appendChild(el)
}

// diff算法(最簡(jiǎn)易實(shí)現(xiàn)效览,同層級(jí)无切,不包含key屬性的節(jié)點(diǎn))
// vnode1是oldVNode, vnode2是newVNode
const patch = (vnode1, vnode2) => {
  // 判斷是否是同一種標(biāo)簽
  if (vnode1.tag !== vnode2.tag) {
    // 移除oldVNode,添加newVNode
    const elParentEl = vnode1.el.parentElement
    elParentEl.removeChild(vnode1.el)
    mount(vnode2, elParentEl)
  } else {
    // el是引用钦铺,在修改el時(shí),同時(shí)修改oldVNode,newVNode(目的是:在oldVNode上直接實(shí)現(xiàn)DOM更新)
    // el就是最終需要的結(jié)果
    const el = vnode2.el = vnode1.el

    // 處理newVNode property(標(biāo)簽的屬性)订雾,給el添加newVNode的屬性
    for (const key in vnode2.property) {
      const newValue = vnode2.property[key]
      const oldValue = vnode1.property[key]
      if (newValue !== oldValue) {
        // 對(duì)事件屬性做單獨(dú)處理
        if (key.startsWith("on")) {
          el.addEventListener(key.slice(2), vnode2.property[key])
        } else {
          el.setAttribute(key, newValue)
        }
      }
    }
    // 處理oldVNode property(標(biāo)簽的屬性),給el移除oldVNode的屬性
    for (const key in vnode1.property) {
      // 對(duì)事件屬性做單獨(dú)處理
      if (key.startsWith("on")) {
          el.removeEventListener(key.slice(2), vnode2.property[key])
        } 
      if (!(key in vnode2.property)) {
          el.removeAttribute(key)
      }
    }

    // 處理children
    // 如果newVNode的children是string或者number類(lèi)型
    if (typeof vnode2.children === 'string' || typeof vnode1.children === 'number') {
      el.innerHTML = vnode2.children
    } else {
      // 如果newVNode的children是array類(lèi)型
      /**
       * 這兒就實(shí)現(xiàn)一種最最簡(jiǎn)單的情況:
       * vnode不帶有key屬性
       */
      // newVNode.length = oldVNode.length
      // 對(duì)子節(jié)點(diǎn)進(jìn)行diff
      const commonLength = Math.min(vnode1.children.length, vnode2.children.length)
      for (let i = 0; i < commonLength; i++) {
        patch(vnode1.children[i], vnode2.children[i])
      }
      // newVNode.length > oldVNode.length
      // 將newVNode多出來(lái)的節(jié)點(diǎn)掛載到el上
      if (vnode2.children.length > vnode1.children.length) {
        const newChildren = vnode2.children.slice(commonLength)
        newChildren.forEach(item => {
          mount(item, el)
        })
      }
      // newVNode.length < oldVNode.length
        // 將oldVNode多出來(lái)的節(jié)點(diǎn)從el上移除
      if (vnode2.children.length < vnode1.children.length) {
        const oldChildren = vnode1.children.slice(commonLength)
        oldChildren.forEach(item => {
          el.remove(item.el)
        })
      }
    }
  }
}

當(dāng)節(jié)點(diǎn)帶有key屬性時(shí)的diff:

新的VNodes和舊的VNodes對(duì)比使用diff算法:diff算法中有一個(gè)patch函數(shù)矛洞,用來(lái)對(duì)比新舊VNode洼哎,比較只會(huì)在同層級(jí)進(jìn)行, 不會(huì)跨層級(jí)比較讶请。
A B C D 新
A B F C D 舊
對(duì)比過(guò)程:
先對(duì)新舊VNode長(zhǎng)度進(jìn)行比較备徐,選擇較短的VNode進(jìn)行遍歷(while)桩砰,在遍歷過(guò)程中祭衩,先正序遍歷负懦,對(duì)相同的節(jié)點(diǎn)(patchFlag標(biāo)記(在編譯時(shí)加上標(biāo)記)和有key的情況下)進(jìn)行比較登疗,然后決定哪些內(nèi)容進(jìn)行替換蹂安,新增凳干,刪除等辫红; 當(dāng)遇到節(jié)點(diǎn)不同時(shí)(比如C F)凭涂,break跳出循環(huán);再進(jìn)行倒序遍歷贴妻,內(nèi)容同正序一樣切油。然后,如果是舊節(jié)點(diǎn)多出了VNode就進(jìn)行unmount(刪除)操作名惩,如果是新Vnode多出了就進(jìn)行mount(新增掛載)操作澎胡。如果中間是亂序,則盡可能地在舊的VNode中找到對(duì)應(yīng)的新的VNode娩鹉,再建立一個(gè)數(shù)組攻谁,然后將舊的Vnode放在與新的Vnode對(duì)應(yīng)的位置上。然后舊的Vnode多余的就進(jìn)行unmount操作弯予,新的VNode多余的就進(jìn)行mount操作戚宦。
在編譯時(shí),會(huì)對(duì)節(jié)點(diǎn)進(jìn)行加上標(biāo)記锈嫩,對(duì)于靜態(tài)節(jié)點(diǎn)(簡(jiǎn)單來(lái)說(shuō)阁苞,就是沒(méi)有變量困檩,不會(huì)動(dòng)態(tài)變化的節(jié)點(diǎn))會(huì)直接跳過(guò)。

如有錯(cuò)誤那槽,歡迎指正悼沿!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骚灸,隨后出現(xiàn)的幾起案子糟趾,更是在濱河造成了極大的恐慌,老刑警劉巖甚牲,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件义郑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丈钙,警方通過(guò)查閱死者的電腦和手機(jī)非驮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雏赦,“玉大人劫笙,你說(shuō)我怎么就攤上這事⌒歉冢” “怎么了填大?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)俏橘。 經(jīng)常有香客問(wèn)我允华,道長(zhǎng),這世上最難降的妖魔是什么寥掐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任靴寂,我火速辦了婚禮,結(jié)果婚禮上召耘,老公的妹妹穿的比我還像新娘百炬。我一直安慰自己,他們只是感情好怎茫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著妓灌,像睡著了一般轨蛤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虫埂,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天祥山,我揣著相機(jī)與錄音,去河邊找鬼掉伏。 笑死缝呕,一個(gè)胖子當(dāng)著我的面吹牛澳窑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播供常,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摊聋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了栈暇?” 一聲冷哼從身側(cè)響起麻裁,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎源祈,沒(méi)想到半個(gè)月后煎源,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡香缺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年手销,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片图张。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锋拖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出埂淮,到底是詐尸還是另有隱情姑隅,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布倔撞,位于F島的核電站讲仰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痪蝇。R本人自食惡果不足惜鄙陡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躏啰。 院中可真熱鬧趁矾,春花似錦、人聲如沸给僵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)帝际。三九已至蔓同,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹲诀,已是汗流浹背斑粱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脯爪,地道東北人则北。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓矿微,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親尚揣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涌矢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容