Vue 數(shù)據(jù)響應(yīng)式原理

前言

Vue.js 的核心包括一套“響應(yīng)式系統(tǒng)”呐籽。

“響應(yīng)式”锋勺,是指當數(shù)據(jù)改變后,Vue 會通知到使用該數(shù)據(jù)的代碼狡蝶。例如庶橱,視圖渲染中使用了數(shù)據(jù),數(shù)據(jù)改變后贪惹,視圖也會自動更新苏章。

舉個簡單的例子,對于模板:

<div id="root">{{ name }}</div>

創(chuàng)建一個 Vue 組件:

var vm = new Vue({
  el: '#root',
  data: {
    name: 'luobo'
  }
})

代碼執(zhí)行后奏瞬,頁面上對應(yīng)位置會顯示:luobo枫绅。

如果想改變顯示的名字,只需要執(zhí)行:

vm.name = 'tang'

這樣頁面上就會顯示修改后的名字了丝格,并不需要去手動修改 DOM 更新數(shù)據(jù)撑瞧。

接下來,我們就一起深入了解 Vue 的數(shù)據(jù)響應(yīng)式原理显蝌,搞清楚響應(yīng)式的實現(xiàn)機制预伺。

基本概念

Vue 的響應(yīng)式订咸,核心機制是 觀察者模式

數(shù)據(jù)是被觀察的一方酬诀,發(fā)生改變時脏嚷,通知所有的觀察者,這樣觀察者可以做出響應(yīng)瞒御,比如父叙,重新渲染然后更新視圖。

我們把依賴數(shù)據(jù)的觀察者稱為 watcher肴裙,那么這種關(guān)系可以表示為:

data -> watcher

數(shù)據(jù)可以有多個觀察者趾唱,怎么記錄這種依賴關(guān)系呢?

Vue 通過在 data 和 watcher 間創(chuàng)建一個 dep 對象蜻懦,來記錄這種依賴關(guān)系:

data - dep -> watcher

dep 的結(jié)構(gòu)很簡單甜癞,除了唯一標識屬性 id,另一個屬性就是用于記錄所有觀察者的 subs:

  • id - number
  • subs - [Watcher]

再來看 watcher宛乃。

Vue 中 watcher 的觀察對象,確切來說是一個求值表達式征炼,或者函數(shù)。這個表達式或者函數(shù)谆奥,在一個 Vue 實例的上下文中求值或執(zhí)行。這個過程中酸些,使用到數(shù)據(jù)空骚,也就是 watcher 所依賴的數(shù)據(jù)擂仍。用于記錄依賴關(guān)系的屬性是 deps熬甚,對應(yīng)的是由 dep 對象組成的數(shù)組逢渔,對應(yīng)所有依賴的數(shù)據(jù)。而表達式或函數(shù)乡括,最終會作為求值函數(shù)記錄到 getter 屬性,每次求值得到的結(jié)果記錄在 value 屬性:

  • vm - VueComponent
  • deps - [Dep]
  • getter - function
  • value - *

另外诲泌,還有一個重要的屬性 cb,記錄回調(diào)函數(shù)哀蘑,當 getter 返回的值與當前 value 不同時被調(diào)用:

  • cb - function

我們通過示例來整理下 data、dep绘迁、watcher 的關(guān)系:

var vm = new Vue({
  data: {
    name: 'luobo',
    age: 18
  }
})

var userInfo = function () {
  return this.name + ' - ' + this.age
}

var onUserInfoChange = function (userInfo) {
  console.log(userInfo)
}

vm.$watch(userInfo, onUserInfoChange)

上面代碼首先創(chuàng)建了一個新的 Vue 實例對象 vm,包含兩個數(shù)據(jù)字段:name棠赛、age膛腐。對于這兩個字段,Vue 會分別創(chuàng)建對應(yīng)的 dep 對象哲身,用于記錄依賴該數(shù)據(jù)的 watcher。

然后定義了一個求值函數(shù) userInfo膀值,注意元镀,這個函數(shù)會在對應(yīng)的 Vue 示例上下文中執(zhí)行欣簇,也就是說翘狱,執(zhí)行時的 this 對應(yīng)的就是 vm砰苍。

回調(diào)函數(shù) onUserInfoChange 只是打印出新的 watcher 得到的新的值,由 userInfo 執(zhí)行后生成赚导。

