理解Vue2.5中diff算法

vue是現(xiàn)在主流前端框架之一,采用了很多高級(jí)特性威酒,如虛擬DOM窑睁,那么它是如何批量更新的,我們一起來了解下葵孤。

數(shù)據(jù)變化担钮,如何更新dom

DOM“天生就慢”,所以前端各大框架都提供了對(duì)DOM操作進(jìn)行優(yōu)化的辦法尤仍,Angular中的是臟值檢查箫津,React首先提出了Virtual Dom,Vue2.0也加入了Virtual Dom宰啦,與React類似苏遥。

要知道渲染真實(shí)DOM的開銷是很大的,比如有時(shí)候我們修改了某個(gè)數(shù)據(jù)赡模,如果直接渲染到真實(shí)dom上會(huì)引起整個(gè)dom樹的重繪和重排田炭,有沒有可能我們只更新我們修改的那一小塊dom而不要更新整個(gè)dom呢?diff算法能夠幫助我們纺裁。

我們先根據(jù)真實(shí)DOM生成一顆virtual DOM诫肠,當(dāng)virtual DOM某個(gè)節(jié)點(diǎn)的數(shù)據(jù)改變后會(huì)生成一個(gè)新的Vnode,然后Vnode和oldVnode作對(duì)比欺缘,發(fā)現(xiàn)有不一樣的地方就直接修改在真實(shí)的DOM上栋豫,然后使oldVnode的值為Vnode。

diff的過程就是調(diào)用名為patch的函數(shù)谚殊,比較新舊節(jié)點(diǎn)丧鸯,一邊比較一邊給真實(shí)的DOM打補(bǔ)丁。

虛擬dom與真實(shí)dom

真實(shí)dom

<div>
    <p>111</p>
</div>

對(duì)應(yīng)的virtual DOM(偽代碼)

var Vnode = {
    tag: 'div',
    children: [
        { tag: 'p', text: '111' }
    ]
};

diff對(duì)比方式

在采取diff算法比較新舊節(jié)點(diǎn)的時(shí)候嫩絮,比較只會(huì)在同層級(jí)進(jìn)行, 不會(huì)跨層級(jí)比較丛肢。

<div>
    <p>123</p>
</div>

<div>
    <span>456</span>
</div>

上面的代碼會(huì)分別比較同一層的兩個(gè)div以及第二層的p和span,但是不會(huì)拿div和span作比較剿干。在別處看到的一張很形象的圖:


diff-compare.png

diff流程圖

當(dāng)數(shù)據(jù)發(fā)生改變時(shí)蜂怎,set方法會(huì)讓調(diào)用Dep.notify通知所有訂閱者Watcher,訂閱者就會(huì)調(diào)用patch給真實(shí)的DOM打補(bǔ)丁置尔,更新相應(yīng)的視圖杠步。


diff.png

VNode對(duì)象

