[vue源码03] watch 侦听属性 - 初始化和更新

image

导航

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick

前置知识

一些单词

somewhat:有点
( somewhat expensive operation 操作有点昂贵 )

teardown:卸载

使用案例

<template>
  <div class="about">
    <h1>This is a watch page</h1>

    <div>count = {{count}}</div>
    <button @click="changeCount">change count</button>
    <br />
    <br />

    <div>immediate立即执行:count1 = {{count1}}</div>
    <button @click="changeCount1">change count1</button>
    <br />
    <br />

    <div>deep深度观测 - 遇到问题是新旧值一样,可以用commputed做深拷贝:count2 = {{nestObj.count2}}</div>
    <button @click="changeCount2">change count2</button>
    <br />
    <br />

    <div>count3 = {{nestObj.count3}}</div>
    <button @click="changeCount3">change count3</button>
    <br />
    <br />

    <button @click="changeTestArr">change testArr</button>
    <br />
    <br />

    <button @click="changeAll">改变所有数据 - 验证sync</button>
  </div>
</template>


<script>
export default {
  data() {
    return {
      count: 0,
      count1: 1,
      nestObj: {
        count2: 2,
        count3: 3
      },
      testArr: {
        count4: 4,
        count5: 5
      },
      testHandlerIsFunctionName: 6,
    };
  },
  computed: {
    deepCopyNestObj() {
      return JSON.parse(JSON.stringify(this.nestObj))
    }
  },
  watch: {
    count: function(val, newVal) { // ---------------------------------- 函数
      console.log(val, newVal);
    },
    count1: {
      handler(v, oldv) {
        console.log(v, oldv, "immediate立即执行,不需要依赖变化", "后执行");
      },
      immediate: true
    },
    nestObj: { // ------------------------------------------------------ 对象
      handler(v, oldv) {
        console.log(v.count2, oldv.count2, "sync再nextTick之前先执行");
      },
      deep: true,
      sync: true // 同步 先于 异步的watch执行,默认是异步
    },
    deepCopyNestObj(newVal, oldVal) {
      console.log(newVal.count2, oldVal.count2, 'deep深度观测 - 遇到问题是新旧值一样,可以用commputed做深拷贝')
    },
    "nestObj.count3": function() {
      // 监听对象中的某个属性,可以使用obj.xxx的字符串形式作为key
      console.log("watch到了nestObj.count3");
    },
    testArr: [ // ------------------------------------------------------ 数组
      function handler1() {
        console.log(1111);
      },
      function handler2() {
        console.log(2222);
      }
    ],
    testHandlerIsFunctionName: 'watchHandlerIsFnName' // --------------- 字符串
    // watchHandlerIsFnName 是一个方法,在 methods 定义的方法
    // 当 testHandlerIsFunctionName 变化,就会调用watchHandlerIsFnName方法
  },
  methods: {
    watchHandlerIsFnName(v, oldV) {
      console.log(v, oldV, 'watch对象的 handler 是一个字符串,即一个方法名')
    },
    changeCount() {
      this.count = this.count + 1;
    },
    changeCount1() {
      this.count1 = this.count1 + 1;
    },
    changeCount2() {
      this.nestObj.count2 = this.nestObj.count2 + 1;
    },
    changeCount3() {
      this.nestObj.count3 = this.nestObj.count3 + 1;
    },
    changeAll() {
      this.count = this.count + 1;
      this.count1 = this.count1 + 1;
      this.nestObj.count2 = this.nestObj.count2 + 1;
      this.nestObj.count3 = this.nestObj.count3 + 1;
      this.testArr = this.testArr + 1;
      this.testHandlerIsFunctionName = this.testHandlerIsFunctionName + 1
    },
    changeTestArr() {
      this.testArr = this.testArr + 1
    }
  }
};
</script>

学习目标

  • watch的两种用法

    • 通过组件的参数,watch作为对象
    • 通过 vm.$watch() 方法来调用
  • 避免死循环

    • 比如 watch: { count: {this.count = this.count + 1}}
    • 上面会观测count的变化,变化后修改count,count变化又继续调用cb去修改count,死循环了
  • wath对象的key对象的value的类型

    • function
    • object
    • array
    • string
    • 最终都会把不同类型的 handler 转换成函数
  • watch对象的 options 对象支持的属性

    • deep
      • 深度监听
      • 循环 ( 访问 ) watch对象中的key对应的 vm.key 嵌套对象的每一个属性,从而触发依赖数据的响应式get,通过 dep.depend()
        • 向 user watcher 的 newDeps 中添加 dep
        • 向 dep 的 subs 中添加 user watcher
    • immediate
      • 立即执行cb,即wache对象中的 handler 函数,无需等到依赖变化才去执行
      • 直接调用 cb(watcher.value)
    • sync
      • 保证 ( 同步wath对象的handler ) 在 ( 普通的watch对象的handler ) 前面执行
      • sync 就直接调用 watcher.run() => this.cb.call(this.vm, value, oldValue) 从而直接执行cb函数
  • watch初始化的流程

    1. 处理watche对象key对应的value的各种类型,把object,array,string都处理成对象的function
    2. 执行 vm.$watchg
    3. new userWatcher()
      • constructor中通过this.get()调用getter函数,把watch对象中的key通过 this.getter = parsePath(expOrFn) 方法分割成数组,通过 vm[key] 去访问,返回watch对象中key对应的响应式数据
      • 访问的时候,又会触发响应式数据的get方法,从而进行依赖收集,在dep中收集user watcher,用于更新
  • 更新流程

    • 依赖变化,触发dep.notify(),玄幻dep.subs数据中的watcher.update()去更新
      • 如果 sync=true就直接调用watcher.run => this.cb.call(this.vm, value, oldValue)
      • 如果 sync=false, queueWatcher => nextTick(flushSchedulerQueue) => watcher.run() => this.cb.call(this.vm, value, oldValue)

