【vue3源碼】十二榆浓、認識虛擬DOM

【vue3源碼】十二、認識虛擬DOM

什么是虛擬DOM撕攒?

虛擬DOM(也可以稱為vnode)描述了一個真實的DOM結(jié)構(gòu)陡鹃,它和真實DOM一樣都是由很多節(jié)點組成的一個樹形結(jié)構(gòu)。本質(zhì)其實就是一個JS對象抖坪,如下就是一個vnode

{
  type: 'div',
  props: {
    id: 'container'
  },
  children: [
    {
      type: 'span',
      props: {
        class: 'span1'
      },
      children: 'Hello '
    },
    {
      type: 'span',
      props: {
        class: 'span2'
      },
      children: 'World'
    },
  ]
}

上面這個vnode描述的真實DOM結(jié)構(gòu)如下:

<div id="container">
  <span class="text1">Hello </span>
  <span class="text2">World</span>
</div>

可以發(fā)現(xiàn)萍鲸,虛擬節(jié)點的type描述了標簽的類型,props描述了標簽的屬性擦俐,children描述了標簽的子節(jié)點脊阴。當(dāng)然一個vnode不僅只有這三個屬性。vue3中對vnode的類型定義如下:

export interface VNode<
  HostNode = RendererNode,
  HostElement = RendererElement,
  ExtraProps = { [key: string]: any }
> {
  // 標記為一個VNode
  __v_isVNode: true
  // 禁止將VNode處理為響應(yīng)式對象
  [ReactiveFlags.SKIP]: true
  // 節(jié)點類型
  type: VNodeTypes
  // 節(jié)點的屬性
  props: (VNodeProps & ExtraProps) | null
  // 便與DOM的復(fù)用蚯瞧,主要用在diff算法中
  key: string | number | symbol | null
  // 被用來給元素或子組件注冊引用信息
  ref: VNodeNormalizedRef | null
  scopeId: string | null
  slotScopeIds: string[] | null
  // 子節(jié)點
  children: VNodeNormalizedChildren
  // 組件實例
  component: ComponentInternalInstance | null
  // 指令信息
  dirs: DirectiveBinding[] | null
  transition: TransitionHooks<HostElement> | null

  // DOM
  // vnode對應(yīng)的DOM
  el: HostNode | null
  anchor: HostNode | null // fragment anchor
  // teleport需要掛載的目標DOM
  target: HostElement | null
  // teleport掛載所需的錨點
  targetAnchor: HostNode | null
   
  // 對于Static vnode所包含的靜態(tài)節(jié)點數(shù)量
  staticCount: number
  // suspense組件的邊界
  suspense: SuspenseBoundary | null
  // suspense的default slot對應(yīng)的vnode
  ssContent: VNode | null
  // suspense的fallback slot對應(yīng)的vnode
  ssFallback: VNode | null

  // 用于優(yōu)化的標記嘿期,主要用于判斷節(jié)點類型
  shapeFlag: number
  // 用于diff優(yōu)化的補丁標記
  patchFlag: number
  dynamicProps: string[] | null
  dynamicChildren: VNode[] | null

  // application root node only
  appContext: AppContext | null
  /**
   * @internal attached by v-memo
   */
  memo?: any[]
  /**
   * @internal __COMPAT__ only
   */
  isCompatRoot?: true
  /**
   * @internal custom element interception hook
   */
  ce?: (instance: ComponentInternalInstance) => void
}

如何創(chuàng)建虛擬DOM

vue3對外提供了h()方法用于創(chuàng)建虛擬DOM。所在文件路徑:packages/runtime-core/src/h.ts

export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) {
    // propsOrChildren是對象且不是數(shù)組
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      // propsOrChildren是vnode
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren])
      }
      // 有props無子節(jié)點
      return createVNode(type, propsOrChildren)
    } else {
      // 有子節(jié)點
      return createVNode(type, null, propsOrChildren)
    }
  } else {
    // 如果參數(shù)大于3埋合,那么第三個參數(shù)及之后的參數(shù)都會被作為子節(jié)點處理
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}