通過 vm.$watch(userInfo, onUserInfoChange),將 vm吼旧、getter、cb 集成在一起創(chuàng)建了新的 watcher掂为。創(chuàng)建成功后员串,watcher 在內(nèi)部已經(jīng)記錄了依賴關(guān)系,watcher.deps 中記錄了 vm 的 name寸齐、age 對應(yīng)的 dep 對象(因為 userInfo 中使用了這兩個數(shù)據(jù))欲诺。

接下來抄谐,我們修改數(shù)據(jù):

vm.name = 'tang'

執(zhí)行后,控制臺會輸出:

tang - 18

同樣瞧栗,如果修改 age 的值斯稳,也會最終觸發(fā) onUserInfoChange 打印出新的結(jié)果。

用個簡單的圖來整理下上面的關(guān)系:

vm.name -- dep1
vm.age  -- dep2
watcher.deps --> [dep1, dep2]

修改 vm.name 后迹恐,dep1 通知相關(guān)的 watcher,然后 watcher 執(zhí)行 getter憎茂,得到新的 value锤岸,再將新的 value 傳給 cb:

vm.name -> dep1 -> watcher -> getter -> value -> cb

可能你也注意到了,上面例子中的 userInfo拳氢,貌似就是計算屬性的作用嘛:

var vm = new Vue({
  data: {
    name: 'luobo',
    age: 18
  },
  computed: {
    userInfo() {
      return this.name + ' - ' + this.age
    }
  }
})

其實蛋铆,計算屬性在內(nèi)部也是基于 watcher 實現(xiàn)的,每個計算屬性對應(yīng)一個 watcher留特,其 getter 也就是計算屬性的聲明函數(shù)玛瘸。
不過,計算屬性對應(yīng)的 watcher 與直接通過 vm.$watch() 創(chuàng)建的 watcher 略有不同右核,畢竟如果沒有地方使用到這個計算屬性渺绒,數(shù)據(jù)改變時都重新進行計算會有點浪費,這個在本文后面會講到。

上面描述了 data针炉、dep扳抽、watcher 的關(guān)系殖侵,但是問題來了镰烧,這種依賴關(guān)系是如何建立的呢?數(shù)據(jù)改變后茉唉,又是如何通知 watcher 的呢结执?

接下來我們深入 Vue 源碼,搞清楚這兩個問題懂傀。

建立依賴關(guān)系

Vue 源碼版本 v2.5.13蜡感,文中摘錄的部分代碼為便于分析進行了簡化或改寫。

響應(yīng)式的核心邏輯犀斋,都在 Vue 項目的 “vue/src/core/observer” 目錄下面杈笔。

我們還是先順著前面示例代碼來捋一遍,首先是 Vue 實例化過程:

var vm = new Vue(/* ... */)

跟將傳入的 data 進行響應(yīng)式初始化相關(guān)的代碼球榆,在 “vue/src/core/instance/state.js” 文件中:

observer/state.js#L149

// new Vue() -> ... -> initState() -> initData()
observe(data)

函數(shù) observe() 的目的是讓傳入的整個對象成為響應(yīng)式的持钉,它會遍歷對象的所有屬性篱昔,然后執(zhí)行:

observer/index.js#L64

// observe() -> new Observer() -> observer.walk()
defineReactive(obj, key, value)

defineReactive() 就是用于定義響應(yīng)式數(shù)據(jù)的核心函數(shù)。它主要做的事情包括:

  • 新建一個 dep 對象空执,與當前數(shù)據(jù)對應(yīng)
  • 通過 Object.defineProperty() 重新定義對象屬性穗椅,配置屬性的 set、get门坷,從而數(shù)據(jù)被獲取、設(shè)置時可以執(zhí)行 Vue 的代碼

OK默蚌,先到這里绸吸,關(guān)于 Vue 實例化告一段落。

需要要注意的是温数,傳入 Vue 的 data 的所有屬性蜻势,會被代理到新創(chuàng)建的 Vue 實例對象上,這樣通過 vm.name 進行操作的其實就是 data.name握玛,這也是借助 Object.defineProperty() 實現(xiàn)的挠铲。

再來看 watcher 的創(chuàng)建過程:

vm.$watch(userInfo, onUserInfoChange)

上述代碼執(zhí)行后,會調(diào)用:

instance/state.js#L346

// Vue.prototype.$watch()
new Watcher(vm, expOrFn, cb, options)

也就是:

new Watcher(vm, userInfo, onUserInfoChange, {/* 略 */})