watch 源码

  • Vue.prototype._init => initState => initWatch(vm, opts.watch) => createWatcher(vm, key, handler) => vm.$watch(expOrFn, handler, options)
  • initWatch - src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
  // initWatch(vm, opts.watch)
  
  for (const key in watch) {
    const handler = watch[key]
    // handler
      // watch对象中的 key 对应的 value
      // 可能是 函数,数组,对象,字符串(方法名)
  
    if (Array.isArray(handler)) {
      // handler是数组,就遍历,把每一个成员传入 createWatcher
        // 成员一般是函数
        // 比如
        // watch: {
        //   testArr: [
        //     function handler1() {
        //       console.log(1111);
        //     },
        //     function handler2() {
        //       console.log(2222);
        //     }
        //   ]
        // }
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      // handler是对象,函数,字符串
      // 比如
      //  watch: {
      //   count: function(val, newVal) {
      //     console.log(val, newVal);
      //   },
      //   count1: {
      //     handler(v, oldv) {
      //       console.log(v, oldv, "immediate立即执行,不需要依赖变化", "后执行");
      //     },
      //     immediate: true,
      //     deep: true,
      //     sync: true,
      //   },
      //   testHandlerIsFunctionName: 'watchHandlerIsFnName'
      // }
      createWatcher(vm, key, handler)
    }
  }
}
  • createWatcher - src/core/instance/state.js
