Vue源碼閱讀(一)

vue簡(jiǎn)介和初始化過程

vue的源碼結(jié)構(gòu)如下


src
├── compiler        # 編譯相關(guān) 
├── core            # 核心代碼 
├── platforms       # 不同平臺(tái)的支持
├── server          # 服務(wù)端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代碼

Vue對(duì)象

在使用vue時(shí)我們知道都是使用new Vue(),來(lái)將vue的實(shí)例掛載到dom對(duì)象上從而運(yùn)用數(shù)據(jù)驅(qū)動(dòng)的方式來(lái)擴(kuò)展我們的代碼款违,我們首先來(lái)看一下Vue的定義
從源頭上看來(lái)自core目錄下的instance的index.js文件

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

在js中一切皆函數(shù)虐秋,其實(shí)Vue就是一個(gè)函數(shù)汉矿,在初始化的時(shí)候執(zhí)行原型鏈上的_init方法,vue沒有把所有的方法都寫在函數(shù)內(nèi)部般此,這樣從代碼上來(lái)說,每次實(shí)例化的時(shí)候不會(huì)生成重復(fù)的代碼
主要還是代碼結(jié)構(gòu)更清晰充边,利用mixin的概念唁毒,把每個(gè)模塊都抽離開,這樣代碼在結(jié)構(gòu)和擴(kuò)展性都有很大提高骡显,這里的每個(gè)mixin先不說疆栏,先看以一下整體結(jié)構(gòu),這里定義完還要被core里的index.js再次包裝調(diào)用initGlobalAPI(Vue)來(lái)初始化全局的api方法惫谤,在web下runtime文件夾下引用再次封裝壁顶,vue是分為運(yùn)行時(shí)可編譯和只運(yùn)行的版本,所以如果需要編譯溜歪,在Vue原型上添加了$mount方法若专,先來(lái)看一下initGlobalAPI,在instance中都是在原型鏈上擴(kuò)展方法,在這里是直接在Vue上擴(kuò)展靜態(tài)方法

function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

數(shù)據(jù)雙向綁定

我們?cè)谑褂胿ue時(shí)看一個(gè)最簡(jiǎn)單的例子


<div class="root">
  {{msg}}
</div>

var root = new Vue({
  el: '#root',
  data: {
    msg: 'hello'
  }
})

我們可以看到這樣簡(jiǎn)單的幾行就可以將數(shù)據(jù)綁定到dom對(duì)象上蝴猪,那內(nèi)部做了什么呢调衰,先來(lái)看一下前面說的膊爪,在實(shí)例化會(huì)調(diào)用原型鏈上的_init方法

Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // a uid
  vm._uid = uid++

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

  // a flag to avoid this being observed
  vm._isVue = true
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  // expose real self
  vm._self = vm
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

這個(gè)方法包括合并配置,初始化生命周期嚎莉,時(shí)間米酬,渲染,data趋箩,props等赃额,最后判斷有沒有傳入的el執(zhí)行$mount方法掛載到dom對(duì)象上
看一下compile版本的mount方法,先將之前定義的mount的方法緩存后邊使用叫确,判斷有沒有render方法跳芳,vue也可以寫類似react的render函數(shù),
如果沒有的話启妹,判斷有沒有模版字符串筛严,獲取模版字符串,或者直接獲取html字符串使用compileToFunctions編譯模版饶米,編譯好了之后主要是為了生成
render方法桨啃,無(wú)論使用單文件組件還是模版方法還是之前demo中的方法都必須經(jīng)過render方法,最后執(zhí)行之前緩存的mount方法檬输,這個(gè)方法是在runtime的index.js中定義的
這個(gè)實(shí)際上就是在執(zhí)行生命周期中的mount周期照瘾,mountComponent方法,我刪除了一下在dev環(huán)境的處理丧慈,這不影響邏輯析命,可以看出mount方法主要就是定義了一個(gè)Watcher來(lái)相應(yīng)組件的變化
執(zhí)行了初始化的一些生命周期,render方法主要就是用來(lái)根據(jù)模版字符串來(lái)生成虛擬dom元素逃默,類似react的jsx編譯成ReactElmemnt的鹃愤,可以看出兩個(gè)庫(kù)從設(shè)計(jì)的基礎(chǔ)架構(gòu)還是很像的

var mount = Vue.prototype.$mount; // 緩存mount方法
Vue.prototype.$mount = function (
  el,
  hydrating
) {
  el = el && query(el);

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    warn(
      "Do not mount Vue to <html> or <body> - mount to normal elements instead."
    );
    return this
  }

  var options = this.$options;
  // resolve template/el and convert to render function
  if (!options.render) { // 如果沒有render方法
    var template = options.template;
    if (template) { // 如果存在template模版文件
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template);
          /* istanbul ignore if */
          if (!template) {
            warn(
              ("Template element not found or is empty: " + (options.template)),
              this
            );
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        {
          warn('invalid template option:' + template, this);
        }
        return this
      }
    } else if (el) { //存在el直接獲取當(dāng)前dom下的html字符串模版
      template = getOuterHTML(el);
    }
    if (template) {
      /* istanbul ignore if */
      if (config.performance && mark) {
        mark('compile');
      }

      var ref = compileToFunctions(template, {// 編譯模版
        outputSourceRange: "development" !== 'production',
        shouldDecodeNewlines: shouldDecodeNewlines,
        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this);
      var render = ref.render;
      var staticRenderFns = ref.staticRenderFns;
      options.render = render;
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if (config.performance && mark) {
        mark('compile end');
        measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
      }
    }
  }
  return mount.call(this, el, hydrating) // 最后執(zhí)行原先定義的mount方法
};
function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode // 創(chuàng)建一個(gè)空的vnode節(jié)點(diǎn)
    if (process.env.NODE_ENV !== 'production') {
    }
  }
  callHook(vm, 'beforeMount') // 執(zhí)行生命周期方法

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating) // 執(zhí)行原型鏈上的_update方法,_render()生成虛擬dom完域,進(jìn)行虛擬dom的比較
    }
  }
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  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
}

