vue2.0的diff算法詳解

目錄

  • 前言
  • virtual dom
  • 分析diff
  • 總結(jié)

前言

vue2.0加入了virtual dom载萌,有向react靠攏的意思。vue的diff位于patch.js文件中奈揍,一個(gè)小框架aoy也同樣使用此算法拥褂,該算法來源于snabbdom,復(fù)雜度為O(n)争占。
了解diff過程可以讓我們更高效的使用框架。

本文力求以圖文并茂的方式來講明這個(gè)diff的過程序目。

virtual dom

如果不了解virtual dom,要理解diff的過程是比較困難的伯襟。虛擬dom對應(yīng)的是真實(shí)dom猿涨, 使用document.CreateElementdocument.CreateTextNode創(chuàng)建的就是真實(shí)節(jié)點(diǎn)。

我們可以做個(gè)試驗(yàn)姆怪。打印出一個(gè)空元素的第一層屬性叛赚,可以看到標(biāo)準(zhǔn)讓元素實(shí)現(xiàn)的東西太多了。如果每次都重新生成新的元素稽揭,對性能是巨大的浪費(fèi)俺附。

var mydiv = document.createElement('div');
for(var k in mydiv ){
 console.log(k)
}

virtual dom就是解決這個(gè)問題的一個(gè)思路,到底什么是virtual dom呢溪掀?通俗易懂的來說就是用一個(gè)簡單的對象去代替復(fù)雜的dom對象事镣。
舉個(gè)簡單的例子,我們在body里插入一個(gè)class為a的div揪胃。

var mydiv = document.createElement('div');
mydiv.className = 'a';
document.body.appendChild(mydiv);

對于這個(gè)div我們可以用一個(gè)簡單的對象mydivVirtual代表它璃哟,它存儲(chǔ)了對應(yīng)dom的一些重要參數(shù)氛琢,在改變dom之前,會(huì)先比較相應(yīng)虛擬dom的數(shù)據(jù)随闪,如果需要改變阳似,才會(huì)將改變應(yīng)用到真實(shí)dom上。

//偽代碼
var mydivVirtual = { 
  tagName: 'DIV',
  className: 'a'
};
var newmydivVirtual = {
   tagName: 'DIV',
   className: 'b'
}
if(mydivVirtual.tagName !== newmydivVirtual.tagName || mydivVirtual.className  !== newmydivVirtual.className){
   change(mydiv)
}

// 會(huì)執(zhí)行相應(yīng)的修改 mydiv.className = 'b';
//最后  <div class='b'></div>

讀到這里就會(huì)產(chǎn)生一個(gè)疑問铐伴,為什么不直接修改dom而需要加一層virtual dom呢撮奏?

很多時(shí)候手工優(yōu)化dom確實(shí)會(huì)比virtual dom效率高,對于比較簡單的dom結(jié)構(gòu)用手工優(yōu)化沒有問題当宴,但當(dāng)頁面結(jié)構(gòu)很龐大畜吊,結(jié)構(gòu)很復(fù)雜時(shí),手工優(yōu)化會(huì)花去大量時(shí)間即供,而且可維護(hù)性也不高定拟,不能保證每個(gè)人都有手工優(yōu)化的能力。至此逗嫡,virtual dom的解決方案應(yīng)運(yùn)而生青自,virtual dom很多時(shí)候都不是最優(yōu)的操作,但它具有普適性驱证,在效率延窜、可維護(hù)性之間達(dá)平衡曼尊。

virtual dom 另一個(gè)重大意義就是提供一個(gè)中間層粘招,js去寫ui,ios安卓之類的負(fù)責(zé)渲染怀喉,就像reactNative一樣伙单。

分析diff

一篇相當(dāng)經(jīng)典的文章React’s diff algorithm中的圖获高,react的diff其實(shí)和vue的diff大同小異。所以這張圖能很好的解釋過程吻育。比較只會(huì)在同層級進(jìn)行, 不會(huì)跨層級比較念秧。

舉個(gè)形象的例子。

<!-- 之前 -->
<div>           <!-- 層級1 -->
  <p>            <!-- 層級2 -->
    <b> aoy </b>   <!-- 層級3 -->   
    <span>diff</Span>
  </P> 
</div>

<!-- 之后 -->
<div>            <!-- 層級1 -->
  <p>             <!-- 層級2 -->
      <b> aoy </b>        <!-- 層級3 -->
  </p>
  <span>diff</Span>
</div>