h函數(shù)會使用createVNode函數(shù)創(chuàng)建虛擬DOM备徐。

export const createVNode = (
  __DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode

可以看到createVNode在開發(fā)環(huán)境下會使用createVNodeWithArgsTransform,其他環(huán)境下會使用_createVNode饥悴。這里我們只看下_createVNode的實現(xiàn)坦喘。

<details>
<summary><code>_createVNode</code>完整代碼</summary>

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
  }

  // 如果type已經(jīng)是個vnode,則復(fù)制個新的vnode
  if (isVNode(type)) {
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
        currentBlock[currentBlock.indexOf(type)] = cloned
      } else {
        currentBlock.push(cloned)
      }
    }
    cloned.patchFlag |= PatchFlags.BAIL
    return cloned
  }

  // class組件的type
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

  //  兼容2.x的異步及函數(shù)式組件
  if (__COMPAT__) {
    type = convertLegacyComponent(type, currentRenderingInstance)
  }

  // class西设、style的標準化
  if (props) {
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

  // 根據(jù)type屬性確定patchFlag
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0

  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    type = toRaw(type)
    warn(
      `Vue received a Component which was made a reactive object. This can ` +
        `lead to unnecessary performance overhead, and should be avoided by ` +
        `marking the component with \`markRaw\` or using \`shallowRef\` ` +
        `instead of \`ref\`.`,
      `\nComponent that was made reactive: `,
      type
    )
  }

  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

</details>

_createVNode可以接受6個參數(shù):

  • typevnode類型
  • propsvnode的屬性
  • children:子vnode
  • patchFlag:補丁標記瓣铣,由編譯器生成vnode時的優(yōu)化提示,在diff期間會進入對應(yīng)優(yōu)化
  • dynamicProps:動態(tài)屬性
  • isBlockNode:是否是個Block節(jié)點

首先會對type進行校驗贷揽,如果type是空的動態(tài)組件棠笑,進行提示,并將type指定為一個Comment注釋DOM禽绪。

if (!type || type === NULL_DYNAMIC_COMPONENT) {
  if (__DEV__ && !type) {
    warn(`Invalid vnode type when creating vnode: ${type}.`)
  }
  type = Comment
}

如果type已經(jīng)是個vnode蓖救,會從type復(fù)制出一個新的vnode洪规。這種情況主要在<component :is="vnode"/>情況下發(fā)生

if (isVNode(type)) {
  const cloned = cloneVNode(type, props, true /* mergeRef: true */)
  if (children) {
    // 修改其children屬性及完善shapeFlag屬性
    normalizeChildren(cloned, children)
  }
  // 將被拷貝的對象存入currentBlock中
  if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
    if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
      currentBlock[currentBlock.indexOf(type)] = cloned
    } else {
      currentBlock.push(cloned)
    }
  }
  cloned.patchFlag |= PatchFlags.BAIL
  return cloned
}

關(guān)于cloneVNode的實現(xiàn):

