2021最新Vue 面试题

Vue面试题

  1. v-html 会有XSS风险,会覆盖子组件

  2. computed 有缓存,data不变则不会重新计算

  3. watch 默认不会深度监听,要deep:true开启

  4. watch 监听引用类型,拿不到oldVal,因为指针相同,此时已经指向了新的val

  5. style指令需要驼峰式命名方式写,例如{fontSize: '10px'}

  6. v-if v-else 可使用变量,也可使用===表达式

  7. v-if 和 v-show 的区别

    v-if false不渲染,适合更新不频繁的,节省性能

    v-show false也渲染

  8. v-for 和 v-if 不能一起使用,v-for 比 v-if 优先级高,影响性能

  9. 事件的event是原生的event,没有装饰过,事件是挂载到当前元素

  10. v-for 的 key 因为 VDOM 的 diff 算法,通过判断新vnode和旧vnode的key是否相等,从而复用与新节点对应的老节点,节约性能开销

  11. 用index做key在diff算法中不起作用,用index拼接其他值会导致节点不能复用,所以用唯一值做key,比如数据id

  12. props 和 $emit

    props父组件通过子组件属性给子组件传值

    $emit子组件绑定父组件方法,在子组件内使用$emit触发父组件的方法

  13. Vue组件如何通讯

    父子组件事件props $emit

    自定义事件 $emit $on

    通过vuex

  14. 组件间通讯-自定义事件(常用兄弟组件通讯)

    Vue自有实现,使用EventBus方式(bus总线机制/发布订阅者模式/观察者模式)

    import Vue from 'vue'
    var event = new Vue()
    export default event
    

    在A组件内使用

    event.$emit('onHandle', params)
    

    在B组件内的mounted中绑定自定义事件

    event.$on('onHandle', this.handle)
    

    在B组局内beforeDestroy中及时销毁监听事件,否则可能造成内存泄露

    event.$off('onHandle', this.handle)
    
  15. 组件生命周期 <font color="red">* 必须背会</font>

    挂载

    beforeCreate->create->beforeMount->mounted
    

    beforeCreate里没有this,实例还未初始化,在数据观测(data observer)和event/watcher事件配置前

    create 初始化示例,没有渲染,取不到dom,没有$el,通常初始化某些属性值

    beforeMount 挂载开始之前,相关渲染首次被调用

    mounted 是渲染完了,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作

    先父组件create->子组件create->子组件mounted->父组件mounted


    更新

    beforeUpdate->updated
    

    beforeUpdate 重新渲染和打补丁之前,在这里更改状态不会触发重新渲染

    updated 由于数据更改导致的重新渲染和打补丁触发

    先父组件beforeUpdate-->子组件beforeUpdate->updated->父组件->updated


    销毁

    beforeDestroy->Destroyed
    

    beforeDestroy 实例销毁之前,里面可以用this

    Destroyed 实例销毁之后,所以东西解绑,移除所以监听,销毁所以子实例,在服务端渲染不会被调用

    先父组件beforeDestroy-->子组件beforeDestroy->Destroyed->父组件->Destroyed

  16. 高级特性

    • <font color="red">可以不深入,但必须知道</font>
    • <font color="red">熟练基本用法,了解使用场景</font>
    • <font color="red">最好能和自己的项目经验结合</font>
    1. 自定义v-model

      v-model会默认利用名为value的prop和名为input的事件,model会避免单选,复选将value特性用于不同目的的冲突

      v-model等于

      <input type="text" 
            :value="val" 
            @input="val=$event.target.value">
      

      demo:

      //父组件
      <CustomVModel v-model="name"/>
      <script>
      import CustomVModel from './CustomVModel'
      export default {
          components: {
              CustomVModel
          },
          data() {
              return {
                  name: ''
              }
          }
      }
      </script>
      // 子组件内
      <template>
         <!--
         1. input使用了 :value 而不是 v-model
         2. change 和 model.even 要对应
         3. text 属性要对应 model.prop 和 props.text
         -->
         <input type="text" 
                 :value="text" 
                 @input="$emit('change', $event.target.value)"
          />
      </template>
      <script>
      export default {
          // 改变默认v-model的绑定属性和抛出事件
          model: {
              prop: 'text' // 对应props text,
              event: 'change'
          },
          props: {
              text: String,
              default() {
                  return ''
              }
          }
      }
      </script>
      
    2. $nextTick、$refs

      $nextTick

      Vue是异步渲染,$nextTick会在DOM渲染之后被触发

      页面渲染会将 data 的修改做整合,多次 data 修改只会渲染一次


      $refs 在节点内写ref="dom" 使用this.$refs.dom拿到节点的dom元素

    3. slot

      基本使用

      父组件在子组件内插入节点内容

      // 父组件
      <SlotDemo>
         {{user.name}}
      </SlotDemo>
      // 子组件 SlotDemo
      <div>
          <slot>默认显示</slot>
      </div>
      

      作用域插槽

      父组件在子组件内插入节点内容,使用子组件的数据

      // 父组件
      <ScopedSlotDemo>
         <template v-slot="slotProps">
             {{slotProps.slotData.name}}
          </template>
      </ScopedSlotDemo>
      // 子组件 ScopedSlotDemo
      <div>
          <slot :slotData="user"></slot>
      </div>
      

      具名插槽

      用于定义多个插槽

      // 父组件
      <NameSlot>
          <template v-slot:header>
             <h1>插入 header slot中</h1>
          </template>
         <p>插入 main slot中,即未命名的 slot</p>
         <template v-slot:footer>
             <p>插入 footer slot中</p>
          </template>
      </NameSlot>
      // 子组件 NameSlot
      <div class="container">
        <header>
          <slot name="header"></slot>
        </header>
        <main>
          <slot></slot>
        </main>
        <footer>
          <slot name="footer"></slot>
        </footer>
      </div>
      
    4. 动态、异步组件

      动态组件

      用法 :is='component-name'

      需要根据数据,动态渲染的场景,即组局类型不确定,例如 信息流

      <div v-for="item in components" v-key="id">
         <component :is="item.name" />    
      </div>
      
      <script>
      import TextInfoComponent from './TextInfoComponent'
      import ImageInfoComponent from './TextInfoComponent'
      export default {
          components: {
              TextInfoComponent,
              ImageInfoComponent
          },
          data() {
              return {
                 components: [{
                     id: 1,
                     name: 'TextInfoComponent',
                 },{
                     id: 2,
                     name: 'ImageInfoComponent',
                 }]
              }
          }
      }
      </script>
      

      异步组件

      import() 函数

      按需加载, 异步加载大组件

      export default {
          conponents: {
              component: () => import('./component')
          }
      }
      
    5. keep-alive

      缓存组件,频繁切换,不需要重复渲染 例如 tab切换

      <keep-alive>
       <KeepAliveStage></KeepAliveStage>
      </keep-alive>
      <script>
      import KeepAliveStage from './KeepAliveStage'
      export default {
          conponents: {
              KeepAliveStage
          }
      }
      </script>
      
    6. mixin

      多组件有相同的逻辑,抽离出来,和主组件混合合并代码

      会出现问题

      • 变量来源不明确,不利于阅读
      • 多mixin会造成命名冲突
      • mixin和组件可能出现多对多的关系,复杂度较高

      Vue 3 提出的Composition API 旨在解决这些问题

      // 定义 ./myMixin.js
      export default {
          data() {
              return {}
          },
          mounted() {},
          methots: {}
      }
      
      // 使用
      import myMixin from './myMixin'
      export default {
          mixins: [myMixin] // 可以多个,自动合并
      }
      
  17. Vuex 使用

    可能会考察 state 的数据结构设计

    vuex.png
