成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

vue2源碼學(xué)習(xí)開(kāi)胃菜——snabbdom源碼學(xué)習(xí)(二)

BetaRabbit / 1145人閱讀

摘要:前言在上一章我們學(xué)習(xí)了,等模塊,在這一篇我們將會(huì)學(xué)習(xí)到的核心功能和功能。如果父節(jié)點(diǎn)沒(méi)變化,我們就比較所有同層的子節(jié)點(diǎn),對(duì)這些子節(jié)點(diǎn)進(jìn)行刪除創(chuàng)建移位操作。只需要對(duì)兩個(gè)進(jìn)行判斷是否相似,如果相似,則對(duì)他們進(jìn)行操作,否則直接用替換。

前言

在上一章我們學(xué)習(xí)了,modules,vnode,h,htmldomapi,is等模塊,在這一篇我們將會(huì)學(xué)習(xí)到
snabbdom的核心功能——patchVnode和updateChildren功能。

繼續(xù)我們的snabbdom源碼之旅 最終章 snabbdom!

首先我們先從簡(jiǎn)單的部分開(kāi)始,比如一些工具函數(shù),我將逐個(gè)來(lái)講解他們的用處

sameNode

這個(gè)函數(shù)主要用于比較oldvnode與vnode同層次節(jié)點(diǎn)的比較,如果同層次節(jié)點(diǎn)的key和sel都相同
我們就可以保留這個(gè)節(jié)點(diǎn),否則直接替換節(jié)點(diǎn)

function sameVnode(vnode1, vnode2) {
  return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}
createKeyToOldIdx

這個(gè)函數(shù)的功能十分簡(jiǎn)單,就是將oldvnode數(shù)組中位置對(duì)oldvnode.key的映射轉(zhuǎn)換為oldvnode.key
對(duì)位置的映射

function createKeyToOldIdx(children, beginIdx, endIdx) {
  var i, map = {}, key;
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key;
    if (isDef(key)) map[key] = i;
  }
  return map;
}
hook

snabbdom在全局下有6種類(lèi)型的鉤子,觸發(fā)這些鉤子時(shí),會(huì)調(diào)用對(duì)應(yīng)的函數(shù)對(duì)節(jié)點(diǎn)的狀態(tài)進(jìn)行更改
首先我們來(lái)看看有哪些鉤子:

Name Triggered when Arguments to callback
pre the patch process begins (patch開(kāi)始時(shí)觸發(fā)) none
init a vnode has been added (vnode被創(chuàng)建時(shí)觸發(fā)) vnode
create a DOM element has been created based on a vnode (vnode轉(zhuǎn)換為真實(shí)DOM節(jié)點(diǎn)時(shí)觸發(fā) emptyVnode, vnode
insert an element has been inserted into the DOM (插入到DOM樹(shù)時(shí)觸發(fā)) vnode
prepatch an element is about to be patched (元素準(zhǔn)備patch前觸發(fā)) oldVnode, vnode
update an element is being updated (元素更新時(shí)觸發(fā)) oldVnode, vnode
postpatch an element has been patched (元素patch完觸發(fā)) oldVnode, vnode
destroy an element is directly or indirectly being removed (元素被刪除時(shí)觸發(fā)) vnode
remove an element is directly being removed from the DOM (元素從父節(jié)點(diǎn)刪除時(shí)觸發(fā),和destory略有不同,remove只影響到被移除節(jié)點(diǎn)中最頂層的節(jié)點(diǎn)) vnode, removeCallback
post the patch process is done (patch完成后觸發(fā)) none

然后,下面列出鉤子對(duì)應(yīng)的狀態(tài)更新函數(shù):

create => style,class,dataset,eventlistener,props,hero

update => style,class,dataset,eventlistener,props,hero

remove => style

destory => eventlistener,style,hero

pre => hero

post => hero

好了,簡(jiǎn)單的都看完了,接下來(lái)我們開(kāi)始打大boss了,第一關(guān)就是init函數(shù)了

init

init函數(shù)有兩個(gè)參數(shù)modules和api,其中modules是init依賴(lài)的模塊,如attribute、props
、eventlistener這些模塊,api則是對(duì)封裝真實(shí)DOM操作的工具函數(shù)庫(kù),如果我們沒(méi)有傳入,則默認(rèn)
使用snabbdom提供的htmldomapi。init還包含了許多vnode和真實(shí)DOM之間的操作和注冊(cè)全局鉤子,
還有patchVnode和updateChildren這兩個(gè)重要功能,然后返回一個(gè)patch函數(shù)

注冊(cè)全局鉤子
     //注冊(cè)鉤子的回調(diào),在發(fā)生狀態(tài)變更時(shí),觸發(fā)對(duì)應(yīng)屬性變更
      for (i = 0; i < hooks.length; ++i) {
        cbs[hooks[i]] = [];
        for (j = 0; j < modules.length; ++j) {
          if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]]);
        }
      }