function createWatcher (
  vm: Component,
  expOrFn: string | Function, // watch对象中的 key
  handler: any, // watch对象中的key对应的value => 对象,函数,数组成员,字符串
  options?: Object // 初始化时是 undefined
) {
  if (isPlainObject(handler)) {
    // handler 是一个对象
      // 比如
        // count1: {
        //   handler(v, oldv) {
        //     console.log(v, oldv);
        //   },
        //   immediate: true,
        //   deep: true,
        //   sync: true
        // }
    options = handler
    handler = handler.handler
    // handler是对象,就把handler赋值给options,把handler对象的handler方法赋值给handler变量
    // 其实就是处理参数
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
    // handler 是一个字符串,就赋值这个字符串代表的方法,在methods对象中定义的方法
  }

  return vm.$watch(expOrFn, handler, options)
  // 传入 vm.$watch 的 handler 都已经处理成了 函数
}
  • Vue.prototype.$watch - src/core/instance/state.js
  Vue.prototype.$watch = function (
    expOrFn: string | Function, 
    // expOrFn
      // watch对象的key
    cb: any, 
    // cb 
      // cb是watcher对象中key对应的value各种情况转换之后的handler函数(可能值是函数,数组,对象,字符串,到这里都转成了函数)
      // 如果不是通过watch对象传入new Vue()的方式,而是直接通过vm.$watch传入,则cb就还可能是 (函数,对象,数组,字符串)
    options?: Object
    // options 是配置对象
      // options的属性可能是下面几个
      // handler immediate deep sync 等
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      // 这里又判断了 cb 是不是对象,原因如下
        // 1. 因为 $watch 可以通过 vm.$watch 的方式调用
        // 2. 如果是通过传入 new Vue({}) 以 watch 对象的方法, cd就是已经是经过处理过后的 函数了,不用再判断对象的情况
      
        return createWatcher(vm, expOrFn, cb, options)
        // 所以如果是上面 1 的情况,就又会调用 createWatcher()去处理handler的类型,处理成函数
    }
    options = options || {}
    options.user = true
    // 向 options 对象添加 user 属性,值为true

    const watcher = new Watcher(vm, expOrFn, cb, options)
    // new 一个 user watcher

    if (options.immediate) {
      // immediate 属性存在,就立即执行 cb,即 handler函数
      try {
        cb.call(vm, watcher.value)
        // cb 即 handler 函数,是接收两个参数的,这里只传了第一个参数,所以答应的话第二个参数是 undefined
          // 第一个参数 newValue
          // 第二个参数 oldValue
            // watch: {
            //   count: function(val, newVal) {
            //     console.log(val, newVal);
            //   }
            // }
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }

    return function unwatchFn () {
      //  Vue.prototype.$watch 函数,会返回 unwatchFn 函数

      watcher.teardown()
      // watcher.teardown()
        // 1. 删除_watchers中的 user watcher
        // 2. 删除 user watcher 中的 deps 中的所有 dep

      // teardown () {
      //   if (this.active) {
      //     // this.active = true 默认为true
      //     if (!this.vm._isBeingDestroyed) {
      //       remove(this.vm._watchers, this)
      //       // 移除 watchers 数组中的 watcher
      //     }
      //     let i = this.deps.length
      //     while (i--) {
      //       this.deps[i].removeSub(this)
      //       // 同时删除 watcher 的 deps 中的所有 watcher
      //         // 比如 在 user watcher,$watch方法最后就会删除 user watcher 的 deps 中订阅的 dep
      //     }
      //     this.active = false
      //     // this.active = false
      //   }
      // }
    }
  }
  • watcher - scr/core/observer/watcher.js

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function; // 比如user watcher 中的 handler 函数
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean; // computed watcher 的标志
  sync: boolean; // user watcher 的 options对象中的 sync 属性
  dirty: boolean; // 用于 computed watcher
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user // user watcher 的 options.user 默认为true
      this.lazy = !!options.lazy // computed watcher 的 options.lazy 默认为true
      this.sync = !!options.sync // 用于 user watcher
      this.before = options.before
    } 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 {
      // expOrFn 不是一个函数
        // 因为:user watcher 中的 expOrFn就是watch对象中的key, 就是一个字符串
        // 所以:用 parsePath 函数就行操作

      this.getter = parsePath(expOrFn)
      // this.getter
        // 1. parsePath(expOrFn) 
          // 返回一个函数,返回函数的参数是 vm 实例
            // return function (obj) { 
            //   // 1. path => 比如 expOrFn = path = 'a.b'
            //   // 2. ojb => vm
            //   // 上面 1和2,那么下面的循环:
            //     // vm.a  => 访问到了a
            //     // vm.a.b => 访问到了b
            //   for (let i = 0; i < segments.length; i++) {
            //     if (!obj) return
            //     obj = obj[segments[i]]
            //   }
            //   return obj
            // }
        // 2. this.getter是在 watcher.get()中调用的
          // this.getter.call(vm, vm)
          // 所以:1中返回函数的参数是 vm
      

      // export function parsePath (path: string): any {
      //   if (bailRE.test(path)) {
      //     return
      //   }
      //   const segments = path.split('.')
      //   // segments 可能情况
      //     // 1.'a.b' 即观测 a对象的b属性 => [a, b]
      //     // 2. a => [a]
      //   return function (obj) { 
      //     // 1. path => 比如 expOrFn = path = 'a.b'
      //     // 2. ojb => vm
      //     // 上面 1和2,那么下面的循环:
      //       // vm.a  => 访问到了a
      //       // vm.a.b => 访问到了b
      //     for (let i = 0; i < segments.length; i++) {
      //       if (!obj) return
      //       obj = obj[segments[i]]
      //     }
      //     return obj
      //     // 返回 响应式get函数中返回的值
      //   }
      // }
     
      
      if (!this.getter) {
        this.getter = noop
        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) {
      // 比如 user watcher 的options中配置了 sync:true 时,调用run方法
      this.run()
    } else {
      queueWatcher(this)
      // export function queueWatcher (watcher: Watcher) {
      //   const id = watcher.id
      //   if (has[id] == null) {
      //     has[id] = true
      //     if (!flushing) {
      //       queue.push(watcher)
      //     } else {
      //       // if already flushing, splice the watcher based on its id
      //       // if already past its id, it will be run next immediately.
      //       let i = queue.length - 1
      //       while (i > index && queue[i].id > watcher.id) {
      //         i--
      //       }
      //       queue.splice(i + 1, 0, watcher)
      //     }
      //     // queue the flush
      //     if (!waiting) {
      //       waiting = true
      
      //       if (process.env.NODE_ENV !== 'production' && !config.async) {
      //         flushSchedulerQueue()
      //         return
      //       }
      //       nextTick(flushSchedulerQueue)
      //     }
      //   }
      // }
    }
  }

  /**
   * 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) {
          // 如果是 user watcher 
          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) {
      // this.active = true 默认为true

      // 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)
        // 移除 watchers 数组中的 watcher
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
        // 同时删除 watcher 的 deps 中的所有 watcher
          // 比如 在 user watcher,$watch方法最后就会删除 user watcher 的 deps 中订阅的 dep
      }
      this.active = false
      // this.active = false
    }
  }
}

  • parsePath - src/core/util/lang.js
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  // segments 可能情况
    // 1.'a.b' 即观测 a对象的b属性 => [a, b]
    // 2. a => [a]

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

推荐阅读更多精彩内容