在 watcher 對象創(chuàng)建過程中安聘,除了記錄 vm瓢棒、getter、cb 以及初始化各種屬性外念颈,最重要的就是調(diào)用了傳入的 getter 函數(shù):

observer/watcher.js#L103

// new Watcher() -> watcher.get()
value = this.getter.call(vm, vm)

在 getter 函數(shù)的執(zhí)行過程中连霉,獲取讀取需要的數(shù)據(jù),于是觸發(fā)了前面通過 defineReactive() 配置的 get 方法:

if (Dep.target) {
  dep.depend()
}

這是做什么呢窟感?

回到 watcher.get() 方法歉井,在執(zhí)行 getter 函數(shù)的前后,分別有如下代碼:

pushTarget(this)
// ... 
value = this.getter.call(vm, vm)
// ...
popTarget()

pushTarget() 將當前 watcher 設(shè)置為 Dep.target谍夭,這樣在執(zhí)行到 vm.name 進一步執(zhí)行對應(yīng)的 get 方法時憨募,Dep.target 的值就是這里的 watcher菜谣,然后通過 dep.depend() 就建立了依賴關(guān)系。

dep.depend() 執(zhí)行的邏輯就比較好推測了尾膊,將 watcher(通過 Dep.target 引用到)記錄到 dep.subs 中冈敛,將 dep 記錄到 watcher.deps 中 —— 依賴關(guān)系建立了!

然后來看建立的依賴關(guān)系是如何使用的抓谴。

數(shù)據(jù)變更同步

繼續(xù)前面的例子癌压,執(zhí)行如下代碼時:

vm.name = 'tang'

會觸發(fā)通過 defineReactive() 配置的 set 方法,如果數(shù)據(jù)改變集侯,那么:

// defineReactive() -> set()
dep.notify()

通過 dep 對象來通知所有的依賴方法帜消,于是 dep 遍歷內(nèi)部的 subs 執(zhí)行:

// dep.notify()
watcher.update()

這樣 watcher 就被通知到了,知道了數(shù)據(jù)改變术健,從而繼續(xù)后續(xù)的處理粘衬。這里先不展開。

到這里勘伺,基本就搞清楚響應(yīng)式的基本機制了褂删,整理一下:

  • 通過 Object.defineProperty() 替換配置對象屬性的 set、get 方法缅帘,實現(xiàn)“攔截”
  • watcher 在執(zhí)行 getter 函數(shù)時觸發(fā)數(shù)據(jù)的 get 方法,從而建立依賴關(guān)系
  • 寫入數(shù)據(jù)時觸發(fā) set 方法逗栽,從而借助 dep 發(fā)布通知失暂,進而 watcher 進行更新

這樣再看 Vue 官方的圖就比較好理解了:

Vue 響應(yīng)式原理

圖片來源:https://vuejs.org/v2/guide/reactivity.html
上圖中左側(cè)是以組件渲染(render)作為 getter 函數(shù)來演示響應(yīng)式過程的弟塞,這其實就是 RenderWatcher 這種特殊類型 watcher 的作用機制,后面還會再講决记。

計算屬性

本文前面提到過計算屬性霉涨,在 Vue 中也是作為 watcher 進行處理的。計算屬性(ComputedWatcher)特殊的地方在于楼镐,它其實沒有 cb(空函數(shù))往枷,只有 getter,并且它的值只在被使用時才計算并緩存错洁。

什么意思呢屯碴?

首先,ComputedWatcher 在創(chuàng)建時忱叭,不會立即執(zhí)行 getter(lazy 選項值為 false)今艺,這樣一開始 ComputedWatcher 并沒有和使用到的數(shù)據(jù)建立依賴關(guān)系。

計算屬性在被“get”時撵彻,首先執(zhí)行預(yù)先定義的 ComputedGetter 函數(shù),這里有一段特殊邏輯:

instance/state.js#L238

function computedGetter () {
  if (watcher.dirty) {
    watcher.evaluate()
  }
  if (Dep.target) {
    watcher.depend()
  }
  return watcher.value
}

首先判斷 watcher 是不是 dirty 狀態(tài)轴合,什么意思呢碗短?

計算屬性對應(yīng)的 watcher 初始創(chuàng)建的時候,并沒有執(zhí)行 getter,這個時候就會設(shè)置 dirty 為 true携栋,這樣當前獲取計算屬性的值的時候婉支,會執(zhí)行 getter 得到 value,然后標記 dirty 為 false蝌以。這樣后續(xù)再獲取計算屬性的值何之,不需要再計算(執(zhí)行 getter),直接就能返回緩存的 value徊件。