emptyNodeAt

這個(gè)函數(shù)主要的功能是將一個(gè)真實(shí)DOM節(jié)點(diǎn)轉(zhuǎn)化成vnode形式,

將轉(zhuǎn)換為{sel:"div#a.b.c",data:{},children:[],text:undefined,elm:
}

     function emptyNodeAt(elm) {
        var id = elm.id ? "#" + elm.id : "";
        var c = elm.className ? "." + elm.className.split(" ").join(".") : "";
        return VNode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
      }
createRmCb

我們知道當(dāng)我們需要remove一個(gè)vnode時(shí),會(huì)觸發(fā)remove鉤子作攔截器,只有在所有remove鉤子
回調(diào)函數(shù)都觸發(fā)完才會(huì)將節(jié)點(diǎn)從父節(jié)點(diǎn)刪除,而這個(gè)函數(shù)提供的就是對(duì)remove鉤子回調(diào)操作的計(jì)數(shù)功能

function createRmCb(childElm, listeners) {
    return function() {
      if (--listeners === 0) {
        var parent = api.parentNode(childElm);
        api.removeChild(parent, childElm);
      }
    };
  }
invokeDestoryHook

這個(gè)函數(shù)用于手動(dòng)觸發(fā)destory鉤子回調(diào),主要步驟如下:

先調(diào)用vnode上的destory

再調(diào)用全局下的destory

遞歸調(diào)用子vnode的destory

function invokeDestroyHook(vnode) {
  var i, j, data = vnode.data;
  if (isDef(data)) {
//先觸發(fā)該節(jié)點(diǎn)上的destory回調(diào)
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
//在觸發(fā)全局下的destory回調(diào)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
//遞歸觸發(fā)子節(jié)點(diǎn)的destory回調(diào)
if (isDef(i = vnode.children)) {
  for (j = 0; j < vnode.children.length; ++j) {
    invokeDestroyHook(vnode.children[j]);
  }
}
  }
}

removeVnodes

這個(gè)函數(shù)主要功能是批量刪除DOM節(jié)點(diǎn),需要配合invokeDestoryHook和createRmCb服用,效果更佳
主要步驟如下:

調(diào)用invokeDestoryHook以觸發(fā)destory回調(diào)

調(diào)用createRmCb來(lái)開(kāi)始對(duì)remove回調(diào)進(jìn)行計(jì)數(shù)

刪除DOM節(jié)點(diǎn)

  /**
   *
   * @param parentElm 父節(jié)點(diǎn)
   * @param vnodes  刪除節(jié)點(diǎn)數(shù)組
   * @param startIdx  刪除起始坐標(biāo)
   * @param endIdx  刪除結(jié)束坐標(biāo)
   */
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
  for (; startIdx <= endIdx; ++startIdx) {
var i, listeners, rm, ch = vnodes[startIdx];
if (isDef(ch)) {
  if (isDef(ch.sel)) {
    //調(diào)用destroy鉤子
    invokeDestroyHook(ch);
    //對(duì)全局remove鉤子進(jìn)行計(jì)數(shù)
    listeners = cbs.remove.length + 1;
    rm = createRmCb(ch.elm, listeners);
    //調(diào)用全局remove回調(diào)函數(shù),并每次減少一個(gè)remove鉤子計(jì)數(shù)
    for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
    //調(diào)用內(nèi)部vnode.data.hook中的remove鉤子(只有一個(gè))
    if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
      i(ch, rm);
    } else {
      //如果沒(méi)有內(nèi)部remove鉤子,需要調(diào)用rm,確保能夠remove節(jié)點(diǎn)
      rm();
    }
  } else { // Text node
    api.removeChild(parentElm, ch.elm);
  }
}
  }
}