export function cloneVNode<T, U>(
  vnode: VNode<T, U>,
  extraProps?: (Data & VNodeProps) | null,
  mergeRef = false
): VNode<T, U> {
  const { props, ref, patchFlag, children } = vnode
  // 如果存在extraProps,需要將extraProps和vnode的props進行合并
  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
  const cloned: VNode = {
    __v_isVNode: true,
    __v_skip: true,
    type: vnode.type,
    props: mergedProps,
    // 如果過mergedProps中不存在key循捺,則設(shè)置為null
    key: mergedProps && normalizeKey(mergedProps),
    //  如果過存在額外的ref
    //      如果過需要合并ref
    //          如果被拷貝節(jié)點中的ref是個數(shù)組斩例,將調(diào)用normalizeRef處理ref,并將結(jié)果合并到被拷貝節(jié)點中的ref中
    //          否則从橘,創(chuàng)建一個新的數(shù)組念赶,存儲ref和normalizeRef(extraProps)的結(jié)果
    //      否則直接調(diào)用normalizeRef(extraProps)處理新的ref
    // 否則ref不變
    ref:
      extraProps && extraProps.ref
        ? mergeRef && ref
          ? isArray(ref)
            ? ref.concat(normalizeRef(extraProps)!)
            : [ref, normalizeRef(extraProps)!]
          : normalizeRef(extraProps)
        : ref,
    scopeId: vnode.scopeId,
    slotScopeIds: vnode.slotScopeIds,
    children:
      __DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
        ? (children as VNode[]).map(deepCloneVNode)
        : children,
    target: vnode.target,
    targetAnchor: vnode.targetAnchor,
    staticCount: vnode.staticCount,
    shapeFlag: vnode.shapeFlag,
    // 如果 vnode 使用額外的 props 克隆,我們不能再假設(shè)其現(xiàn)有的補丁標志是可靠的恰力,需要添加 FULL_PROPS 標志
    // 如果存在extraProps叉谜,并且vnode.type不是是Fragment片段的情況下:
    //    如果patchFlag為-1,說明是靜態(tài)節(jié)點踩萎,它的內(nèi)容不會發(fā)生變化停局。新的vnode的patchFlag為PatchFlags.FULL_PROPS,表示props中存在動態(tài)key
    //    如果patchFlag不為-1香府,將patchFlag與PatchFlags.FULL_PROPS進行或運算
    // 否則patchFlag保持不變
    patchFlag:
      extraProps && vnode.type !== Fragment
        ? patchFlag === -1 // hoisted node
          ? PatchFlags.FULL_PROPS
          : patchFlag | PatchFlags.FULL_PROPS
        : patchFlag,
    dynamicProps: vnode.dynamicProps,
    dynamicChildren: vnode.dynamicChildren,
    appContext: vnode.appContext,
    dirs: vnode.dirs,
    transition: vnode.transition,
    component: vnode.component,
    suspense: vnode.suspense,
    ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
    ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
    el: vnode.el,
    anchor: vnode.anchor
  }
  // 用于兼容vue2
  if (__COMPAT__) {
    defineLegacyVNodeProperties(cloned)
  }
  return cloned as any
}

在復(fù)制節(jié)點的過程中主要處理經(jīng)歷以下步驟:

  1. 被拷貝節(jié)點的props與額外的props的合并
  2. 創(chuàng)建新的vnode
    • key的處理:取合并后的props中的key董栽,如果不存在,取null
    • ref的合并:根據(jù)是否需要合并ref企孩,決定是否合并ref
    • patchFlag的處理:如果vnode使用額外的props克隆裆泳,補丁標志不再可靠的,需要添加FULL_PROPS標志
    • ssContent的處理:使用cloneVNode復(fù)制被拷貝節(jié)點的ssContent
    • ssFallback的處理:使用cloneVNode復(fù)制被拷貝節(jié)點的ssFallback
  3. 兼容vue2
  4. 返回新的vnode

在克隆vnode時柠硕,props會使用mergeProps進行合并:

export function mergeProps(...args: (Data & VNodeProps)[]) {
  const ret: Data = {}
  for (let i = 0; i < args.length; i++) {
    const toMerge = args[i]
    for (const key in toMerge) {
      if (key === 'class') {
        if (ret.class !== toMerge.class) {
          // 建立一個數(shù)組并調(diào)用normalizeClass工禾,最終class會是字符串的形式
          ret.class = normalizeClass([ret.class, toMerge.class])
        }
      } else if (key === 'style') {
        // 建立style數(shù)組并調(diào)用normalizeStyle,最終style是對象形式
        ret.style = normalizeStyle([ret.style, toMerge.style])
      } else if (isOn(key)) { // 以on開頭的屬性蝗柔,統(tǒng)一按事件處理
        const existing = ret[key]
        const incoming = toMerge[key]
        // 如果已經(jīng)存在的key對應(yīng)事件與incoming不同闻葵,并且已經(jīng)存在的key對應(yīng)事件中不包含incoming
        if (
          incoming &&
          existing !== incoming &&
          !(isArray(existing) && existing.includes(incoming))
        ) {
          // 如果過存在existing,將existing癣丧、incoming合并到一個新的數(shù)組中
          ret[key] = existing
            ? [].concat(existing as any, incoming as any)
            : incoming
        }
      } else if (key !== '') { // 其他情況直接對ret[key]進行賦值槽畔,靠后合并的值會取代之前的值
        ret[key] = toMerge[key]
      }
    }
  }
  return ret
}

