前置核心概念

Object.defineProperty介绍

也许你已经从很多地方了解到,Vue.js利用了Object.defineProperty(obj, key, descriptor)方法来实现响应式,其中Object.defineProperty()方法的参数介绍如下:

  • obj:要定义其属性的对象。
  • key:要定义或修改属性的名称。
  • descriptor:要定义或修改属性的描述符。

其中descriptor有很多可选的键值, 然而对Vue响应式来说最重要的是getset方法,它们分别会在获取属性值的时候触发getter,设置属性值的时候触发setter。在介绍原理之前,我们使用Object.defineProperty()来实现一个简单的响应式例子:

function defineReactive (obj, key, val) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      console.log('get msg')
      return val
    },
    set: function reactiveSetter (newVal) {
      console.log('set msg')
      val = newVal
    }
  })
}
const vm = {
  msg: 'hello, Vue.js'
}
let msg = ''
defineReactive(vm, 'msg', vm.msg)
msg = vm.msg          // get msg
vm.msg = 'Hello, Msg' // set msg
msg = vm.msg          // get msg

为了在别的地方方便的使用Object.defineProperty()方法,因此我们把其封装成一个defineReactive函数。

proxy代理

在开发过程中,我们经常会使用this.xxx的形式直接访问props或者data中的值,这是因为Vuepropsdata默认做了proxy代理。关于什么是proxy代理,请先看一个简单的例子:

this._data = {
  name: 'AAA',
  age: 23
}
// 代理前
console.log(this._data.name) // AAA
proxy(vm, '_data', 'name')
// 代理后
console.log(this.name)       // AAA

接下来我们详细介绍proxy()方法是如何实现的,在instance/state.js文件中定义了proxy方法,它的代码也很简单:

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

我们可以从上面的代码中发现,proxy方法主要是做了属性的getset方法劫持。

const name = this.name
this.name = 'BBB'
// 等价于
const name = this._data.name
this._data.name = 'BBB'

$options属性

在之前的介绍中,我们知道当初始化Vue实例的时候,传递的options会根据不同的情况进行配置合并,关于详细的options合并策略我们会在之后的章节详细介绍,现阶段我们只需要知道$options可以拿到合并后的所有属性,例如propsmethods以及data等等。

假设我们定义了如下实例:

const vm = new Vue({
  el: '#app',
  props: {
    msg: ''
  },
  data () {
    return {
      firstName: 'AAA',
      lastName: 'BBB',
      age: 23
    }
  },
  methods: {
    sayHello () {
      console.log('Hello, Vue.js')
    }
  },
  computed: {
    fullName () {
      return this.firstName + this.lastName
    }
  }
})

那么我们在之后可以通过下面的方式来取这些属性。

const opts = this.$options
const props = opts.props
const methods = opts.methods
const data = opts.data
const computed = opts.computed
const watch = opts.watch
// ...等等
最后更新时间:
贡献者: wangtunan