createElm

就如太極有陰就有陽(yáng)一樣,既然我們有remove操作,肯定也有createelm的操作,這個(gè)函數(shù)主要功能
如下:

初始化vnode,調(diào)用init鉤子

創(chuàng)建對(duì)應(yīng)tagname的DOM element節(jié)點(diǎn),并將vnode.sel中的id名和class名掛載上去

如果有子vnode,遞歸創(chuàng)建DOM element節(jié)點(diǎn),并添加到父vnode對(duì)應(yīng)的element節(jié)點(diǎn)上去,

否則如果有text屬性,則創(chuàng)建text節(jié)點(diǎn),并添加到父vnode對(duì)應(yīng)的element節(jié)點(diǎn)上去

vnode轉(zhuǎn)換成dom節(jié)點(diǎn)操作完成后,調(diào)用create鉤子

如果vnode上有insert鉤子,那么就將這個(gè)vnode放入insertedVnodeQueue中作記錄,到時(shí)

再在全局批量調(diào)用insert鉤子回調(diào)
function createElm(vnode, insertedVnodeQueue) {
   var i, data = vnode.data;
   if (isDef(data)) {
 //當(dāng)節(jié)點(diǎn)上存在hook而且hook中有init鉤子時(shí),先調(diào)用init回調(diào),對(duì)剛創(chuàng)建的vnode進(jìn)行處理
 if (isDef(i = data.hook) && isDef(i = i.init)) {
   i(vnode);
   //獲取init鉤子修改后的數(shù)據(jù)
   data = vnode.data;
 }
   }
   var elm, children = vnode.children, sel = vnode.sel;
   if (isDef(sel)) {
 // Parse selector
 var hashIdx = sel.indexOf("#");
 //先id后class
 var dotIdx = sel.indexOf(".", hashIdx);
 var hash = hashIdx > 0 ? hashIdx : sel.length;
 var dot = dotIdx > 0 ? dotIdx : sel.length;
 var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
 //創(chuàng)建一個(gè)DOM節(jié)點(diǎn)引用,并對(duì)其屬性實(shí)例化
 elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag): api.createElement(tag);
  //獲取id名 #a --> a
 if (hash < dot) elm.id = sel.slice(hash + 1, dot);
 //獲取類(lèi)名,并格式化  .a.b --> a b
 if (dotIdx > 0) elm.className = sel.slice(dot + 1).replace(/./g, " ");
 //如果存在子元素Vnode節(jié)點(diǎn),則遞歸將子元素節(jié)點(diǎn)插入到當(dāng)前Vnode節(jié)點(diǎn)中,并將已插入的子元素節(jié)點(diǎn)在insertedVnodeQueue中作記錄
 if (is.array(children)) {
   for (i = 0; i < children.length; ++i) {
     api.appendChild(elm, createElm(children[i], insertedVnodeQueue));
   }
   //如果存在子文本節(jié)點(diǎn),則直接將其插入到當(dāng)前Vnode節(jié)點(diǎn)
 } else if (is.primitive(vnode.text)) {
   api.appendChild(elm, api.createTextNode(vnode.text));
 }
 //當(dāng)創(chuàng)建完畢后,觸發(fā)全局create鉤子回調(diào)
 for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
 i = vnode.data.hook; // Reuse variable
 if (isDef(i)) {
   if (i.create) i.create(emptyNode, vnode);
   //如果有insert鉤子,則推進(jìn)insertedVnodeQueue中作記錄,從而實(shí)現(xiàn)批量插入觸發(fā)insert回調(diào)
   if (i.insert) insertedVnodeQueue.push(vnode);
 }
   }
   //如果沒(méi)聲明選擇器,則說(shuō)明這個(gè)是一個(gè)text節(jié)點(diǎn)
   else {
 elm = vnode.elm = api.createTextNode(vnode.text);
   }
   return vnode.elm;
 }

addVnodes

這個(gè)函數(shù)十分簡(jiǎn)單,就是將vnode轉(zhuǎn)換后的dom節(jié)點(diǎn)插入到dom樹(shù)的指定位置中去