另外蒜危,計算屬性的 watcher 在執(zhí)行 watcher.evaluate() 是,進一步調(diào)用 watcher.get()部翘,從而進行依賴收集响委。而依賴的數(shù)據(jù)在改變后,會通知計算屬性的 watcher表牢,但是 watcher 只是標記自身為 dirty贝次,而不計算。這樣的好處是可以減小開銷敲茄,只在有地方需要計算屬性的值時才執(zhí)行計算。

如果依賴的數(shù)據(jù)發(fā)生變更掏父,計算屬性只是標記 dirty 為 true秆剪,會不會有問題呢?

解決這個問題的是上面代碼的這一部分:

if (Dep.target) {
  watcher.depend()
}

也就是說陶缺,如果當前有在收集依賴的 watcher洁灵,那么當前計算屬性的 watcher 會間接地通過 watcher.depend() 將依賴關(guān)系“繼承”給這個 watcher(watcher.depend() 內(nèi)部是對每個 watcher.deps 記錄的 dep 執(zhí)行 dep.depend() 從而讓依賴數(shù)據(jù)與當前的 watcher 建立依賴關(guān)系)徽千。

所以,依賴數(shù)據(jù)改變双抽,依賴計算屬性的 watcher 會直接得到通知荠诬,再來獲取計算屬性的值的時候,計算屬性才進行計算求值方椎。

所以钧嘶,依賴計算屬性的 watcher 可以視為依賴 watcher 的 watcher。這樣的 watcher 在 Vue 中最常見不過闸拿,那就是 RenderWatcher书幕。

RenderWatcher 及異步更新

相信讀過前文,你應(yīng)該對 Vue 響應(yīng)式原理有基本的認識苛骨。那么 Vue 是如何將其運用到視圖更新中的呢?答案就是這里要講的 RenderWatcher俐筋。

RenderWatcher 首先是 watcher严衬,只不過和計算屬性對應(yīng)的 ComputedWatcher 類似,它也有些特殊的行為粱挡。

RenderWatcher 的創(chuàng)建俄精,在函數(shù) mountComponent 中:

// Vue.prototype.$mount() -> mountComponent()
let updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

核心代碼就在這里了嘀倒。這個 watcher 就是 Vue 實例對象唯一的 RenderWatcher局冰,在 watcher 構(gòu)造函數(shù)中,會記錄到 vm._watcher 上(普通 watcher 只會記錄到 vm._watchers 數(shù)組中)碳胳。

這個 watcher 也會在創(chuàng)建的最后執(zhí)行 watcher.get()沫勿,也就是執(zhí)行 getter 收集依賴的過程产雹。而在這里,getter 就是 updateComponent蔓挖,也就是說瘟判,執(zhí)行了渲染+更新 DOM!并且篮撑,這個過程中使用到的數(shù)據(jù)也被收集了依賴關(guān)系匆瓜。

那么未蝌,理所當然地质欲,在 render() 中使用到數(shù)據(jù)嘶伟,發(fā)生改變,自然會通知到 RenderWatcher绊袋,從而最終更新視圖铸鹰!

不過,這里會有個疑問:如果進行多次數(shù)據(jù)修改展姐,那么豈不是要頻繁執(zhí)行 DOM 更新剖毯?

這里就涉及到 RenderWatcher 的特殊功能了:異步更新

結(jié)合前面內(nèi)容擂达,我們知道數(shù)據(jù)更新后胶滋,依賴該數(shù)據(jù)的 watcher 會執(zhí)行 watcher.update()究恤,這個在前文中沒有展開,現(xiàn)在我們來看下這個方法:

observer/watcher.js#L161

if (this.lazy) {
  this.dirty = true
} else if (this.sync) {
  this.run()
} else {
  queueWatcher(this)
}

第一種情況唤蔗,lazy 為 true窟赏,也就是計算屬性,上一節(jié)已經(jīng)提到過棍掐,只是標記 dirty 為 true拷况,并不立即計算掘殴,不再贅述奏寨。sync 為 true 的情況鹰服,這里也不管,不過看起來也很簡單套菜,就是立即執(zhí)行計算嘛设易。

最后的情況顿肺,就是這里 RenderWatcher 的場景,并不立即執(zhí)行于购,也不是像計算屬性那樣標記為 dirty 就完了知染,而是放到了一個隊列中斑胜。