一個(gè)VNode的實(shí)例包含了以下屬性,這部分代碼在src/core/vdom/vnode.js里

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  functionalContext: Component | void; // real context vm for functional nodes
  functionalOptions: ?ComponentOptions; // for SSR caching
  functionalScopeId: ?string; // functioanl scope id support
  • tag: 當(dāng)前節(jié)點(diǎn)的標(biāo)簽名
  • data: 當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)對(duì)象榜轿,具體包含哪些字段可以參考vue源碼types/vnode.d.ts中對(duì)VNodeData的定義
  • children: 數(shù)組類型幽歼,包含了當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)
  • text: 當(dāng)前節(jié)點(diǎn)的文本,一般文本節(jié)點(diǎn)或注釋節(jié)點(diǎn)會(huì)有該屬性
  • elm: 當(dāng)前虛擬節(jié)點(diǎn)對(duì)應(yīng)的真實(shí)的dom節(jié)點(diǎn)
  • ns: 節(jié)點(diǎn)的namespace
  • context: 編譯作用域
  • functionalContext: 函數(shù)化組件的作用域
  • key: 節(jié)點(diǎn)的key屬性谬盐,用于作為節(jié)點(diǎn)的標(biāo)識(shí)甸私,有利于patch的優(yōu)化
  • componentOptions: 創(chuàng)建組件實(shí)例時(shí)會(huì)用到的選項(xiàng)信息
  • child: 當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的組件實(shí)例
  • parent: 組件的占位節(jié)點(diǎn)
  • raw: raw html
  • isStatic: 靜態(tài)節(jié)點(diǎn)的標(biāo)識(shí)
  • isRootInsert: 是否作為根節(jié)點(diǎn)插入,被包裹的節(jié)點(diǎn)飞傀,該屬性的值為false
  • isComment: 當(dāng)前節(jié)點(diǎn)是否是注釋節(jié)點(diǎn)
  • isCloned: 當(dāng)前節(jié)點(diǎn)是否為克隆節(jié)點(diǎn)
  • isOnce: 當(dāng)前節(jié)點(diǎn)是否有v-once指令

VNode的分類

VNode可以理解為VueVirtual Dom的一個(gè)基類皇型,通過VNode構(gòu)造函數(shù)生成的VNnode實(shí)例可為如下幾類:

  • EmptyVNode: 沒有內(nèi)容的注釋節(jié)點(diǎn)
  • TextVNode: 文本節(jié)點(diǎn)
  • ElementVNode: 普通元素節(jié)點(diǎn)
  • ComponentVNode: 組件節(jié)點(diǎn)
  • CloneVNode: 克隆節(jié)點(diǎn),可以是以上任意類型的節(jié)點(diǎn)砸烦,唯一的區(qū)別在于isCloned屬性為true

具體Diff分析

來看看patch是怎么打補(bǔ)丁的(代碼只保留核心部分)

function patch (oldVnode, vnode) {
    // some code
    if (sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode)
    } else {
        const oEl = oldVnode.el // 當(dāng)前oldVnode對(duì)應(yīng)的真實(shí)元素節(jié)點(diǎn)
        let parentEle = api.parentNode(oEl)  // 父元素
        createEle(vnode)  // 根據(jù)Vnode生成新元素
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 將新元素添加進(jìn)父元素
            api.removeChild(parentEle, oldVnode.el)  // 移除以前的舊元素節(jié)點(diǎn)
            oldVnode = null
        }
    }
    // some code 
    return vnode
}

patch函數(shù)接收兩個(gè)參數(shù)oldVnode和Vnode分別代表新的節(jié)點(diǎn)和之前的舊節(jié)點(diǎn)
判斷兩節(jié)點(diǎn)是否值得比較犀被,值得比較則執(zhí)行patchVnode

function sameVnode (a, b) {
  return (
    a.key === b.key &&  // key值
    a.tag === b.tag &&  // 標(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必須相同
  )
}

不值得比較則用Vnode替換oldVnode
如果兩個(gè)節(jié)點(diǎn)都是一樣的,那么就深入檢查他們的子節(jié)點(diǎn)雪隧。如果兩個(gè)節(jié)點(diǎn)不一樣那就說明Vnode完全被改變了儒将,就可以直接替換oldVnode。
雖然這兩個(gè)節(jié)點(diǎn)不一樣但是他們的子節(jié)點(diǎn)一樣怎么辦讯柔?別忘了蝎困,diff可是逐層比較的,如果第一層不一樣那么就不會(huì)繼續(xù)深入比較第二層了庄拇。

patchVnode

當(dāng)我們確定兩個(gè)節(jié)點(diǎn)值得比較之后我們會(huì)對(duì)兩個(gè)節(jié)點(diǎn)指定patchVnode方法注服。那么這個(gè)方法做了什么呢韭邓?

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)
        }
    }
}