function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
    for (; startIdx <= endIdx; ++startIdx) {
      api.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before);
    }
  }

說(shuō)完上面的節(jié)點(diǎn)工具函數(shù)之后,我們就開(kāi)始看如何進(jìn)行patch操作了,首先我們從patch,也就是init
返回的函數(shù)開(kāi)始

patch

首先我們需要明確的一個(gè)是,如果按照傳統(tǒng)的diff算法,那么為了找到最小變化,需要逐層逐層的去
搜索比較,這樣時(shí)間復(fù)雜度將會(huì)達(dá)到 O(n^3)的級(jí)別,代價(jià)十分高,考慮到節(jié)點(diǎn)變化很少是跨層次的,
vdom采取的是一種簡(jiǎn)化的思路,只比較同層節(jié)點(diǎn),如果不同,那么即使該節(jié)點(diǎn)的子節(jié)點(diǎn)沒(méi)變化,我們
也不復(fù)用,直接將從父節(jié)點(diǎn)開(kāi)始的子樹(shù)全部刪除,然后再重新創(chuàng)建節(jié)點(diǎn)添加到新的位置。如果父節(jié)點(diǎn)
沒(méi)變化,我們就比較所有同層的子節(jié)點(diǎn),對(duì)這些子節(jié)點(diǎn)進(jìn)行刪除、創(chuàng)建、移位操作。有了這個(gè)思想,
理解patch也十分簡(jiǎn)單了。patch只需要對(duì)兩個(gè)vnode進(jìn)行判斷是否相似,如果相似,則對(duì)他們進(jìn)行
patchVnode操作,否則直接用vnode替換oldvnode。

return function(oldVnode, vnode) {
    var i, elm, parent;
    //記錄被插入的vnode隊(duì)列,用于批觸發(fā)insert
    var insertedVnodeQueue = [];
    //調(diào)用全局pre鉤子
    for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
    //如果oldvnode是dom節(jié)點(diǎn),轉(zhuǎn)化為oldvnode
    if (isUndef(oldVnode.sel)) {
      oldVnode = emptyNodeAt(oldVnode);
    }
    //如果oldvnode與vnode相似,進(jìn)行更新
    if (sameVnode(oldVnode, vnode)) {
      patchVnode(oldVnode, vnode, insertedVnodeQueue);
    } else {
      //否則,將vnode插入,并將oldvnode從其父節(jié)點(diǎn)上直接刪除
      elm = oldVnode.elm;
      parent = api.parentNode(elm);

      createElm(vnode, insertedVnodeQueue);

      if (parent !== null) {
        api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
        removeVnodes(parent, [oldVnode], 0, 0);
      }
    }
    //插入完后,調(diào)用被插入的vnode的insert鉤子
    for (i = 0; i < insertedVnodeQueue.length; ++i) {
      insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
    }
    //然后調(diào)用全局下的post鉤子
    for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
    //返回vnode用作下次patch的oldvnode
    return vnode;
  };
patchVnode

真正對(duì)vnode內(nèi)部patch的還是得靠patchVnode。讓我們看看他到底做了什么?