我們可能期望將<span>直接移動(dòng)到<p>的后邊布疼,這是最優(yōu)的操作摊趾。但是實(shí)際的diff操作是移除<p>里的<span>在創(chuàng)建一個(gè)新的<span>插到<p>的后邊。
因?yàn)樾录拥?code><span>在層級2游两,舊的在層級3砾层,屬于不同層級的比較。

源碼分析

文中的代碼位于aoy-diff中贱案,已經(jīng)精簡了很多代碼肛炮,留下最核心的部分。

diff的過程就是調(diào)用patch函數(shù),就像打補(bǔ)丁一樣修改真實(shí)dom铸董。

function patch (oldVnode, vnode) {
    if (sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode)
    } else {
        const oEl = oldVnode.el
        let parentEle = api.parentNode(oEl)
        createEle(vnode)
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
            api.removeChild(parentEle, oldVnode.el)
            oldVnode = null
        }
    }
    return vnode
}

patch函數(shù)有兩個(gè)參數(shù)祟印,vnodeoldVnode,也就是新舊兩個(gè)虛擬節(jié)點(diǎn)粟害。在這之前蕴忆,我們先了解完整的vnode都有什么屬性,舉個(gè)一個(gè)簡單的例子:

// body下的 <div id="v" class="classA"><div> 對應(yīng)的 oldVnode 就是

{
  el:  div  //對真實(shí)的節(jié)點(diǎn)的引用悲幅,本例中就是document.querySelector('#id.classA')
  tagName: 'DIV',   //節(jié)點(diǎn)的標(biāo)簽
  sel: 'div#v.classA'  //節(jié)點(diǎn)的選擇器
  data: null,       // 一個(gè)存儲(chǔ)節(jié)點(diǎn)屬性的對象套鹅,對應(yīng)節(jié)點(diǎn)的el[prop]屬性,例如onclick , style
  children: [], //存儲(chǔ)子節(jié)點(diǎn)的數(shù)組汰具,每個(gè)子節(jié)點(diǎn)也是vnode結(jié)構(gòu)
  text: null,    //如果是文本節(jié)點(diǎn)卓鹿,對應(yīng)文本節(jié)點(diǎn)的textContent,否則為null
}

需要注意的是留荔,el屬性引用的是此 virtual dom對應(yīng)的真實(shí)dom吟孙,patchvnode參數(shù)的el最初是null,因?yàn)?code>patch之前它還沒有對應(yīng)的真實(shí)dom聚蝶。

來到patch的第一部分杰妓,

if (sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode)
} 

sameVnode函數(shù)就是看這兩個(gè)節(jié)點(diǎn)是否值得比較,代碼相當(dāng)簡單:

function sameVnode (a, b) {
  return (
    a.key === b.key &&  // key值
    a.tagName === b.tagName &&  // 標(biāo)簽名
    a.isComment === b.isComment &&  // 是否為注釋節(jié)點(diǎn)
    // 是否都定義了data碘勉,data包含一些具體信息巷挥,例如onclick , style
    isDef(a.data) === isDef(b.data) &&  
    sameInputType(a, b) // 當(dāng)標(biāo)簽是<input>的時(shí)候,type必須相同
  )
}

兩個(gè)vnodekeysel相同才去比較它們验靡,比如pspan倍宾,div.classAdiv.classB都被認(rèn)為是不同結(jié)構(gòu)而不去比較它們。

如果值得比較會(huì)執(zhí)行patchVnode(oldVnode, vnode)胜嗓,稍后會(huì)詳細(xì)講patchVnode函數(shù)高职。

當(dāng)節(jié)點(diǎn)不值得比較,進(jìn)入else中

else {
        const oEl = oldVnode.el
        let parentEle = api.parentNode(oEl)
        createEle(vnode)
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
            api.removeChild(parentEle, oldVnode.el)
            oldVnode = null
        }
    }

過程如下:

  • 取得oldvnode.el的父節(jié)點(diǎn)辞州,parentEle是真實(shí)dom

  • createEle(vnode)會(huì)為vnode創(chuàng)建它的真實(shí)dom初厚,令vnode.el =真實(shí)dom

  • parentEle將新的dom插入,移除舊的dom

當(dāng)不值得比較時(shí)孙技,新節(jié)點(diǎn)直接把老節(jié)點(diǎn)整個(gè)替換了

最后

return vnode