這個(gè)函數(shù)做了以下事情:

  • 找到對(duì)應(yīng)的真實(shí)dom,稱為el
  • 判斷Vnode和oldVnode是否指向同一個(gè)對(duì)象溶弟,如果是女淑,那么直接return
  • 如果他們都有文本節(jié)點(diǎn)并且不相等,那么將el的文本節(jié)點(diǎn)設(shè)置為Vnode的文本節(jié)點(diǎn)辜御。
  • 如果oldVnode有子節(jié)點(diǎn)而Vnode沒有鸭你,則刪除el的子節(jié)點(diǎn)
  • 如果oldVnode沒有子節(jié)點(diǎn)而Vnode有,則將Vnode的子節(jié)點(diǎn)真實(shí)化之后添加到el
  • 如果兩者都有子節(jié)點(diǎn)擒权,則執(zhí)行updateChildren函數(shù)比較子節(jié)點(diǎn)袱巨,這一步很重要
    其他幾個(gè)點(diǎn)都很好理解,我們?cè)敿?xì)來講一下updateChildren

updateChildren

updataChildren是Diff算法的核心碳抄,所以本文對(duì)updataChildren進(jìn)行了圖文的分析愉老。

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0 // 舊頭索引
    let newStartIdx = 0 // 新頭索引
    let oldEndIdx = oldCh.length - 1 // 舊尾索引
    let newEndIdx = newCh.length - 1 // 新尾索引
    let oldStartVnode = oldCh[0] // oldVnode的第一個(gè)child
    let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一個(gè)child
    let newStartVnode = newCh[0] // newVnode的第一個(gè)child
    let newEndVnode = newCh[newEndIdx] // newVnode的最后一個(gè)child
    let oldKeyToIdx, idxInOld, vnodeToMove, 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

    // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了剖效,證明diff完了俺夕,循環(huán)結(jié)束
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 如果oldVnode的第一個(gè)child不存在
      if (isUndef(oldStartVnode)) {
        // oldStart索引右移
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left

      // 如果oldVnode的最后一個(gè)child不存在
      } else if (isUndef(oldEndVnode)) {
        // oldEnd索引左移
        oldEndVnode = oldCh[--oldEndIdx]

      // oldStartVnode和newStartVnode是同一個(gè)節(jié)點(diǎn)
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // patch oldStartVnode和newStartVnode, 索引左移贱鄙,繼續(xù)循環(huán)
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]

      // oldEndVnode和newEndVnode是同一個(gè)節(jié)點(diǎn)
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // patch oldEndVnode和newEndVnode劝贸,索引右移,繼續(xù)循環(huán)
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]

      // oldStartVnode和newEndVnode是同一個(gè)節(jié)點(diǎn)
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // patch oldStartVnode和newEndVnode
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        // 如果removeOnly是false逗宁,則將oldStartVnode.eml移動(dòng)到oldEndVnode.elm之后
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        // oldStart索引右移映九,newEnd索引左移
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]

      // 如果oldEndVnode和newStartVnode是同一個(gè)節(jié)點(diǎn)
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // patch oldEndVnode和newStartVnode
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        // 如果removeOnly是false,則將oldEndVnode.elm移動(dòng)到oldStartVnode.elm之前
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        // oldEnd索引左移瞎颗,newStart索引右移
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]

      // 如果都不匹配
      } else {
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

        // 嘗試在oldChildren中尋找和newStartVnode的具有相同的key的Vnode
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

        // 如果未找到件甥,說明newStartVnode是一個(gè)新的節(jié)點(diǎn)
        if (isUndef(idxInOld)) { // New element
          // 創(chuàng)建一個(gè)新Vnode
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)

        // 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove
        } else {
          vnodeToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }

          // 比較兩個(gè)具有相同的key的新節(jié)點(diǎn)是否是同一個(gè)節(jié)點(diǎn)
          //不設(shè)key哼拔,newCh和oldCh只會(huì)進(jìn)行頭尾兩端的相互比較引有,設(shè)key后,除了頭尾兩端的比較外倦逐,還會(huì)從用key生成的對(duì)象oldKeyToIdx中查找匹配的節(jié)點(diǎn)譬正,所以為節(jié)點(diǎn)設(shè)置key可以更高效的利用dom。
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // patch vnodeToMove和newStartVnode
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
            // 清除
            oldCh[idxInOld] = undefined
            // 如果removeOnly是false檬姥,則將找到的和newStartVnodej具有相同的key的Vnode曾我,叫vnodeToMove.elm
            // 移動(dòng)到oldStartVnode.elm之前
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

          // 如果key相同,但是節(jié)點(diǎn)不相同健民,則創(chuàng)建一個(gè)新的節(jié)點(diǎn)
          } else {
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          }
        }

        // 右移
        newStartVnode = newCh[++newStartIdx]
      }
    }