function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
    var i, hook;
    //在patch之前,先調(diào)用vnode.data的prepatch鉤子
    if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
      i(oldVnode, vnode);
    }
    var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children;
    //如果oldvnode和vnode的引用相同,說(shuō)明沒(méi)發(fā)生任何變化直接返回,避免性能浪費(fèi)
    if (oldVnode === vnode) return;
    //如果oldvnode和vnode不同,說(shuō)明vnode有更新
    //如果vnode和oldvnode不相似則直接用vnode引用的DOM節(jié)點(diǎn)去替代oldvnode引用的舊節(jié)點(diǎn)
    if (!sameVnode(oldVnode, vnode)) {
      var parentElm = api.parentNode(oldVnode.elm);
      elm = createElm(vnode, insertedVnodeQueue);
      api.insertBefore(parentElm, elm, oldVnode.elm);
      removeVnodes(parentElm, [oldVnode], 0, 0);
      return;
    }
    //如果vnode和oldvnode相似,那么我們要對(duì)oldvnode本身進(jìn)行更新
    if (isDef(vnode.data)) {
      //首先調(diào)用全局的update鉤子,對(duì)vnode.elm本身屬性進(jìn)行更新
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
      //然后調(diào)用vnode.data里面的update鉤子,再次對(duì)vnode.elm更新
      i = vnode.data.hook;
      if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
    }
    //如果vnode不是text節(jié)點(diǎn)
    if (isUndef(vnode.text)) {
      //如果vnode和oldVnode都有子節(jié)點(diǎn)
      if (isDef(oldCh) && isDef(ch)) {
        //當(dāng)Vnode和oldvnode的子節(jié)點(diǎn)不同時(shí),調(diào)用updatechilren函數(shù),diff子節(jié)點(diǎn)
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
      }
      //如果vnode有子節(jié)點(diǎn),oldvnode沒(méi)子節(jié)點(diǎn)
      else if (isDef(ch)) {
        //oldvnode是text節(jié)點(diǎn),則將elm的text清除
        if (isDef(oldVnode.text)) api.setTextContent(elm, "");
        //并添加vnode的children
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      }
      //如果oldvnode有children,而vnode沒(méi)children,則移除elm的children
      else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      }
      //如果vnode和oldvnode都沒(méi)chidlren,且vnode沒(méi)text,則刪除oldvnode的text
      else if (isDef(oldVnode.text)) {
        api.setTextContent(elm, "");
      }
    }

    //如果oldvnode的text和vnode的text不同,則更新為vnode的text
    else if (oldVnode.text !== vnode.text) {
      api.setTextContent(elm, vnode.text);
    }
    //patch完,觸發(fā)postpatch鉤子
    if (isDef(hook) && isDef(i = hook.postpatch)) {
      i(oldVnode, vnode);
    }
  }
updateChildren

對(duì)于同層的子節(jié)點(diǎn),snabbdom主要有刪除、創(chuàng)建的操作,同時(shí)通過(guò)移位的方法,達(dá)到最大復(fù)用存在
節(jié)點(diǎn)的目的,其中需要維護(hù)四個(gè)索引,分別是:

oldStartIdx => 舊頭索引

oldEndIdx => 舊尾索引

newStartIdx => 新頭索引

newEndIdx => 新尾索引

然后開(kāi)始將舊子節(jié)點(diǎn)組和新子節(jié)點(diǎn)組進(jìn)行逐一比對(duì),直到遍歷完任一子節(jié)點(diǎn)組,比對(duì)策略有5種:

oldStartVnode和newStartVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,然后新舊頭索引都后移

oldEndVnode和newEndVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,然后新舊尾索引前移

oldStartVnode和newEndVnode進(jìn)行比對(duì),如果相似,則進(jìn)行patch,將舊節(jié)點(diǎn)移位到最后

然后舊頭索引后移,尾索引前移,為什么要這樣做呢?我們思考一種情況,如舊節(jié)點(diǎn)為【5,1,2,3,4】
,新節(jié)點(diǎn)為【1,2,3,4,5】,如果缺乏這種判斷,意味著需要先將5->1,1->2,2->3,3->4,4->5五
次刪除插入操作,即使是有了key-index來(lái)復(fù)用,也會(huì)出現(xiàn)也會(huì)出現(xiàn)【5,1,2,3,4】->
【1,5,2,3,4】->【1,2,5,3,4】->【1,2,3,5,4】->【1,2,3,4,5】共4次操作,如果
有了這種判斷,我們只需要將5插入到舊尾索引后面即可,從而實(shí)現(xiàn)右移

oldEndVnode和newStartVnode進(jìn)行比對(duì),處理和上面類(lèi)似,只不過(guò)改為左移

如果以上情況都失敗了,我們就只能復(fù)用key相同的節(jié)點(diǎn)了。首先我們要通過(guò)createKeyToOldIdx

創(chuàng)建key-index的映射,如果新節(jié)點(diǎn)在舊節(jié)點(diǎn)中不存在,我們將它插入到舊頭索引節(jié)點(diǎn)前,
然后新頭索引向后;如果新節(jié)點(diǎn)在就舊節(jié)點(diǎn)組中存在,先找到對(duì)應(yīng)的舊節(jié)點(diǎn),然后patch,并將
舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)設(shè)置為undefined,代表已經(jīng)遍歷過(guò)了,不再遍歷,否則可能存在重復(fù)
插入的問(wèn)題,最后將節(jié)點(diǎn)移位到舊頭索引節(jié)點(diǎn)之前,新頭索引向后