關(guān)于normalizeClassnormalizeStyle的實現(xiàn):

export function normalizeClass(value: unknown): string {
  let res = ''
  if (isString(value)) {
    res = value
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      const normalized = normalizeClass(value[i])
      if (normalized) {
        res += normalized + ' '
      }
    }
  } else if (isObject(value)) {
    for (const name in value) {
      if (value[name]) {
        res += name + ' '
      }
    }
  }
  return res.trim()
}

export function normalizeStyle(
  value: unknown
): NormalizedStyle | string | undefined {
  if (isArray(value)) {
    const res: NormalizedStyle = {}
    for (let i = 0; i < value.length; i++) {
      const item = value[i]
      const normalized = isString(item)
        ? parseStringStyle(item)
        : (normalizeStyle(item) as NormalizedStyle)
      if (normalized) {
        for (const key in normalized) {
          res[key] = normalized[key]
        }
      }
    }
    return res
  } else if (isString(value)) {
    return value
  } else if (isObject(value)) {
    return value
  }
}

const listDelimiterRE = /;(?![^(]*\))/g
const propertyDelimiterRE = /:(.+)/

export function parseStringStyle(cssText: string): NormalizedStyle {
  const ret: NormalizedStyle = {}
  cssText.split(listDelimiterRE).forEach(item => {
    if (item) {
      const tmp = item.split(propertyDelimiterRE)
      tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim())
    }
  })
  return ret
}

回到_createVNode中胁编,當(dāng)復(fù)制出一個新的vnode后厢钧,調(diào)用了一個normalizeChildren方法,該方法的作用是對新復(fù)制的vnode嬉橙,修改其children屬性及完善shapeFlag屬性

export function normalizeChildren(vnode: VNode, children: unknown) {
  let type = 0
  const { shapeFlag } = vnode
  // 如果children為null或undefined早直,children取null
  if (children == null) {
    children = null
  } else if (isArray(children)) {
    // 如果過children數(shù)數(shù)組,type改為ShapeFlags.ARRAY_CHILDREN
    type = ShapeFlags.ARRAY_CHILDREN
  } else if (typeof children === 'object') { // 如果children是對象
    // 如果vndoe是element或teleport
    if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.TELEPORT)) {
      // 取默認插槽
      const slot = (children as any).default
      if (slot) {
        // _c 標記由 withCtx() 添加市框,表示這是一個已編譯的插槽
        slot._c && (slot._d = false)
        // 將默認插槽的結(jié)果作為vnode的children
        normalizeChildren(vnode, slot())
        slot._c && (slot._d = true)
      }
      return
    } else {
      type = ShapeFlags.SLOTS_CHILDREN
      const slotFlag = (children as RawSlots)._
      if (!slotFlag && !(InternalObjectKey in children!)) {
         // 如果槽未規(guī)范化霞扬,則附加上下文實例(編譯過或標準話的slots已經(jīng)有上下文)
        ;(children as RawSlots)._ctx = currentRenderingInstance
      } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
        // 子組件接收來自父組件的轉(zhuǎn)發(fā)slots。
        // 它的插槽類型由其父插槽類型決定。
        if (
          (currentRenderingInstance.slots as RawSlots)._ === SlotFlags.STABLE
        ) {
          ;(children as RawSlots)._ = SlotFlags.STABLE
        } else {
          ;(children as RawSlots)._ = SlotFlags.DYNAMIC
          vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
        }
      }
    }
  } else if (isFunction(children)) { // 如果過children是function
    children = { default: children, _ctx: currentRenderingInstance }
    type = ShapeFlags.SLOTS_CHILDREN
  } else {
    children = String(children)
    // force teleport children to array so it can be moved around
    if (shapeFlag & ShapeFlags.TELEPORT) {
      type = ShapeFlags.ARRAY_CHILDREN
      children = [createTextVNode(children as string)]
    } else {
      type = ShapeFlags.TEXT_CHILDREN
    }
  }
  vnode.children = children as VNodeNormalizedChildren
  vnode.shapeFlag |= type
}