這個隊列是干什么的呢?

相關(guān)代碼在 observer/scheduler.js 中止潘,簡單來說凭戴,就是實現(xiàn)了異步更新。

理解其實現(xiàn)者冤,首先要對瀏覽器的事件循環(huán)(Event Loop)機制有一定了解档痪。如果你對事件循環(huán)機制不是很了解,可以看下面這篇文章:

JavaScript 運行機制詳解:再談Event Loop - 阮一峰

事件循環(huán)機制其實有點復(fù)雜愿汰,但只有理解事件循環(huán),才能對這里 Vue 異步更新的方案有深入的認識摇予。

基于事件循環(huán)機制吗跋,RenderWatcher 將其 getter小腊,也就是 updateComponent 函數(shù)異步執(zhí)行,并且本缠,多次觸發(fā)
RenderWatcher 的 update()入问,最終也只會執(zhí)行一次 updateComponent,這樣也就解決了性能問題楣黍。

不過棱烂,隨之而來的新問題是颊糜,修改完數(shù)據(jù),不能直接反應(yīng)到 DOM 上业筏,而是要等異步更新執(zhí)行過后才可以鸟赫,這也是為什么 Vue 提供了 nextTick() 接口,并且要求開發(fā)者將對 DOM 的操作放到 nextTick() 回調(diào)中執(zhí)行的原因台谢。

Vuex岁经、Vue-Router

再來看 Vue 套裝中的 Vuex蒿偎、Vue-Router怀读,它們也是基于 Vue 的響應(yīng)式機制實現(xiàn)功能骑脱。

先來看 Vuex叁丧,代碼版本 v3.0.1啤誊。

Vuex

在應(yīng)用了 Vuex 的應(yīng)用中,所有組件都可以通過 this.$store 來引用到全局的 store拥娄,并且在使用了 store 的數(shù)據(jù)后蚊锹,還能在數(shù)據(jù)改變后得到同步,這其實就是響應(yīng)式的應(yīng)用了稚瘾。

首先看 this.$store 的實現(xiàn)牡昆,這個其實是通過全局 mixin 實現(xiàn),代碼在:

src/mixin.js#L26

this.$store = options.store || options.parent.$store

這樣在每個組件的 beforeCreate 時摊欠,會執(zhí)行 $store 屬性的初始化丢烘。

而 store 數(shù)據(jù)的響應(yīng)式處理些椒,則是通過實例化一個 Vue 對象實現(xiàn):

src/store.js#L251

// new Store() -> resetStoreVM()
store._vm = new Vue({
  data: {
    $$state: state
  },
  computed // 對應(yīng) store.getters
})

結(jié)合前文的介紹播瞳,這里就很好理解了。因為 state 以及處理為響應(yīng)式數(shù)據(jù)免糕,而 getters 也創(chuàng)建為計算屬性赢乓,所以對這些數(shù)據(jù)的使用,就建立依賴關(guān)系石窑,從而可以響應(yīng)數(shù)據(jù)改變了牌芋。

Vue-Router

Vue-Router 中,比較重要的數(shù)據(jù)是 $route尼斧,即當前的頁面路由數(shù)據(jù),在路由改變的時候试吁,需要替換展示不同組件(router-view 組件實現(xiàn))棺棵。

vm.$route 實踐上是來自 Vue.prototype,但其對應(yīng)的值熄捍,最終對應(yīng)到的是 router.history.current烛恤。

結(jié)合前面的分析,這里的 history.current 肯定得是響應(yīng)式數(shù)據(jù)余耽,所以缚柏,來找下對其進行初始化的地方,其實是在全局 mixin 的 beforeCreate 這里:

v2.8.1/src/install.js#L27

// beforeCreate
Vue.util.defineReactive(this, '_route', this._router.history.current)

這樣 this._route 就是響應(yīng)式的了碟贾,那么如果頁面路由改變币喧,又是如何修改這里的 _route 的呢轨域?

答案在 VueRouter 的 init() 這里:

history.listen(route => {
  this.apps.forEach((app) => {
    app._route = route
  })
})

一個 router 對象可能和多個 vue 實例對象(這里叫作 app)關(guān)聯(lián),每次路由改變會通知所有的實例對象杀餐。

再來看使用 vm.$route 的地方干发,也就是 VueRouter 的兩個組件:

  • <router-link>
  • <router-view>