patch最后會(huì)返回vnode,vnode和進(jìn)入patch之前的不同在哪排作?
沒錯(cuò)牵啦,就是vnode.el,唯一的改變就是之前vnode.el = null, 而現(xiàn)在它引用的是對應(yīng)的真實(shí)dom妄痪。

var oldVnode = patch (oldVnode, vnode)

至此完成一個(gè)patch過程哈雏。

patchVnode

兩個(gè)節(jié)點(diǎn)值得比較時(shí),會(huì)調(diào)用patchVnode函數(shù)

patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        }else if (ch){
            createEle(vnode) //create el's children dom
        }else if (oldCh){
            api.removeChildren(el)
        }
    }
}

const el = vnode.el = oldVnode.el 這是很重要的一步,讓vnode.el引用到現(xiàn)在的真實(shí)dom裳瘪,當(dāng)el修改時(shí)土浸,vnode.el會(huì)同步變化。

節(jié)點(diǎn)的比較有5種情況:

  1. if (oldVnode === vnode)彭羹,他們的引用一致黄伊,可以認(rèn)為沒有變化。

  2. if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text)派殷,文本節(jié)點(diǎn)的比較还最,需要修改,則會(huì)調(diào)用Node.textContent = vnode.text毡惜。

  3. if( oldCh && ch && oldCh !== ch ), 兩個(gè)節(jié)點(diǎn)都有子節(jié)點(diǎn)拓轻,而且它們不一樣,這樣我們會(huì)調(diào)用updateChildren函數(shù)比較子節(jié)點(diǎn)经伙,這是diff的核心扶叉,后邊會(huì)講到。

  4. else if (ch)帕膜,只有新的節(jié)點(diǎn)有子節(jié)點(diǎn)枣氧,調(diào)用createEle(vnode)vnode.el已經(jīng)引用了老的dom節(jié)點(diǎn)泳叠,createEle函數(shù)會(huì)在老dom節(jié)點(diǎn)上添加子節(jié)點(diǎn)作瞄。

  5. else if (oldCh),新節(jié)點(diǎn)沒有子節(jié)點(diǎn)危纫,老節(jié)點(diǎn)有子節(jié)點(diǎn)宗挥,直接刪除老節(jié)點(diǎn)。

updateChildren
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, elmToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        /*前四種情況其實(shí)是指定key的時(shí)候种蝶,判定為同一個(gè)VNode契耿,則直接patchVnode即可,分別比較oldCh以及newCh的兩頭節(jié)點(diǎn)2*2=4種情況*/
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        /*
          生成一個(gè)key與舊VNode的key對應(yīng)的哈希表(只有第一次進(jìn)來undefined的時(shí)候會(huì)生成螃征,也為后面檢測重復(fù)的key值做鋪墊)
          比如childre是這樣的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  
          結(jié)果生成{key0: 0, key1: 1, key2: 2}
        */
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        /*如果newStartVnode新的VNode節(jié)點(diǎn)存在key并且這個(gè)key在oldVnode中能找到則返回這個(gè)節(jié)點(diǎn)的idxInOld(即第幾個(gè)節(jié)點(diǎn)搪桂,下標(biāo))*/
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        if (isUndef(idxInOld)) { // New element
          /*newStartVnode沒有key或者是該key沒有在老節(jié)點(diǎn)中找到則創(chuàng)建一個(gè)新的節(jié)點(diǎn)*/
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          /*獲取同key的老節(jié)點(diǎn)*/
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            /*如果elmToMove不存在說明之前已經(jīng)有新節(jié)點(diǎn)放入過這個(gè)key的DOM中,提示可能存在重復(fù)的key盯滚,確保v-for的時(shí)候item有唯一的key值*/
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(elmToMove, newStartVnode)) {
            /*Github:https://github.com/answershuto*/
            /*如果新VNode與得到的有相同key的節(jié)點(diǎn)是同一個(gè)VNode則進(jìn)行patchVnode*/
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            /*因?yàn)橐呀?jīng)patchVnode進(jìn)去了踢械,所以將這個(gè)老節(jié)點(diǎn)賦值undefined,之后如果還有新節(jié)點(diǎn)與該節(jié)點(diǎn)key相同可以檢測出來提示已有重復(fù)的key*/
            oldCh[idxInOld] = undefined
            /*當(dāng)有標(biāo)識(shí)位canMove實(shí)可以直接插入oldStartVnode對應(yīng)的真實(shí)DOM節(jié)點(diǎn)前面*/
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            /*當(dāng)新的VNode與找到的同樣key的VNode不是sameVNode的時(shí)候(比如說tag不一樣或者是有不一樣type的input標(biāo)簽)魄藕,創(chuàng)建一個(gè)新的節(jié)點(diǎn)*/
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /*全部比較完成以后内列,發(fā)現(xiàn)oldStartIdx > oldEndIdx的話,說明老節(jié)點(diǎn)已經(jīng)遍歷完了背率,新節(jié)點(diǎn)比老節(jié)點(diǎn)多话瞧,所以這時(shí)候多出來的新節(jié)點(diǎn)需要一個(gè)一個(gè)創(chuàng)建出來加入到真實(shí)DOM中*/
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /*如果全部比較完成以后發(fā)現(xiàn)newStartIdx > newEndIdx嫩与,則說明新節(jié)點(diǎn)已經(jīng)遍歷完了,老節(jié)點(diǎn)多余新節(jié)點(diǎn)交排,這個(gè)時(shí)候需要將多余的老節(jié)點(diǎn)從真實(shí)DOM中移除*/
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }

代碼很密集划滋,為了形象的描述這個(gè)過程,可以看看這張圖埃篓。

過程可以概括為:oldChnewCh各有兩個(gè)頭尾的變量StartIdxEndIdx处坪,它們的2個(gè)變量相互比較,一共有4種比較方式都许。如果4種比較都沒匹配稻薇,如果設(shè)置了key,就會(huì)用key進(jìn)行比較胶征,在比較的過程中塞椎,變量會(huì)往中間靠,一旦StartIdx>EndIdx表明oldChnewCh至少有一個(gè)已經(jīng)遍歷完了睛低,就會(huì)結(jié)束比較案狠。

具體的diff分析

設(shè)置key和不設(shè)置key的區(qū)別:

不設(shè)keynewCholdCh只會(huì)進(jìn)行頭尾兩端的相互比較钱雷,設(shè)key后骂铁,除了頭尾兩端的比較外,還會(huì)從用key生成的對象oldKeyToIdx中查找匹配的節(jié)點(diǎn)罩抗,所以為節(jié)點(diǎn)設(shè)置key可以更高效的利用dom拉庵。

diff的遍歷過程中,只要是對dom進(jìn)行的操作都調(diào)用api.insertBefore套蒂,api.insertBefore只是原生insertBefore的簡單封裝钞支。
比較分為兩種,一種是有vnode.key的操刀,一種是沒有的烁挟。但這兩種比較對真實(shí)dom的操作是一致的。

對于與sameVnode(oldStartVnode, newStartVnode)sameVnode(oldEndVnode,newEndVnode)true的情況骨坑,不需要對dom進(jìn)行移動(dòng)撼嗓。

總結(jié)遍歷過程,有3種dom操作:

  1. 當(dāng)oldStartVnode欢唾,newEndVnode值得比較且警,說明oldStartVnode.el跑到oldEndVnode.el的后邊了。

圖中假設(shè)startIdx遍歷到1礁遣。

  1. 當(dāng)oldEndVnode振湾,newStartVnode值得比較,oldEndVnode.el跑到了oldStartVnode.el的前邊亡脸,準(zhǔn)確的說應(yīng)該是oldEndVnode.el需要移動(dòng)到oldStartVnode.el的前邊押搪。
  1. newCh中的節(jié)點(diǎn)oldCh里沒有, 將新節(jié)點(diǎn)插入到oldStartVnode.el的前邊浅碾。

在結(jié)束時(shí)大州,分為兩種情況:

  1. oldStartIdx > oldEndIdx,可以認(rèn)為oldCh先遍歷完垂谢。當(dāng)然也有可能newCh此時(shí)也正好完成了遍歷厦画,統(tǒng)一都?xì)w為此類。此時(shí)newStartIdxnewEndIdx之間的vnode是新增的滥朱,調(diào)用addVnodes根暑,把他們?nèi)坎暹M(jìn)before的后邊,before很多時(shí)候是為null的徙邻。addVnodes調(diào)用的是insertBefore操作dom節(jié)點(diǎn)排嫌,我們看看insertBefore的文檔:parentElement.insertBefore(newElement, referenceElement)
    如果referenceElementnullnewElement將被插入到子節(jié)點(diǎn)的末尾。如果newElement已經(jīng)在DOM樹中缰犁,newElement首先會(huì)從DOM樹中移除淳地。所以before為null,newElement將被插入到子節(jié)點(diǎn)的末尾帅容。
  1. newStartIdx > newEndIdx颇象,可以認(rèn)為newCh先遍歷完。此時(shí)oldStartIdxoldEndIdx之間的vnode在新的子節(jié)點(diǎn)里已經(jīng)不存在了并徘,調(diào)用removeVnodes將它們從dom里刪除遣钳。

