virtual dom 原理理解

首先理解VNode對(duì)象

一個(gè)VNode的實(shí)例對(duì)象包含了以下屬性,參見源碼src/vdom/vnode.js

constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }

其中幾個(gè)比較重要的屬性:

  • 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)
  • key: 節(jié)點(diǎn)的key屬性实夹,用于作為節(jié)點(diǎn)的標(biāo)識(shí),有利于patch的優(yōu)化

比如尘颓,定義一個(gè)vnode痘系,它的數(shù)據(jù)結(jié)構(gòu)是:

    {
        tag: 'div'
        data: {
            id: 'app',
            class: 'page-box'
        },
        children: [
            {
                tag: 'p',
                text: 'this is demo'
            }
        ]
    }

通過一定的渲染函數(shù)艺智,最后渲染出的實(shí)際的dom結(jié)構(gòu)就是:

   <div id="app" class="page-box">
       <p>this is demo</p>
   </div>

VNode對(duì)象是JS用對(duì)象模擬的DOM節(jié)點(diǎn),通過渲染這些對(duì)象即可渲染成一棵dom樹褐筛。

patch

我對(duì)patch的理解就是對(duì)內(nèi)容已經(jīng)變更的節(jié)點(diǎn)進(jìn)行修改的過程

當(dāng)model中的響應(yīng)式的數(shù)據(jù)發(fā)生了變化腾它,這些響應(yīng)式的數(shù)據(jù)所維護(hù)的dep數(shù)組便會(huì)調(diào)用dep.notify()方法完成所有依賴遍歷執(zhí)行的工作,這里面就包括了視圖的更新即updateComponent方法死讹。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

完成視圖的更新工作事實(shí)上就是調(diào)用了vm._update方法瞒滴,這個(gè)方法接收的第一個(gè)參數(shù)是剛生成的Vnode(vm._render()會(huì)生成一個(gè)新的Vnode)
vm._update方法主要調(diào)用了vm._patch_() 方法,這也是整個(gè)virtaul-dom當(dāng)中最為核心的方法赞警,主要完成了prevVnode和vnode的diff過程并根據(jù)需要操作的vdom節(jié)點(diǎn)打patch妓忍,最后生成新的真實(shí)dom節(jié)點(diǎn)并完成視圖的更新工作。

   function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
        // 當(dāng)oldVnode不存在時(shí)
        if (isUndef(oldVnode)) {
            // 創(chuàng)建新的節(jié)點(diǎn)
            createElm(vnode, insertedVnodeQueue, parentElm, refElm)
        } else {
            const isRealElement = isDef(oldVnode.nodeType)
            if (!isRealElement && sameVnode(oldVnode, vnode)) {
            // patch existing root node
            patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } 
        }
    }

在當(dāng)oldVnode不存在的時(shí)候愧旦,這個(gè)時(shí)候是root節(jié)點(diǎn)初始化的過程世剖,因此調(diào)用了createElm(vnode, insertedVnodeQueue, parentElm, refElm)方法去創(chuàng)建一個(gè)新的節(jié)點(diǎn)。而當(dāng)oldVnode是vnode且sameVnode(oldVnode, vnode)2個(gè)節(jié)點(diǎn)的基本屬性相同笤虫,那么就進(jìn)入了2個(gè)節(jié)點(diǎn)的patch以及diff過程旁瘫。
(在對(duì)oldVnode和vnode類型判斷中有個(gè)sameVnode方法,這個(gè)方法決定了是否需要對(duì)oldVnode和vnode進(jìn)行diff及patch的過程琼蚯。如果2個(gè)vnode的基本屬性存在不一致的情況酬凳,那么就會(huì)直接跳過diff的過程,進(jìn)而依據(jù)vnode新建一個(gè)真實(shí)的dom遭庶,同時(shí)刪除老的dom節(jié)點(diǎn))

function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  )
}

patch過程主要調(diào)用了patchVnode(src/core/vdom/patch.js)方法進(jìn)行的:

if (isDef(data) && isPatchable(vnode)) {
      // cbs保存了hooks鉤子函數(shù): 'create', 'activate', 'update', 'remove', 'destroy'
      // 取出cbs保存的update鉤子函數(shù)宁仔,依次調(diào)用,更新attrs/style/class/events/directives/refs等屬性
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }

更新真實(shí)dom節(jié)點(diǎn)的data屬性峦睡,相當(dāng)于對(duì)dom節(jié)點(diǎn)進(jìn)行了預(yù)處理的操作
接下來:

    ...
    const elm = vnode.elm = oldVnode.elm
    const oldCh = oldVnode.children
    const ch = vnode.children
    // 如果vnode沒有文本節(jié)點(diǎn)
    if (isUndef(vnode.text)) {
      // 如果oldVnode的children屬性存在且vnode的屬性也存在
      if (isDef(oldCh) && isDef(ch)) {
        // updateChildren翎苫,對(duì)子節(jié)點(diǎn)進(jìn)行diff
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        // 如果oldVnode的text存在,那么首先清空text的內(nèi)容
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // 然后將vnode的children添加進(jìn)去
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // 刪除elm下的oldchildren
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        // oldVnode有子節(jié)點(diǎn)榨了,而vnode沒有煎谍,那么就清空這個(gè)節(jié)點(diǎn)
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      // 如果oldVnode和vnode文本屬性不同,那么直接更新真是dom節(jié)點(diǎn)的文本元素
      nodeOps.setTextContent(elm, vnode.text)
    }

這個(gè)patch的過程又分為幾種情況:
1.當(dāng)vnode的text為空龙屉,即不是文本節(jié)點(diǎn)時(shí)呐粘。

  • 如果oldVnode和新節(jié)點(diǎn)vnode都有子節(jié)點(diǎn)。
    則調(diào)用updateChildren( ),對(duì)子節(jié)點(diǎn)進(jìn)行diff
  • 如果只有新節(jié)點(diǎn)vnode有子節(jié)點(diǎn)
    則判斷oldVnode是否是文本節(jié)點(diǎn)事哭,如果是文本節(jié)點(diǎn)漫雷,則首先清空真實(shí)節(jié)點(diǎn)的text的內(nèi)容。然后把新節(jié)點(diǎn)的children添加到elm中鳍咱。
  • 如果只有oldVnode有子節(jié)點(diǎn)時(shí)
    則調(diào)用removeVnodes()刪除elm下的oldVnode的children降盹。
  • 如果oldVnode和新節(jié)點(diǎn)vnode都沒有子節(jié)點(diǎn),且oldVnode是文本節(jié)點(diǎn)
    則清空真實(shí)節(jié)點(diǎn)的text的內(nèi)容谤辜。

2.當(dāng)vnode的text存在蓄坏,即是文本節(jié)點(diǎn)時(shí)
則設(shè)置真實(shí)節(jié)點(diǎn)的text內(nèi)容為vnode的text內(nèi)容。

diff過程

我對(duì)diff的理解就是遍歷兩棵不同的虛擬樹丑念,如果其中有的節(jié)點(diǎn)不同涡戳,則進(jìn)行patch。

上個(gè)函數(shù)的updateChildren(src/core/vdom/patch.js)方法就是diff過程脯倚,它也是整個(gè)diff過程中最重要的環(huán)節(jié):

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    // 為oldCh和newCh分別建立索引渔彰,為之后遍歷的依據(jù)
    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
    
    // 直到oldCh或者newCh被遍歷完后跳出循環(huán)
    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)) {
        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)
        // 插入到老的開始節(jié)點(diǎn)的前面
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // 如果以上條件都不滿足,那么這個(gè)時(shí)候開始比較key值推正,首先建立key和index索引的對(duì)應(yīng)關(guān)系
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        // 如果idxInOld不存在
        // 1. newStartVnode上存在這個(gè)key,但是oldKeyToIdx中不存在
        // 2. newStartVnode上并沒有設(shè)置key屬性
        if (isUndef(idxInOld)) { // New element
          // 創(chuàng)建新的dom節(jié)點(diǎn)
          // 插入到oldStartVnode.elm前面
          // 參見createElm方法
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          
          // 將找到的key一致的oldVnode再和newStartVnode進(jìn)行diff
          if (sameVnode(elmToMove, newStartVnode)) {
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            oldCh[idxInOld] = undefined
            // 移動(dòng)node節(jié)點(diǎn)
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            // 創(chuàng)建新的dom節(jié)點(diǎn)
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    // 如果最后遍歷的oldStartIdx大于oldEndIdx的話
    if (oldStartIdx > oldEndIdx) {        // 如果是老的vdom先被遍歷完
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      // 添加newVnode中剩余的節(jié)點(diǎn)到parentElm中
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) { // 如果是新的vdom先被遍歷完恍涂,則刪除oldVnode里面所有的節(jié)點(diǎn)
      // 刪除剩余的節(jié)點(diǎn)
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
}

代碼中,oldStartIdx植榕,oldEndIdx是遍歷oldCh(oldVnode的子節(jié)點(diǎn))的索引
newStartIdx再沧,newEndIdx是遍歷newCh(vnode的子節(jié)點(diǎn))的索引

diff遍歷的過程如下: (節(jié)點(diǎn)屬性中不帶key的情況)

遍歷完的條件就是oldCh或者newCh的startIndex >= endIndex
首先先判斷oldCh的起始節(jié)點(diǎn)oldStartVnode和末尾節(jié)點(diǎn)oldEndVnode是否存在,如果不存在尊残,則oldCh的起始節(jié)點(diǎn)向后移動(dòng)一位炒瘸,末尾節(jié)點(diǎn)向前移動(dòng)一位。

如果存在寝衫,則每一輪diff都進(jìn)行比較如下比較:

  1. sameVnode(oldStartVnode, newStartVnode)
    判斷老節(jié)點(diǎn)的初節(jié)點(diǎn)和新節(jié)點(diǎn)的初節(jié)點(diǎn)是否是同一類型顷扩,如果是,則對(duì)它們兩個(gè)進(jìn)行patchVnode(patch過程).兩個(gè)節(jié)點(diǎn)初節(jié)點(diǎn)分別向后移動(dòng)一位竞端。
  2. 如果1不滿足屎即,sameVnode(oldEndVnode, newEndVnode)
    判斷老節(jié)點(diǎn)的尾節(jié)點(diǎn)和新節(jié)點(diǎn)的尾節(jié)點(diǎn)是否是同一類型,如果是事富,則對(duì)它們兩個(gè)進(jìn)行patchVnode(patch過程).兩個(gè)節(jié)點(diǎn)尾節(jié)點(diǎn)分別向前移動(dòng)一位。
  3. 如果2也不滿足乘陪,則sameVnode(oldStartVnode, newEndVnode)
    判斷老節(jié)點(diǎn)的初節(jié)點(diǎn)和新節(jié)點(diǎn)的尾節(jié)點(diǎn)是否是同一類型统台,如果是,則對(duì)它們兩個(gè)進(jìn)行patchVnode(patch過程).老節(jié)點(diǎn)的初節(jié)點(diǎn)向后移動(dòng)一位啡邑,新節(jié)點(diǎn)尾節(jié)點(diǎn)向前移動(dòng)一位贱勃。
  4. 如果3也不滿足,則sameVnode(oldEndVnode, newStartVnode)
    判斷老節(jié)點(diǎn)的尾節(jié)點(diǎn)和新節(jié)點(diǎn)的初節(jié)點(diǎn)是否是同一類型,如果是贵扰,則對(duì)它們兩個(gè)進(jìn)行patchVnode(patch過程).老節(jié)點(diǎn)的尾節(jié)點(diǎn)向前移動(dòng)一位仇穗,新節(jié)點(diǎn)初節(jié)點(diǎn)向后移動(dòng)一位。
    5.如果以上都不滿足戚绕,則創(chuàng)建新的dom節(jié)點(diǎn),newCh的startVnode被添加到oldStartVnode的前面纹坐,同時(shí)newStartIndex后移一位;

用圖來描述就是


第一輪diff

第二輪diff

第三輪diff

第四輪diff

第五輪diff

遍歷的過程結(jié)束后舞丛,newStartIdx > newEndIdx耘子,說明此時(shí)oldCh存在多余的節(jié)點(diǎn),那么最后就需要將oldCh的多余節(jié)點(diǎn)從parentElm中刪除球切。
如果oldStartIdx > oldEndIdx谷誓,說明此時(shí)newCh存在多余的節(jié)點(diǎn),那么最后就需要將newCh的多余節(jié)點(diǎn)添加到parentElm中吨凑。

diff遍歷的過程如下: (節(jié)點(diǎn)屬性中帶key的情況)

前四步還和上面的一樣
第五步:如果前四步都不滿足捍歪,則首先建立oldCh key和index索引的對(duì)應(yīng)關(guān)系。

  • 如果newStartVnode上存在這個(gè)key,但是oldKeyToIdx中不存在
    則創(chuàng)建新的dom節(jié)點(diǎn),newCh的startVnode被添加到oldStartVnode的前面鸵钝,同時(shí)newStartIndex后移一位费封;
  • 如果找到與newStartVnode key一致的oldVnode
    則先將這兩個(gè)節(jié)點(diǎn)進(jìn)行patchVnode(patch過程),然后將newStartVnode移到oldStartVnode的前面蒋伦,并在oldCh中刪除與newStartVnode key一致的oldVnode弓摘,然后新節(jié)點(diǎn)初節(jié)點(diǎn)向后移動(dòng)一位籽慢。再進(jìn)行遍歷硼瓣。

用圖來描述就是


第一輪diff

第二輪diff

第三輪diff

第四輪diff

第五輪diff

最后,由于newStartIndex>newEndIndex,所以newCh剩余的節(jié)點(diǎn)會(huì)被添加到parentElm中

總結(jié)

Virtual DOM 算法主要是實(shí)現(xiàn)上面三個(gè)概念:VNode牲证,diff研叫,patch
總結(jié)下來就是

1. 通過構(gòu)造VNode構(gòu)建虛擬DOM

2. 通過虛擬DOM構(gòu)建真正的DOM

3. 生成新的虛擬DOM

4. 比較兩棵虛擬DOM樹的不同.從根節(jié)點(diǎn)開始比較锤窑,diff過程

5. 在真正的DOM元素上應(yīng)用變更,patch

其中patch的過程中遇到兩個(gè)節(jié)點(diǎn)有子節(jié)點(diǎn),則對(duì)其子節(jié)點(diǎn)進(jìn)行diff嚷炉。
而diff的過程又會(huì)調(diào)用patch渊啰。

參考鏈接:
知乎:如何理解虛擬DOM?
Vue原理解析之Virtual Dom
Vue 2.0 的 virtual-dom 實(shí)現(xiàn)簡(jiǎn)析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市申屹,隨后出現(xiàn)的幾起案子绘证,更是在濱河造成了極大的恐慌,老刑警劉巖哗讥,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚷那,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡杆煞,警方通過查閱死者的電腦和手機(jī)魏宽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門腐泻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人队询,你說我怎么就攤上這事派桩。” “怎么了蚌斩?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵铆惑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我凳寺,道長(zhǎng)鸭津,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任肠缨,我火速辦了婚禮逆趋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晒奕。我一直安慰自己闻书,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布脑慧。 她就那樣靜靜地躺著魄眉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闷袒。 梳的紋絲不亂的頭發(fā)上坑律,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音囊骤,去河邊找鬼晃择。 笑死,一個(gè)胖子當(dāng)著我的面吹牛也物,可吹牛的內(nèi)容都是我干的宫屠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼滑蚯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浪蹂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起告材,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤坤次,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后创葡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浙踢,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年灿渴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洛波。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骚露,死狀恐怖蹬挤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棘幸,我是刑警寧澤焰扳,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站误续,受9級(jí)特大地震影響吨悍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹋嵌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一育瓜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栽烂,春花似錦躏仇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怀喉,卻和暖如春书妻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躬拢。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工躲履, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人估灿。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓崇呵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親馅袁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子域慷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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