+ 基本概念

1. state // 单一状态树
2. getters // 获取属性,计算属性啥的
3. actions // 类似控制器,commit 多个 mutations,这里可以异步操作,其他的不行
4. mutations // 更改状态,必须是同步函数
5. module // 模块化Store
6. plugins //插件,相当mutation的拦截器,提供store.subscribe((mutation, state) => {})

--------------

+ Vue组件内

1. dispatch // 用来触发 actions
2. commit // 用来触发 mutations
3. mapState // 映射 state 的函数,computed 中 ...mapState()
4. mapGetters  // 映射 getters 的函数,computed 中 ...mapState()
5. mapActions // 映射 actions 的函数,methods 中 ...mapActions()
6. mapMutations // 映射 mutations 的函数,methods 中 ...mapMutations()
  1. Vue-router 使用

    路由模式(hash,H5 history),后者需要server支持

    mode:‘history’

    路由配置(动态路由,懒加载)

    :id那种,在$route.params.id取到

  2. Vue 原理

    • 如何理解MVVM

      mvvm.png

      Model-View-ViewModel 数据模型-视图-视图数据

      数据驱动视图

      View和Model没有直接关联,ViewMode是给他们提供双向数据绑定的联系,优点是让开发者只需关注业务,不需要手动操作DOM,也不需要关注数据状态同步的问题

    • 监听data变化的核心API是什么,怎么深度监听-递归

      Vue 2 是通过 Object.defineProperty 的 getter 和 setter,并结合观察者模式实现数据绑定的

      能监听属性的get set方法

      缺点

      用于深度监听要递归到底,一次性计算量大

      无法监听新增属性/删除属性

      无法原生监听数组,需要特殊处理

      Vue 3 是通过 es6的 proxy

      优点是之前操作的是对象的属性,现在是操作对象,对js引擎比较友好

      缺点是兼容不好,不能用polyfill,不支持低版本浏览器

      监听数组-重新定义数组原型链

      const oldArrayProperty = Array.prototype
      // 创建新对象,原型指向oldArrayProperty,在扩展新的方法不会影响原型
      const arrProto = Object.create(oldArrayProperty)
      ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
          arrProto[methodName] = function() {
              updateView() // 更新视图
              oldArrayProperty[methodName].call(this, ...arguments)
          }
      })
      
    • 虚拟DOM

      vdom (Virtual DOM) 是实现vue的重要基石

      用JS模拟DOM结构,计算出最小的变更,操作DOM

      数据驱动视图,控制DOM操作

    • diff算法

      diff算法是vdom中的核心,最关键的部分

      vue2 参考snabbdom,双端比较

      diff01.png

      核心思路

      patch新旧vnode的children

      新的没有不管旧的有没有,就移除children

      新旧都有,就去对比新旧vnode的children

      新有旧没有,就添加children

      对比新旧vnode的children,先用四个指针指向新旧nodeChildren的头尾

      然后循环指针到中间,循环内进行对比key和sel是否相等

      1. 旧children的开始 和 新children的开始 对比
      2. 旧children的结束 和 新children的结束 对比
      3. 旧children的开始 和 新children的结束 对比
      4. 旧children的结束 和 新children的开始 对比
      5. 以上有相同的,就执行处理新旧节点的children,并对应移动指针
      6. 以上都没找到相同的,则去用新的key看看旧的里面有没有,如果没有则插入,然后移动指针,如果有再判断sel是否相同,不相同就插入,然后移动指针,相同就执行处理新旧节点的children,去掉旧节点,插入新节点,然后移动指针
      // children 和 text 是不会共存的,要么是节点要么是文本
      // elm 对应的dom元素
      // 所有元素都可以有key
      Vnode = {sel, data, children, text, elm, key}
      
      //先创建Vnode
      h() => Vnode
      
      patch(oldVnode, newVnode) => {
          if 第一个参数不是Vnode
              创建一个空的Vnode,关联到这个elm元素
          
          if 判断Vnode是否相同,则去对比(两个Vnode的key 和 sel 都相等)
              Vnode对比: patchVnode()
          else 不同的Vnode直接删掉重建 
      }
      
      patchVnode(oldVnode, newVnode) => {
          执行 prepatch hook
          设置 vnode.elm,把新的 elm 赋值成旧的 elm, 让新的知道更新到了哪个 elm
          得到 oldChildren 和 newChildren
          if 判断newVnode 有没有 children, newVnode.text == undefined (vnode.children 一般有值) 
              if 新旧都有 children
                  更新children: updateChildren()
              else if 新 children 有,旧 children 无 (旧 text 有)
                  if 如果就的 text 有值,则清空 text
                  添加 children
              else if 新 children 无,旧 children 有
                  移除 children
              else 旧 text 有
                  清空 text
              清空 text
          else vnode.children 没有值,则删除旧的 children 并设置清空 text
      }
      
      // 核心方法
      updateChildren(elm, oldChildren, newChildren) => {
          // 用于循环从四周到中间
          定义指针 oldStartIndex, oldEndIndex, newStartIndex, oldStartIndex
          
          while(指针到中间停止) {
              if 判断元素是否是空,操作指针移动
              else if oldStart == newStart 开始和开始,判断是否相同(key 和 sel 都相等)
                  patchVnode(), 操作指针移动
              else if oldEnd == newEnd 结束和结束,判断是否相同(key 和 sel 都相等)
                  patchVnode(), 操作指针移动
              else if oldStart == newEnd 开始和结束,判断是否相同(key 和 sel 都相等)
                  patchVnode(), 操作指针移动
              else if oldEnd == newStart 结束和开始,判断是否相同(key 和 sel 都相等)
                  patchVnode(), 操作指针移动
              else 以上四个都未命中
                  // 用新节点 key,判断能否对应 oldChildren 中某个节点的 key
                  在oldChildren中,用新节点的key去拿节点
                  if 没拿到没对应上,则
                      插入新节点, 操作指针移动
                  else 对应上
                      if sel 是否相等
                          插入新节点, 操作指针移动
                      else key 和 sel 都相等
                          patchVnode(),去掉旧节点,插入新节点, 操作指针移动
          }
      }
      

      vue3 参考inferno,最长递增子序列

      两个理念。第一个是相同的前置与后置元素的预处理;第二个则是最长递增子序列

      1. 从头对比找到相同的节点patch,发现不同则跳出

      2. 如果1没有patch完,则从后往前找相同的节点patch,发现不同则跳出

      3. 如果新节点大于与旧节点,对剩余的都创建新的vnode

      4. 如果旧节点大于新节点,对于超出的旧节点全部卸载

      5. 如果3,4都没有,则是对于不确定的元素(没有patch到相同的vnode)

        1. 把没有比较过的新vnode保存在map里

        2. 记录已经patch节点的熟练patched,没有经过patch的新节点数量tuBePatched

        3. 建立一个数组newIndexToOldIndexMap,每个元素都记录旧节点的索引,这个数组的索引就是新节点的索引

        4. 遍历旧节点

          1. 如果toBePatched为0,那么统一卸载旧节点
          2. 如果旧节点key存在,通过key找到对应新节点的index
          3. 如果旧节点key不存在,遍历剩下的所有新节点,试图找到新节点对应的index
          4. 如果没有找到对应的新节点就卸载旧节点
          5. 如果找到对应的新节点,把旧节点的索引记录在新节点的数组中,如果节点发生移动就记录已经移动了,最后patch新节点
        5. 如果发生移动,根据newIndexToOldIndexMap找到最长稳定序列,

        6. 如果证明不存在旧节点就创建新节点,对于发生移动的节点进行移动处理

  3. 模版编译

    模版编译为 render 函数,执行 render 函数返回 vnode

    基于 vnode 再执行 patch 和 diff

    使用webpack vue-loader

    • JS的 with 语法

      改变 {} 内自由变量的查找规则,当做 obj 属性来查找

      如果找不到匹配的 obj 属性,就会报错

      with 要慎用,它打破了作用域规则,易读性变差

      // with 能改变 {} 内自由变量的查找方式,将 {} 内自由变量,当做 obj 的属性来查找
      const obj = {a:100, b:200}
      console.log(obj.a)
      console.log(obj.b)
      console.log(obj.c) // undefined
      with(obj) {
          console.log(a)
          console.log(b)
          console.log(c) // 会报错
      }
      
  4. 组件 渲染/更新 过程