下面舉個(gè)例子,畫出diff完整的過程麦乞,每一步dom的變化都用不同顏色的線標(biāo)出蕴茴。

  1. a,b,c,d,e假設(shè)是4個(gè)不同的元素,我們沒有設(shè)置key時(shí)路幸,b沒有復(fù)用荐开,而是直接創(chuàng)建新的,刪除舊的简肴。


  2. 當(dāng)我們給4個(gè)元素加上唯一key時(shí)晃听,b得到了的復(fù)用。

這個(gè)例子如果我們使用手工優(yōu)化砰识,只需要3步就可以達(dá)到能扒。

總結(jié)

  • 盡量不要跨層級的修改dom

  • 設(shè)置key可以最大化的利用節(jié)點(diǎn)

  • 不要盲目相信diff的效率,在必要時(shí)可以手工優(yōu)化

本文參考:https://segmentfault.com/a/1190000008782928

https://github.com/answershuto/learnVue/blob/master/docs/VirtualDOM%E4%B8%8Ediff(Vue%E5%AE%9E%E7%8E%B0).MarkDown

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辫狼,一起剝皮案震驚了整個(gè)濱河市初斑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膨处,老刑警劉巖见秤,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砂竖,死亡現(xiàn)場離奇詭異,居然都是意外死亡鹃答,警方通過查閱死者的電腦和手機(jī)乎澄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來测摔,“玉大人置济,你說我怎么就攤上這事》姘耍” “怎么了浙于?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挟纱。 經(jīng)常有香客問我羞酗,道長,這世上最難降的妖魔是什么樊销? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任整慎,我火速辦了婚禮,結(jié)果婚禮上围苫,老公的妹妹穿的比我還像新娘裤园。我一直安慰自己,他們只是感情好剂府,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布拧揽。 她就那樣靜靜地躺著,像睡著了一般腺占。 火紅的嫁衣襯著肌膚如雪淤袜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天衰伯,我揣著相機(jī)與錄音铡羡,去河邊找鬼。 笑死意鲸,一個(gè)胖子當(dāng)著我的面吹牛烦周,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怎顾,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼读慎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了槐雾?” 一聲冷哼從身側(cè)響起夭委,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎募强,沒想到半個(gè)月后株灸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇摄,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年蚂且,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了配猫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杏死,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捆交,到底是詐尸還是另有隱情淑翼,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布品追,位于F島的核電站玄括,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肉瓦。R本人自食惡果不足惜遭京,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泞莉。 院中可真熱鬧哪雕,春花似錦、人聲如沸鲫趁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挨厚。三九已至堡僻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疫剃,已是汗流浹背钉疫。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巢价,地道東北人牲阁。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蹄溉,于是被迫代替她去往敵國和親咨油。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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

  • 轉(zhuǎn)載請注明出處本文轉(zhuǎn)載至我的blog 目錄 前言 virtual dom 分析diff 總結(jié) 前言 vue2.0加...
    yangjingzhuo閱讀 9,430評論 2 23
  • 詳解vue的diff算法 1. 當(dāng)數(shù)據(jù)發(fā)生變化時(shí)柒爵,vue是怎么更新節(jié)點(diǎn)的役电? 要知道渲染真實(shí)DOM的開銷是很大的,比...
    Lilio閱讀 286評論 0 1
  • vue是現(xiàn)在主流前端框架之一棉胀,采用了很多高級特性法瑟,如虛擬DOM冀膝,那么它是如何批量更新的,我們一起來了解下霎挟。 數(shù)據(jù)變...
    老鼠AI大米_Java全棧閱讀 905評論 0 4
  • 23期第一周復(fù)盤情況如下: 1.計(jì)劃每天慢跑8公里窝剖,實(shí)際執(zhí)行情況:7天跑了61.98公里,平均每天跑8.85...
    annie_bbe1閱讀 215評論 2 2
  • 維特塔羅:圣杯國王 國王端坐在寶座上酥夭,頭略微側(cè)向一邊赐纱,看起來表情生動(dòng)有趣,好像在說熬北,我坐在這里履行我的職責(zé)疙描,但我也...
    funnygirl1231閱讀 353評論 0 0