遍歷完之后,將剩余的新Vnode添加到最后一個(gè)新節(jié)點(diǎn)的位置后或者刪除多余的舊節(jié)點(diǎn)

/**
   *
     * @param parentElm 父節(jié)點(diǎn)
     * @param oldCh 舊節(jié)點(diǎn)數(shù)組
     * @param newCh 新節(jié)點(diǎn)數(shù)組
     * @param insertedVnodeQueue
     */
  function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {

    var oldStartIdx = 0, newStartIdx = 0;
    var oldEndIdx = oldCh.length - 1;
    var oldStartVnode = oldCh[0];
    var oldEndVnode = oldCh[oldEndIdx];
    var newEndIdx = newCh.length - 1;
    var newStartVnode = newCh[0];
    var newEndVnode = newCh[newEndIdx];
    var oldKeyToIdx, idxInOld, elmToMove, before;

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx];
      }
      //如果舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)相同,
      else if (sameVnode(oldStartVnode, newStartVnode)) {
        //對(duì)舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)進(jìn)行diff更新, 從而達(dá)到復(fù)用節(jié)點(diǎn)效果
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
        //舊頭索引向后
        oldStartVnode = oldCh[++oldStartIdx];
        //新頭索引向后
        newStartVnode = newCh[++newStartIdx];
      }
      //如果舊尾索引節(jié)點(diǎn)和新尾索引節(jié)點(diǎn)相似,可以復(fù)用
      else if (sameVnode(oldEndVnode, newEndVnode)) {
        //舊尾索引節(jié)點(diǎn)和新尾索引節(jié)點(diǎn)進(jìn)行更新
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
        //舊尾索引向前
        oldEndVnode = oldCh[--oldEndIdx];
        //新尾索引向前
        newEndVnode = newCh[--newEndIdx];
      }
        //如果舊頭索引節(jié)點(diǎn)和新頭索引節(jié)點(diǎn)相似,可以通過(guò)移動(dòng)來(lái)復(fù)用
        //如舊節(jié)點(diǎn)為【5,1,2,3,4】,新節(jié)點(diǎn)為【1,2,3,4,5】,如果缺乏這種判斷,意味著
        //那樣需要先將5->1,1->2,2->3,3->4,4->5五次刪除插入操作,即使是有了key-index來(lái)復(fù)用,
        // 也會(huì)出現(xiàn)【5,1,2,3,4】->【1,5,2,3,4】->【1,2,5,3,4】->【1,2,3,5,4】->【1,2,3,4,5】
        // 共4次操作,如果有了這種判斷,我們只需要將5插入到最后一次操作即可
      else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
        api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
        oldStartVnode = oldCh[++oldStartIdx];
        newEndVnode = newCh[--newEndIdx];
      }
      //原理與上面相同
      else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
        api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
        oldEndVnode = oldCh[--oldEndIdx];
        newStartVnode = newCh[++newStartIdx];
      }
      //如果上面的判斷都不通過(guò),我們就需要key-index表來(lái)達(dá)到最大程度復(fù)用了
      else {
        //如果不存在舊節(jié)點(diǎn)的key-index表,則創(chuàng)建
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
        //找到新節(jié)點(diǎn)在舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)的位置
        idxInOld = oldKeyToIdx[newStartVnode.key];
        //如果新節(jié)點(diǎn)在舊節(jié)點(diǎn)中不存在,我們將它插入到舊頭索引節(jié)點(diǎn)前,然后新頭索引向后
        if (isUndef(idxInOld)) { // New element
          api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
          newStartVnode = newCh[++newStartIdx];
        } else {
          //如果新節(jié)點(diǎn)在就舊節(jié)點(diǎn)組中存在,先找到對(duì)應(yīng)的舊節(jié)點(diǎn)
          elmToMove = oldCh[idxInOld];
          //先將新節(jié)點(diǎn)和對(duì)應(yīng)舊節(jié)點(diǎn)作更新
          patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
          //然后將舊節(jié)點(diǎn)組中對(duì)應(yīng)節(jié)點(diǎn)設(shè)置為undefined,代表已經(jīng)遍歷過(guò)了,不在遍歷,否則可能存在重復(fù)插入的問(wèn)題

          oldCh[idxInOld] = undefined;
          //插入到舊頭索引節(jié)點(diǎn)之前
          api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
          //新頭索引向后
          newStartVnode = newCh[++newStartIdx];
        }
      }
    }
    //當(dāng)舊頭索引大于舊尾索引時(shí),代表舊節(jié)點(diǎn)組已經(jīng)遍歷完,將剩余的新Vnode添加到最后一個(gè)新節(jié)點(diǎn)的位置后
    if (oldStartIdx > oldEndIdx) {
      before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx+1].elm;
      addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
    }
    //如果新節(jié)點(diǎn)組先遍歷完,那么代表舊節(jié)點(diǎn)組中剩余節(jié)點(diǎn)都不需要,所以直接刪除
    else if (newStartIdx > newEndIdx) {
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
    }
  }

