# 组件内部状态

现在我们已经知道,vue._init()大致的执行流程,那么其中的state到底是怎么实现的呢?

# initProps

// core/instance/state.js
function initProps (vm: Component, propsOptions: Object) {
  // 传入的props对象
  const propsData = vm.$options.propsData || {}
  // props实际存储的位置,_props
  const props = vm._props = {}
  // 缓存prop键,以便将来的道具更新可以使用Array进行迭代而不是动态对象键枚举。
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  
  // 如果是根节点,那么不可观察
  if (!isRoot) {
    toggleObserving(false)
  }

  // 遍历propsData
  for (const key in propsOptions) {
    // 加入缓存
    keys.push(key)
    // 验证props的值,得到正确的值
    const value = validateProp(key, propsOptions, propsData, vm)

    // 数据劫持
    defineReactive(props, key, value)
    
    // 把props中的每一个值,代理到实例上
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }

  // 状态恢复
  toggleObserving(true)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

从上面代码分析可以看出

  • props的值在被验证之后,被挂载到内部变量_props
  • 设置可以通过this访问的_props代理

# initMethods

function initMethods (vm: Component, methods: Object) {
  for (const key in methods) {
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
  }
}
1
2
3
4
5

从上面代码分析可以看出

  • 遍历methods中所有的方法
  • 通过bind方法使得方法中this指向当前实例
  • 把处理后的方法挂载到当前实例(this)

# initData

function initData (vm: Component) {
  // 获取data选项
  let data = vm.$options.data
  
  // 获取data的指,并且赋值给_data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  
  // 容错处理
  if (!isPlainObject(data)) {
    data = {}
  }
  // 获取所有的key
  const keys = Object.keys(data)
  let i = keys.length

  while (i--) {
    const key = keys[i]
    
    // 如果key不是以$或者_开头
    if (!isReserved(key)) {
      // 把_data中的每一个值,代理到实例上
      proxy(vm, `_data`, key)
    }
  }
  // 为data添加观察者
  observe(data, true)
}

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    // 执行data方法,这里的this指向实例
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

从上面代码分析可以看出

  • 首先获取了传入的data选项中的值,并且把值挂载到内部变量_data
  • 设置可以通过this访问的_data代理
  • 通过调用observe来观察data中的值

# initComputed

// 默认的配置
const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  // 内部变量
  const watchers = vm._computedWatchers = Object.create(null)
  // 服务端渲染?
  const isSSR = isServerRendering()

  for (const key in computed) {
    // 用户穿入的值
    const userDef = computed[key]
    // 获取getter
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    if (!isSSR) {
      // 每一个计算属性都将对应一个 watcher 实例
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // 定义计算属性
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
  }
}

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // 如果不是服务端渲染,那么需要缓存
  const shouldCache = !isServerRendering()

  if (typeof userDef === 'function') {
    // 如果传入的是方法,那么包装
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    // 如果传入的是对象,那么包装
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
 
  // 把key代理到当前实例
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    // 获取对应的watcher
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 懒监听
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        // 添加依赖
        watcher.depend()
      }
      return watcher.value
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  • 定义内部变量_computedWatchers,
  • _computedWatchers对象里面是key相对应的watcher实例,以便于收集compute中的依赖
  • computedkey挂载到当前vue实例上

# initWatch

  • 直接调用this.$watch生成watcher实例
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 问题

  • 问题: 为什么props直接用了defineReactive,而data却用了oberve?
    • props只是做了单个值的观察
    • data的只属性如果也是对象的话,也做了子属性值的数据观察
上次更新: 6/26/2019, 10:38:12 PM