vue2.0源碼解析 - 依賴收集

依賴收集
通過上一節(jié)的分析我們了解 Vue 會(huì)把普通對(duì)象變成響應(yīng)式對(duì)象胸完,響應(yīng)式對(duì)象 getter 相關(guān)的邏輯就是做依賴收集郊愧,這一節(jié)我們來詳細(xì)分析這個(gè)過程誓禁。
我們先來回顧一下 getter 部分的邏輯:

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep() // ???

  const property = Object.getOwnPropertyDescriptor(obj, key) // Object.getOwnPropertyDescriptor() 方法返回指定對(duì)象上一個(gè)自有屬性對(duì)應(yīng)的屬性描述符肉盹。(自有屬性指的是直接賦予該對(duì)象的屬性,不需要從原型鏈上進(jìn)行查找的屬性)
  if (property && property.configurable === false) { // 當(dāng)且僅當(dāng)指定對(duì)象的屬性描述可以被改變或者屬性可被刪除時(shí)豁延,為true。
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get // 拿到屬性原生get
  const setter = property && property.set
  // val不是對(duì)象則不添加Observe
  let childOb = !shallow && observe(val) // observe(val) 判斷子屬性val是否還是一個(gè)對(duì)象腊状,是的話再次調(diào)用 observe(val)
  Object.defineProperty(obj, key, { // 給key定義響應(yīng)式屬性诱咏;
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()  // 依賴收集
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // 派發(fā)更新
    }
  })
}

這段代碼我們只需要關(guān)注 2 個(gè)地方,一個(gè)是 const dep = new Dep() 實(shí)例化一個(gè) Dep 的實(shí)例缴挖,另一個(gè)是在 get 函數(shù)中通過 dep.depend 做依賴收集袋狞,這里還有個(gè)對(duì) childObj 判斷的邏輯,我們之后會(huì)介紹它的作用。

Dep

Dep 是整個(gè) getter 依賴收集的核心苟鸯,它的定義在 src/core/observer/dep.js 中:

/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}

Dep 是一個(gè) Class法焰,它定義了一些屬性和方法,這里需要特別注意的是它有一個(gè)靜態(tài)屬性 target倔毙,這是一個(gè)全局唯一 Watcher埃仪,這是一個(gè)非常巧妙的設(shè)計(jì),因?yàn)樵谕粫r(shí)間只能有一個(gè)全局的 Watcher 被計(jì)算陕赃,另外它的自身屬性 subs 也是 Watcher 的數(shù)組卵蛉。
Dep 實(shí)際上就是對(duì) Watcher 的一種管理,Dep 脫離 Watcher 單獨(dú)存在是沒有意義的么库,為了完整地講清楚依賴收集過程傻丝,我們有必要看一下 Watcher 的一些相關(guān)實(shí)現(xiàn),它的定義在 src/core/observer/watcher.js 中:

Watcher
/* @flow */

import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError
} from '../util/index'

import type { ISet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

Watcher 是一個(gè) Class诉儒,在它的構(gòu)造函數(shù)中葡缰,定義了一些和 Dep 相關(guān)的屬性:
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
其中,this.depsthis.newDeps 表示 Watcher 實(shí)例持有的 Dep 實(shí)例的數(shù)組忱反;而 this.depIdsthis.newDepIds 分別代表 this.depsthis.newDepsidSet(這個(gè) SetES6 的數(shù)據(jù)結(jié)構(gòu)泛释,它的實(shí)現(xiàn)在 src/core/util/env.js 中)。那么這里為何需要有 2 個(gè) Dep 實(shí)例數(shù)組呢温算,稍后我們會(huì)解釋怜校。
Watcher 還定義了一些原型的方法,和依賴收集相關(guān)的有 get注竿、addDepcleanupDeps 方法茄茁,單個(gè)介紹它們的實(shí)現(xiàn)不方便理解,我會(huì)結(jié)合整個(gè)依賴收集的過程把這幾個(gè)方法講清楚巩割。

過程分析

之前我們介紹當(dāng)對(duì)數(shù)據(jù)對(duì)象的訪問會(huì)觸發(fā)他們的 getter 方法裙顽,那么這些對(duì)象什么時(shí)候被訪問呢?還記得之前我們介紹過 Vuemount 過程是通過 mountComponent 函數(shù)宣谈,其中有一段比較重要的邏輯愈犹,大致如下:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

當(dāng)我們?nèi)?shí)例化一個(gè)渲染 watcher 的時(shí)候,首先進(jìn)入 watcher 的構(gòu)造函數(shù)邏輯蒲祈,然后會(huì)執(zhí)行它的 this.get() 方法甘萧,進(jìn)入 get 函數(shù),首先會(huì)執(zhí)行:


  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) // this.getter 對(duì)應(yīng)就是 updateComponent 函數(shù)梆掸,這實(shí)際上就是在執(zhí)行:
vm._update(vm._render(), hydrating)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

pushTarget(this)
pushTarget 的定義在 src/core/observer/dep.js 中:

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target // _target Watcher.js Watcher 類
}

實(shí)際上就是把 Dep.target 賦值為當(dāng)前的渲染 watcher 并壓棧(為了恢復(fù)用)扬卷。接著又執(zhí)行了:
value = this.getter.call(vm, vm)
this.getter 對(duì)應(yīng)就是 updateComponent 函數(shù),這實(shí)際上就是在執(zhí)行:
vm._update(vm._render(), hydrating)
它會(huì)先執(zhí)行 vm._render() 方法酸钦,因?yàn)橹胺治鲞^這個(gè)方法會(huì)生成 渲染 VNode怪得,并且在這個(gè)過程中會(huì)對(duì) vm 上的數(shù)據(jù)訪問,這個(gè)時(shí)候就觸發(fā)了數(shù)據(jù)對(duì)象的 getter
那么每個(gè)對(duì)象值的 getter 都持有一個(gè) dep徒恋,在觸發(fā) getter 的時(shí)候會(huì)調(diào)用 dep.depend() 方法蚕断,也就會(huì)執(zhí)行 Dep.target.addDep(this)

剛才我們提到這個(gè)時(shí)候 Dep.target 已經(jīng)被賦值為渲染 watcher入挣,那么就執(zhí)行到 addDep 方法:

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

這時(shí)候會(huì)做一些邏輯判斷(保證同一數(shù)據(jù)不會(huì)被添加多次)后執(zhí)行 dep.addSub(this)亿乳,那么就會(huì)執(zhí)行 this.subs.push(sub),也就是說把當(dāng)前的 watcher 訂閱到這個(gè)數(shù)據(jù)持有的 depsubs 中径筏,這個(gè)目的是為后續(xù)數(shù)據(jù)變化時(shí)候能通知到哪些 subs 做準(zhǔn)備葛假。
所以在 vm._render() 過程中,會(huì)觸發(fā)所有數(shù)據(jù)的 getter滋恬,這樣實(shí)際上已經(jīng)完成了一個(gè)依賴收集的過程聊训。那么到這里就結(jié)束了么,其實(shí)并沒有恢氯,再完成依賴收集后带斑,還有幾個(gè)邏輯要執(zhí)行,首先是:

      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }

這個(gè)是要遞歸去訪問 value勋拟,觸發(fā)它所有子項(xiàng)的 getter勋磕,這個(gè)之后會(huì)詳細(xì)講。接下來執(zhí)行:
popTarget()
popTarget 的定義在 src/core/observer/dep.js 中:

export function popTarget () {
  Dep.target = targetStack.pop() // pop() 方法用于刪除并返回?cái)?shù)組的最后一個(gè)元素
}

實(shí)際上就是把 Dep.target恢復(fù)成上一個(gè)狀態(tài)指黎,因?yàn)楫?dāng)前 vm 的數(shù)據(jù)依賴收集已經(jīng)完成朋凉,那么對(duì)應(yīng)的渲染Dep.target 也需要改變。最后執(zhí)行:
this.cleanupDeps()
其實(shí)很多人都分析過并了解到 Vue 有依賴收集的過程醋安,但我?guī)缀鯖]有看到有人分析依賴清空的過程,其實(shí)這是大部分同學(xué)會(huì)忽視的一點(diǎn)墓毒,也是 Vue 考慮特別細(xì)的一點(diǎn)吓揪。

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

