响应式对象

Vue.js 实现响应式的核心是利用了 ES5 的 Object.defineProperty,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因,我们先来对它有个直观的认识。

Object.defineProperty

Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法:

  1. Object.defineProperty(obj, prop, descriptor)
  • obj 是要在其上定义属性的对象;
  • prop 是要定义或修改的属性的名称;
  • descriptor 是将被定义或修改的属性描述符

详情请看文档

initState

在Vue做初始化阶段是执行_init(options)方法中会执行一个initState(vm)的方法,这个方法的主要操作:

  • 初始化props
  • 初始化data
  • 初始化Computed
  • 初始化Watcher
  • 初始化Methods
    1. export function initState (vm: Component) {
    2. vm._watchers = []
    3. const opts = vm.$options
    4. if (opts.props) initProps(vm, opts.props)
    5. if (opts.methods) initMethods(vm, opts.methods)
    6. if (opts.data) {
    7. initData(vm)
    8. } else {
    9. observe(vm._data = {}, true /* asRootData */)
    10. }
    11. if (opts.computed) initComputed(vm, opts.computed)
    12. if (opts.watch && opts.watch !== nativeWatch) {
    13. initWatch(vm, opts.watch)
    14. }
    15. }
    这里就是判断对应的props、data、computed、watcher、methods是否存在,存在执行对应的初始化方法

先重点分析props、data

initProps

  1. function initProps (vm: Component, propsOptions: Object) {
  2. const propsData = vm.$options.propsData || {}
  3. const props = vm._props = {}
  4. // cache prop keys so that future props updates can iterate using Array
  5. // instead of dynamic object key enumeration.
  6. const keys = vm.$options._propKeys = []
  7. const isRoot = !vm.$parent
  8. // root instance props should be converted
  9. if (!isRoot) {
  10. toggleObserving(false)
  11. }
  12. for (const key in propsOptions) {
  13. keys.push(key)
  14. const value = validateProp(key, propsOptions, propsData, vm)
  15. /* istanbul ignore else */
  16. if (process.env.NODE_ENV !== 'production') {
  17. const hyphenatedKey = hyphenate(key)
  18. if (isReservedAttribute(hyphenatedKey) ||
  19. config.isReservedAttr(hyphenatedKey)) {
  20. warn(
  21. `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
  22. vm
  23. )
  24. }
  25. defineReactive(props, key, value, () => {
  26. if (vm.$parent && !isUpdatingChildComponent) {
  27. warn(
  28. `Avoid mutating a prop directly since the value will be ` +
  29. `overwritten whenever the parent component re-renders. ` +
  30. `Instead, use a data or computed property based on the prop's ` +
  31. `value. Prop being mutated: "${key}"`,
  32. vm
  33. )
  34. }
  35. })
  36. } else {
  37. defineReactive(props, key, value)
  38. }
  39. // static props are already proxied on the component's prototype
  40. // during Vue.extend(). We only need to proxy props defined at
  41. // instantiation here.
  42. if (!(key in vm)) {
  43. proxy(vm, `_props`, key)
  44. }
  45. }
  46. toggleObserving(true)
  47. }

initProps主要实现就是遍历props中的每个属性,执行defineReactive(props,key,value)将每个值转为响应式数据
defineReactive()如何将对象的每个属性转为响应式数据后面在详细介绍。

initData

  1. function initData (vm: Component) {
  2. let data = vm.$options.data
  3. data = vm._data = typeof data === 'function'
  4. ? getData(data, vm)
  5. : data || {}
  6. if (!isPlainObject(data)) {
  7. data = {}
  8. process.env.NODE_ENV !== 'production' && warn(
  9. 'data functions should return an object:\n' +
  10. 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
  11. vm
  12. )
  13. }
  14. // proxy data on instance
  15. const keys = Object.keys(data)
  16. const props = vm.$options.props
  17. const methods = vm.$options.methods
  18. let i = keys.length
  19. while (i--) {
  20. const key = keys[i]
  21. if (process.env.NODE_ENV !== 'production') {
  22. if (methods && hasOwn(methods, key)) {
  23. warn(
  24. `Method "${key}" has already been defined as a data property.`,
  25. vm
  26. )
  27. }
  28. }
  29. if (props && hasOwn(props, key)) {
  30. process.env.NODE_ENV !== 'production' && warn(
  31. `The data property "${key}" is already declared as a prop. ` +
  32. `Use prop default value instead.`,
  33. vm
  34. )
  35. } else if (!isReserved(key)) {
  36. proxy(vm, `_data`, key)
  37. }
  38. }
  39. // observe data
  40. observe(data, true /* asRootData */)
  41. }

initData也做了两件事,第一就是遍历data返回的对象属性,通过proxy把每个属性都可以通过vm.key访问值,第二则是调用ovserve观察这个data的变化,把data中的每个属性变为响应式。

Proxy