然后判斷vnode是否應(yīng)該被收集到Block中喻圃,并返回拷貝的節(jié)點萤彩。

如果type不是vnode,在方法最后會調(diào)用一個createBaseVNode創(chuàng)建vnode

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  const vnode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  } as VNode

  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
    // normalize suspense children
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }

  // validate key
  if (__DEV__ && vnode.key !== vnode.key) {
    warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
  }

  // 收集vnode到block樹中
  if (
    isBlockTreeEnabled > 0 &&
    // 避免block自己收集自己
    !isBlockNode &&
    // 存在父block
    currentBlock &&
    // vnode.patchFlag需要大于0或shapeFlag中存在ShapeFlags.COMPONENT
    // patchFlag的存在表明該節(jié)點需要修補更新斧拍。
    // 組件節(jié)點也應(yīng)該總是打補丁雀扶,因為即使組件不需要更新,它也需要將實例持久化到下一個 vnode肆汹,以便以后可以正確卸載它
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }

  return vnode
}

總結(jié)

虛擬DOM的創(chuàng)建流程:

  1. 如果type是個空的動態(tài)組件怕吴,將vnode.type指定為Comment注釋節(jié)點。
  2. 如果type已經(jīng)是個vnode县踢,則拷貝一個新的vnode返回。
  3. 處理class component
  4. 兼容vue2的異步組件及函數(shù)式組件
  5. classstyle的標準化
  6. 根據(jù)type屬性初步確定patchFlag
  7. 調(diào)用createBaseVNode方法創(chuàng)建vnode并返回

createBaseVNode

  1. 先創(chuàng)建一個vnode對象
  2. 完善childrenpatchFlag屬性
  3. 判斷是否應(yīng)該被父Block收集
  4. 處理兼容vue2
  5. 返回vnode
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伟件,一起剝皮案震驚了整個濱河市硼啤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斧账,老刑警劉巖谴返,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咧织,居然都是意外死亡嗓袱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門习绢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渠抹,“玉大人,你說我怎么就攤上這事闪萄∥嗳矗” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵败去,是天一觀的道長放航。 經(jīng)常有香客問我,道長圆裕,這世上最難降的妖魔是什么广鳍? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮吓妆,結(jié)果婚禮上赊时,老公的妹妹穿的比我還像新娘。我一直安慰自己行拢,他們只是感情好蛋叼,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般狈涮。 火紅的嫁衣襯著肌膚如雪狐胎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天歌馍,我揣著相機與錄音握巢,去河邊找鬼。 笑死松却,一個胖子當(dāng)著我的面吹牛暴浦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晓锻,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼歌焦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砚哆?” 一聲冷哼從身側(cè)響起独撇,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躁锁,沒想到半個月后纷铣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡战转,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年搜立,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槐秧。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡啄踊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刁标,到底是詐尸還是另有隱情社痛,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布命雀,位于F島的核電站蒜哀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吏砂。R本人自食惡果不足惜撵儿,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狐血。 院中可真熱鬧淀歇,春花似錦、人聲如沸匈织。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纳决,卻和暖如春碰逸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阔加。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工饵史, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胜榔。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓胳喷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夭织。 傳聞我的和親對象是個殘疾皇子吭露,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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