data.png
> 初次渲染的过程
>
> > 解析模版为 render 函数
> >
> > 触发响应式,监听 data 属性的 getter setter
> >
> > 执行 render 函数,生成 vnode, 执行 patch(elm, vnode)
>
> 更新过程
>
> > 修改 data,触发 setter
> >
> > 重新执行 render 函数,生成 newVnode
> >
> > 执行 patch(vnode, newVnode)
>
> 异步渲染 $nextTick
>
> > 汇总 data 的修改,一次性更新视图
> >
> > 减少 DOM 操作次数,提升性能
  1. 路由原理

    稍微复制的SPA,都需要路由

    hash-window.onhashchange

    H5 history-history.pushState 和 window.onpopstate

    H5 history 需要后端支持

    两者选择

    toB 推荐hash,简单易用,对 url 规范不敏感

    toC 可以考虑选择H5 history,但需要服务端支持

    能选择简单的就别用复杂的,要考虑成本和收益

    hash 特点

    hash 变化会触发网页跳转,即浏览器的前期、后退

    hash 变化不会刷新页面,SPA必需的特点

    hash 永远不会提交到server端(前端自生自灭)

    H5 history

    用 url 规范的路由,但跳转时不刷新页面

    history.pushState

    window.onpopstate

  2. 面试题

    v-show 和 v-if 的区别

    v-show 通过 css display 控制显示和隐藏

    v-if 是组件真正的渲染和销毁,而不是显示和隐藏

    频繁切换显示状态用 v-show,否则用 v-if

    为什么 v-for 中用 key

    必须用 key,而且不能是 index 和 random

    diff 算法中通过 tag 和 key 来判断,是否是 sameNode

    减少渲染次数,提升渲染性能

    描述 vue 组件的生命周期

    单组件生命周期图

    父子组件生命周期关系比如 mount 和 update

    Vue 组件如何通讯

    父子组件 props 和 this.$emit

    自定义事件 event.on event.off event.$emit

    vuex

    描述组件渲染和更新的过程

    渲染图

    双向数据绑定 v-model 的实现原理

    input 元素的 value = this.name

    绑定 input 事件 this.name = $event.target.value

    data 更新触发 re-render

    对 MVVM 的理解

    computed 有何特点

    缓存,data 不变不会重新计算

    提高性能

    为何组件 data 必须是一个函数

    js 特性导致的,Object 是引用数据类型,会导致同一个属性改一个都变了,是函数的时候才能实例独立不相互影响

    ajax 应该放在哪个生命周期

    mounted

    js 是单线程,ajax 是异步获取数据

    放在 mounted 之前没有用,只会让逻辑更加混乱,除非有特殊需求

    如何将组件所有的 props 传递给子组件

    $props

    <Uer v-bind="$props" />

    如何自己实现 v-model

    多个组件有相同的逻辑,如何抽离

    mixin

    mixin 的缺点

    何时要使用异步组件

    加载大组件

    路由异步加载

    何时需要使用 keep-alive

    缓存组件,不需要重复渲染

    多个静态 tab 页的切换

    优化性能

    何时需要使用beforeDestory

    解绑自定义事件 event.$off

    清楚定时器

    解绑自定义的 DOM 事件,如 window scroll 等

    什么是作用域插槽

    把子组件的数据传给父组件显示

    vuex 中 action 和 mutation 有何区别

    action 中处理异步,mutation 不可以

    mutation 做原子操作

    action 可以整合多个 mutation

    vue-router 常用的路由模式

    hash 默认

    H5 history

    vue-router 两种模式是怎么实现的

    hash window.onhashchange

    H5 history window.history.pushState window.history.onpopstate

    vue-router 两种模式的区别

    hash有#号,能控制浏览器前后跳转,不会提交到server端

    H5 history 是规范 url,需要 server 端配合

    如何配置 vue-router 异步加载

    通过 () => import()

    用 vnode 描述一个 DOM 结构

    监听 data 变化的核心 API 是什么

    Object.defineProperty

    有何缺点

    深度监听需要递归、监听数组需要特殊处理,无法监听新增和删除的属性,使用vue.set vue.delete

    Vue3 Proxy 兼容问题,而且不能 polyfill

    Vue 如何监听数组变化

    Object.defineProperty 不能监听数组变化

    重新定义原型,重新push pop等方法,实现监听

    Proxy 可以原生支持监听数组变化

    描述响应式原理

    监听 data 变化

    组件渲染和更新的流程

    diff 算法的时间复杂度

    O(n) ,在O(n^3)基础上做的调整,只比较同一层级,比较tag不相同直接销毁重建,通过tag和key判断是不是同一组件,是就不重复对比

    简述 diff 算法过程

    patch(elem, vnode) 和 patch(vnode, newVnode)

    patchVnode 和 addVnodes 和 removeVnodes

    updateChildren,通过key判断是不是同一节点

    vue为何是异步渲染,$nextTick 何用

    异步渲染,合并 data 修改,提高性能

    $nextTick 在 DOM 更新之后,触发回调

    vue 常见性能优化

    合理使用 v-show v-if

    合理使用 computed

    v-for 时加 key,以及避免和 v-if 同时使用

    自定义事件,DOM事件及时销毁

    合理使用异步组件

    合理使用 keep-alive

    data 层级不要太深

    使用 vue-loader 在开发环境做模版编译(预编译)

    webpack 层面的优化

    前端通用的性能优化,如图片懒加载

    使用ssr

  3. Vue 3

    Vue3 比 Vue2 有什么优势

    性能更好

    体积更小

    更好的 ts 支持

    更好的代码组织

    更好的逻辑抽离

    更多新功能

    Vue3 声明周期

    Options API: beforeDestory 改为 beforeUnmount destoryed 改为 unmouted

    升级内容

    全部用 ts 重写

    性能提升,代码量减少

    调整部分API

  4. 持续更新中...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容