先說一下這個(gè)函數(shù)做了什么

將Vnode的子節(jié)點(diǎn)Vch和oldVnode的子節(jié)點(diǎn)oldCh提取出來
oldCh和vCh各有兩個(gè)頭尾的變量StartIdx和EndIdx抒巢,它們的2個(gè)變量相互比較,一共有4種比較方式秉犹。如果4種比較都沒匹配蛉谜,如果設(shè)置了key稚晚,就會(huì)用key進(jìn)行比較,在比較的過程中型诚,變量會(huì)往中間靠客燕,一旦StartIdx>EndIdx表明oldCh和vCh至少有一個(gè)已經(jīng)遍歷完了,就會(huì)結(jié)束比較俺驶。
圖解updateChildren
終于來到了這一部分幸逆,上面的總結(jié)相信很多人也看得一臉懵逼棍辕,下面我們好好說道說道暮现。(這都是我自己畫的,求推薦好用的畫圖工具...)

粉紅色的部分為oldCh和vCh


vnode.png

我們將它們?nèi)〕鰜聿⒎謩e用s和e指針指向它們的頭child和尾child


patchnode.png

現(xiàn)在分別對(duì)oldS楚昭、oldE栖袋、S、E兩兩做sameVnode比較抚太,有四種比較方式塘幅,當(dāng)其中兩個(gè)能匹配上那么真實(shí)dom中的相應(yīng)節(jié)點(diǎn)會(huì)移到Vnode相應(yīng)的位置,這句話有點(diǎn)繞尿贫,打個(gè)比方

如果是oldS和E匹配上了电媳,那么真實(shí)dom中的第一個(gè)節(jié)點(diǎn)會(huì)移到最后
如果是oldE和S匹配上了,那么真實(shí)dom中的最后一個(gè)節(jié)點(diǎn)會(huì)移到最前庆亡,匹配上的兩個(gè)指針向中間移動(dòng)
如果四種匹配沒有一對(duì)是成功的匾乓,那么遍歷oldChild,S挨個(gè)和他們匹配又谋,匹配成功就在真實(shí)dom中將成功的節(jié)點(diǎn)移到最前面拼缝,如果依舊沒有成功的,那么將S對(duì)應(yīng)的節(jié)點(diǎn)插入到dom中對(duì)應(yīng)的oldS位置彰亥,oldS和S指針向中間移動(dòng)咧七。

再配個(gè)圖


patchnode2.png

第一步
oldS = a, oldE = d;
S = a, E = b;
oldS和S匹配任斋,則將dom中的a節(jié)點(diǎn)放到第一個(gè)继阻,已經(jīng)是第一個(gè)了就不管了,此時(shí)dom的位置為:a b d

第二步
oldS = b, oldE = d废酷;
S = c, E = b;
oldS和E匹配穴翩,就將原本的b節(jié)點(diǎn)移動(dòng)到最后,因?yàn)镋是最后一個(gè)節(jié)點(diǎn)锦积,他們位置要一致芒帕,這就是上面說的:當(dāng)其中兩個(gè)能匹配上那么真實(shí)dom中的相應(yīng)節(jié)點(diǎn)會(huì)移到Vnode相應(yīng)的位置,此時(shí)dom的位置為:a d b