至此,snabbdom的主要功能就分析完了

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88233.html

相關(guān)文章

  • Snabbdom.js(一)

    摘要:閑聊在學(xué)的過(guò)程中,虛擬應(yīng)該是聽(tīng)的最多的概念之一,得知其是借鑒進(jìn)行開(kāi)發(fā),故習(xí)之。以我的觀點(diǎn)來(lái)看,多個(gè)相同元素渲染時(shí),則需要為每個(gè)元素添加值。 閑聊:在學(xué)vue的過(guò)程中,虛擬dom應(yīng)該是聽(tīng)的最多的概念之一,得知其是借鑒snabbdom.js進(jìn)行開(kāi)發(fā),故習(xí)之。由于我工作處于IE8的環(huán)境,對(duì)ES6,TS這些知識(shí)的練習(xí)也只是淺嘗輒止,而snabbdom.js從v.0.5.4這個(gè)版本后開(kāi)始使用TS...

    mating 評(píng)論0 收藏0
  • vue2源碼學(xué)習(xí)開(kāi)胃——snabbdom源碼學(xué)習(xí)(一)

    摘要:前言最近在學(xué)習(xí)的源碼,剛開(kāi)始看其源碼,著實(shí)找不到方向,因?yàn)槠湓诘膶?shí)現(xiàn)上還加入了很多本身的鉤子,加大了閱讀難度。 前言 最近在學(xué)習(xí)vue2.0的源碼,剛開(kāi)始看其vdom源碼,著實(shí)找不到方向,因?yàn)槠湓趘dom的實(shí)現(xiàn)上還加入了很多vue2.0本身的鉤子,加大了閱讀難度。于是看到第一行尤大說(shuō)vue2.0的vdom是在snabbdom的基礎(chǔ)上改過(guò)來(lái)的,而snabbdom只有不到300sloc,那...

    betacat 評(píng)論0 收藏0
  • Luy 1.0 :一個(gè)React-like輪子的誕生

    摘要:司徒正美的一款了不起的化方案,支持到。行代碼內(nèi)實(shí)現(xiàn)一個(gè)胡子大哈實(shí)現(xiàn)的作品其實(shí)就是的了源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對(duì)比。 前言 在過(guò)去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開(kāi)始寫(xiě)了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱欤?..

    codecook 評(píng)論0 收藏0
  • javascript高級(jí)學(xué)習(xí)總結(jié)(

    摘要:那個(gè)率先改變的實(shí)例的返回值,就會(huì)傳遞給的回調(diào)函數(shù)。函數(shù)對(duì)函數(shù)的改進(jìn),體現(xiàn)在以下四點(diǎn)內(nèi)置執(zhí)行器。進(jìn)一步說(shuō),函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè)對(duì)象,而命令就是內(nèi)部命令的語(yǔ)法糖。中的本質(zhì)就是沒(méi)有的隱藏的組件。 1、原型 - jquery使用showImg(https://segmentfault.com/img/bVbwNcY?w=692&h=442);注釋 : 實(shí)例雖然不同,但是構(gòu)...

    Songlcy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<