兩個組件都是在 render() 中,與 $route 建立了依賴關(guān)系史翘,根據(jù) route 的值進行渲染枉长。這里具體過程就不展開了,感興趣可以看下相關(guān)源碼(v2.8.1/src/components)琼讽,原理方面在 RenderWatcher 一節(jié)已經(jīng)介紹過必峰。

實踐:watch-it

了解了以上這么多,也想自己試試钻蹬,把 Vue 響應(yīng)式相關(guān)的核心邏輯剝離出來吼蚁,做一個單純的數(shù)據(jù)響應(yīng)式的庫。由于只關(guān)注數(shù)據(jù)脉让,所以在剝離過程中桂敛,將與 Vue 組件/實例對象相關(guān)的部分都移除了,包括 watcher.vm 也不再需要溅潜,這樣 watcher.getter 計算時不再指定上下文對象术唬。

感興趣,想直接看代碼的滚澜,可以前往 luobotang/watch-it粗仓。

watch-it 只包括數(shù)據(jù)響應(yīng)式相關(guān)的功能,暴露了4個接口:

  • defineReactive(obj, key, val):為對象配置一個響應(yīng)式數(shù)據(jù)屬性
  • observe(obj):將一個數(shù)據(jù)對象配置為響應(yīng)式设捐,內(nèi)部對所有的屬性執(zhí)行 defineReactive
  • defineComputed(target, key, userDef):為對象配置一個計算屬性借浊,內(nèi)部創(chuàng)建了 watcher
  • watch(fn, cb, options):監(jiān)聽求值函數(shù)中數(shù)據(jù)改變,變化時調(diào)用 cb萝招,內(nèi)部創(chuàng)建了 watcher

來看一個使用示例:

const { observe, watch } = require('@luobotang/watch-it')

const data = {
  name: 'luobo',
  age: 18
}

observe(data)

const userInfo = function() {
  return data.name + ' - ' + data.age
}

watch(userInfo, (value) => console.log(value))

這樣蚂斤,當數(shù)據(jù)修改時,通過會打印出新的 userInfo 的值槐沼。

去除虛擬 DOM曙蒸,只通過響應(yīng)式機制,我還構(gòu)建了一個簡單的 Vue岗钩,并實現(xiàn)了一個 DEMO:

watch-it/example/

源碼在這里:

luobotang/watch-it/example/vue.js

總結(jié)

OK纽窟,以上就是有關(guān) Vue 響應(yīng)式原理的全部了,當然兼吓,只是我的理解和實踐臂港。

在梳理和寫下這些內(nèi)容的過程中,我收獲很多,也希望內(nèi)容能夠?qū)δ阌兴鶐椭?/p>

水平有限审孽,錯漏難免县袱,歡迎指出。

最后瓷胧,感謝閱讀显拳!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搓萧,隨后出現(xiàn)的幾起案子杂数,更是在濱河造成了極大的恐慌,老刑警劉巖瘸洛,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揍移,死亡現(xiàn)場離奇詭異,居然都是意外死亡反肋,警方通過查閱死者的電腦和手機那伐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來石蔗,“玉大人罕邀,你說我怎么就攤上這事⊙啵” “怎么了诉探?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棍厌。 經(jīng)常有香客問我肾胯,道長,這世上最難降的妖魔是什么耘纱? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任敬肚,我火速辦了婚禮,結(jié)果婚禮上束析,老公的妹妹穿的比我還像新娘艳馒。我一直安慰自己,他們只是感情好员寇,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布弄慰。 她就那樣靜靜地躺著,像睡著了一般丁恭。 火紅的嫁衣襯著肌膚如雪曹动。 梳的紋絲不亂的頭發(fā)上斋日,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天牲览,我揣著相機與錄音,去河邊找鬼。 笑死第献,一個胖子當著我的面吹牛贡必,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庸毫,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼仔拟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了飒赃?” 一聲冷哼從身側(cè)響起利花,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎载佳,沒想到半個月后炒事,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蔫慧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年挠乳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姑躲。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡睡扬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出黍析,到底是詐尸還是另有隱情卖怜,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布橄仍,位于F島的核電站韧涨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏侮繁。R本人自食惡果不足惜虑粥,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宪哩。 院中可真熱鬧娩贷,春花似錦、人聲如沸锁孟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽品抽。三九已至储笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圆恤,已是汗流浹背突倍。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羽历。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓焊虏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秕磷。 傳聞我的和親對象是個殘疾皇子诵闭,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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