第三步
oldS = d, oldE = d丰介;
S = c, E = d;
oldE和E匹配背蟆,位置不變此時(shí)dom的位置為:a d b

第四步
oldS++;
oldE--;
oldS > oldE;
遍歷結(jié)束鉴分,說明oldCh先遍歷完。就將剩余的vCh節(jié)點(diǎn)根據(jù)自己的的index插入到真實(shí)dom中去带膀,此時(shí)dom位置為:a c d b

一次模擬完成志珍。

這個(gè)匹配過程的結(jié)束有兩個(gè)條件:

  • oldS > oldE表示oldCh先遍歷完,那么就將多余的vCh根據(jù)index添加到dom中去(如上圖)
  • S > E表示vCh先遍歷完垛叨,那么就在真實(shí)dom中將區(qū)間為[oldS, oldE]的多余節(jié)點(diǎn)刪掉
    如下圖伦糯,新dom比舊dom少,最終為:a b e


    patchnode3.png

再來一個(gè)例子嗽元,最終dom順序?yàn)椋篴 e b f


patchnode4.png

當(dāng)這些節(jié)點(diǎn)sameVnode成功后就會(huì)緊接著執(zhí)行patchVnode了敛纲,可以看一下上面的代碼

if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode)
}

就這樣層層遞歸下去,直到將oldVnode和Vnode中的所有子節(jié)點(diǎn)比對(duì)完剂癌。也將dom的所有補(bǔ)丁都打好啦淤翔。那么現(xiàn)在再回過去看updateChildren的代碼會(huì)不會(huì)容易很多呢?

參考:
https://www.cnblogs.com/wind-lanyan/p/9061684.html
https://www.cnblogs.com/isLiu/p/7909889.html
https://blog.csdn.net/m6i37jk/article/details/78140159

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佩谷,一起剝皮案震驚了整個(gè)濱河市旁壮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谐檀,老刑警劉巖抡谐,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異桐猬,居然都是意外死亡麦撵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門课幕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厦坛,“玉大人,你說我怎么就攤上這事乍惊《沤眨” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵润绎,是天一觀的道長(zhǎng)撬碟。 經(jīng)常有香客問我,道長(zhǎng)莉撇,這世上最難降的妖魔是什么呢蛤? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮棍郎,結(jié)果婚禮上其障,老公的妹妹穿的比我還像新娘。我一直安慰自己涂佃,他們只是感情好励翼,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布蜈敢。 她就那樣靜靜地躺著,像睡著了一般汽抚。 火紅的嫁衣襯著肌膚如雪抓狭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天造烁,我揣著相機(jī)與錄音否过,去河邊找鬼。 笑死惭蟋,一個(gè)胖子當(dāng)著我的面吹牛苗桂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敞葛,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼誉察,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼与涡!你這毒婦竟也來了惹谐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤驼卖,失蹤者是張志新(化名)和其女友劉穎氨肌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酌畜,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怎囚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桥胞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恳守。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贩虾,靈堂內(nèi)的尸體忽然破棺而出催烘,到底是詐尸還是另有隱情,我是刑警寧澤缎罢,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布伊群,位于F島的核電站,受9級(jí)特大地震影響策精,放射性物質(zhì)發(fā)生泄漏舰始。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一咽袜、第九天 我趴在偏房一處隱蔽的房頂上張望丸卷。 院中可真熱鬧,春花似錦询刹、人聲如沸谜嫉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骄恶。三九已至食铐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僧鲁,已是汗流浹背虐呻。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寞秃,地道東北人斟叼。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像春寿,于是被迫代替她去往敵國(guó)和親朗涩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361