考慮到 Vue 是數(shù)據(jù)驅(qū)動(dòng)的,所以每次數(shù)據(jù)變化都會(huì)重新 render所计,那么 vm._render()方法又會(huì)再次執(zhí)行柠辞,并再次觸發(fā)數(shù)據(jù)的 getters,所以 Wathcer 在構(gòu)造函數(shù)中會(huì)初始化 2 個(gè) Dep 實(shí)例數(shù)組主胧,newDeps 表示新添加的 Dep 實(shí)例數(shù)組叭首,而 deps 表示上一次添加的 Dep 實(shí)例數(shù)組。
在執(zhí)行 cleanupDeps 函數(shù)的時(shí)候踪栋,會(huì)首先遍歷 deps焙格,移除對(duì) dep 的訂閱,然后把 newDepIdsdepIds 交換夷都,newDepsdeps 交換眷唉,并把 newDepIdsnewDeps 清空。
那么為什么需要做 deps 訂閱的移除呢,在添加 deps 的訂閱過程冬阳,已經(jīng)能通過 id 去重避免重復(fù)訂閱了蛤虐。
考慮到一種場景,我們的模板會(huì)根據(jù)v-if去渲染不同子模板 ab肝陪,當(dāng)我們滿足某種條件的時(shí)候渲染 a 的時(shí)候驳庭,會(huì)訪問到 a 中的數(shù)據(jù),這時(shí)候我們對(duì) a 使用的數(shù)據(jù)添加了 getter氯窍,做了依賴收集嚷掠,那么當(dāng)我們?nèi)バ薷?a 的數(shù)據(jù)的時(shí)候,理應(yīng)通知到這些訂閱者荞驴。那么如果我們一旦改變了條件渲染了 b 模板不皆,又會(huì)對(duì) b 使用的數(shù)據(jù)添加了 getter,如果我們沒有依賴移除的過程熊楼,那么這時(shí)候我去修改 a 模板的數(shù)據(jù)霹娄,會(huì)通知 a 數(shù)據(jù)的訂閱的回調(diào),這顯然是有浪費(fèi)的鲫骗。
因此 Vue 設(shè)計(jì)了在每次添加完新的訂閱犬耻,會(huì)移除掉舊的訂閱,這樣就保證了在我們剛才的場景中执泰,如果渲染 b 模板的時(shí)候去修改 a 模板的數(shù)據(jù)枕磁,a 數(shù)據(jù)訂閱回調(diào)已經(jīng)被移除了,所以不會(huì)有任何浪費(fèi)术吝,真的是非常贊嘆 Vue 對(duì)一些細(xì)節(jié)上的處理计济。
總結(jié)

通過這一節(jié)的分析,我們對(duì) Vue 數(shù)據(jù)的依賴收集過程已經(jīng)有了認(rèn)識(shí)排苍,并且對(duì)這其中的一些細(xì)節(jié)做了分析沦寂。收集依賴的目的是為了當(dāng)這些響應(yīng)式數(shù)據(jù)發(fā)送變化,觸發(fā)它們的 setter 的時(shí)候淘衙,能知道應(yīng)該通知哪些訂閱者去做相應(yīng)的邏輯處理传藏,我們把這個(gè)過程叫派發(fā)更新,其實(shí) WatcherDep 就是一個(gè)非常經(jīng)典的觀察者設(shè)計(jì)模式的實(shí)現(xiàn)彤守,下一節(jié)我們來詳細(xì)分析一下派發(fā)更新的過程毯侦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市具垫,隨后出現(xiàn)的幾起案子侈离,更是在濱河造成了極大的恐慌,老刑警劉巖做修,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霍狰,死亡現(xiàn)場離奇詭異抡草,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔗坯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門康震,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宾濒,你說我怎么就攤上這事腿短。” “怎么了绘梦?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵橘忱,是天一觀的道長。 經(jīng)常有香客問我卸奉,道長钝诚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任榄棵,我火速辦了婚禮凝颇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疹鳄。我一直安慰自己拧略,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布瘪弓。 她就那樣靜靜地躺著垫蛆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腺怯。 梳的紋絲不亂的頭發(fā)上袱饭,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音瓢喉,去河邊找鬼宁赤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栓票,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愕够,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼走贪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惑芭?” 一聲冷哼從身側(cè)響起坠狡,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遂跟,沒想到半個(gè)月后逃沿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婴渡,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年凯亮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了边臼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假消,死狀恐怖柠并,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情富拗,我是刑警寧澤臼予,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站啃沪,受9級(jí)特大地震影響粘拾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜创千,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一缰雇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧签餐,春花似錦寓涨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冠摄,卻和暖如春糯崎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背河泳。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工沃呢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拆挥。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓薄霜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纸兔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惰瓜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345