vue的虛擬dom

vue虛擬dom的實(shí)現(xiàn)借鑒了snabbdom软吐,增加了一些vue特有的屬性,我們一起來(lái)看一下vdom下的vnode.js文件
下邊是vnode的數(shù)據(jù)結(jié)構(gòu)吟税,可以看出相比snabbdom增加了很多凹耙,并且提供了一些創(chuàng)建vnode元素的方法

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;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support
}

來(lái)看一看是怎么通過render生成vdom的,實(shí)際上是通過_createElement生成的可以看到肠仪,先對(duì)傳入的一些參數(shù)進(jìn)行一些校驗(yàn)肖抱,對(duì)chilren進(jìn)行規(guī)范化使其是vnode的數(shù)組
然后根據(jù)tag進(jìn)行判斷,是原生標(biāo)簽直接生成vnode异旧,已注冊(cè)的組件通過component生成vnode意述,返回vnode

function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) { // 把children格式化成vnode數(shù)組
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) { // 判斷是不是一些內(nèi)置的div,span原生標(biāo)簽
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // 判斷是不是已經(jīng)注冊(cè)的組件標(biāo)簽
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

看到vnode的生成過程再來(lái)看看update這個(gè)方法在初次渲染和數(shù)據(jù)更新會(huì)執(zhí)行,并對(duì)dom進(jìn)行操作,這里比較重要的是patch方法荤崇,
我們通過源碼最終能找到這個(gè)代碼是在web下runtime目錄index.js下,由于不同平臺(tái)實(shí)現(xiàn)不一樣镐依,在瀏覽器中是在vdom中的patch.js中createPatchFunction,我們?cè)趕nabbdom中的patch也是根據(jù)依賴的hook生成的天试,在vue中也是基本的思路,在runtime中傳入依賴的一些模塊傳入的兩個(gè)參數(shù)然低,最終返回一個(gè)patch方法喜每,這個(gè)放會(huì)根據(jù)新舊vnode進(jìn)行diff算法,簡(jiǎn)單來(lái)說就是第一次生成直接創(chuàng)建新的元素雳攘,如果新舊的key带兜,tag,isComment相等就執(zhí)行patchVnode來(lái)更新dom和vnode吨灭,否則就刪除old創(chuàng)建新的到old的位置刚照,這和react的diff算法思想基本一致,只不過實(shí)現(xiàn)不一樣最后return真實(shí)的dom

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  const prevEl = vm.$el // 真實(shí)dom
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm) // 生成還原vm的方法
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) { // 初始化
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else { // 數(shù)據(jù)更新時(shí)
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // update __vue__ reference
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) { // 更新真實(shí)dom上對(duì)虛擬dom的指向
    vm.$el.__vue__ = vm
  }
  // if parent is an HOC, update its $el as well
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
}
function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend // 依賴的模塊和dom操作api

  for (i = 0; i < hooks.length; ++i) { // 根據(jù)定義好的hooks將依賴中的方法添加到cbs中去方便在不同時(shí)期執(zhí)行不同方法
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  // ...省略
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}

最后盜一張圖看一下整個(gè)流程

init流程圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喧兄,一起剝皮案震驚了整個(gè)濱河市无畔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吠冤,老刑警劉巖浑彰,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拯辙,居然都是意外死亡郭变,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門涯保,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诉濒,“玉大人,你說我怎么就攤上這事夕春∥椿模” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵撇他,是天一觀的道長(zhǎng)茄猫。 經(jīng)常有香客問我,道長(zhǎng)困肩,這世上最難降的妖魔是什么划纽? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锌畸,結(jié)果婚禮上勇劣,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好比默,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布幻捏。 她就那樣靜靜地躺著,像睡著了一般命咐。 火紅的嫁衣襯著肌膚如雪篡九。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天醋奠,我揣著相機(jī)與錄音榛臼,去河邊找鬼。 笑死窜司,一個(gè)胖子當(dāng)著我的面吹牛沛善,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塞祈,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼金刁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了议薪?” 一聲冷哼從身側(cè)響起尤蛮,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笙蒙,沒想到半個(gè)月后抵屿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捅位,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年轧葛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艇搀。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尿扯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焰雕,到底是詐尸還是另有隱情衷笋,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布矩屁,位于F島的核電站辟宗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吝秕。R本人自食惡果不足惜泊脐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烁峭。 院中可真熱鬧容客,春花似錦秕铛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至供置,卻和暖如春谨湘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芥丧。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工悲关, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娄柳。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像艘绍,于是被迫代替她去往敵國(guó)和親赤拒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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