二、响应式原理initState(vm)

1.inState响应式入口

  • initState在import { initState } from './state'
  • 同级目录下找到state.js
export function initState (vm: Component) {  vm._watchers = []  const opts = vm.$options    // 解决props对象 为每一个props对象下面设置响应式,并将其代理到vm的实例上    if (opts.props) initProps(vm, opts.props)    解决methods  校验每个属性的值是否为函数  和props属性比对进行判重解决,并将其代理到vm的实例上    if (opts.methods) initMethods(vm, opts.methods)    1.判重解决,data对象上的属性不能和props、methods对象上的属性雷同  2.代理data到vm实例上的  3.为data对象上的数据设置响应式    if (opts.data) {    initData(vm)  } else {    observe(vm._data = {}, true /* asRootData */)  }    解决conputed  1.为computed【key】 创立 watcher 实例  默认是懒执行  也就是有缓存  2.代理computed【key】,到vm实例  3.判重,computed中的key不能和data,props中的属性反复    if (opts.computed) initComputed(vm, opts.computed)    解决watch  1.解决watch对象  2.为每一watch.key 创立watcher实例,key和watcher 实例可能是一对多的关系  3.如果设置了 immediate,则立刻执行 回调函数    if (opts.watch && opts.watch !== nativeWatch) {    initWatch(vm, opts.watch)  }    computed和watch 在实质上是没有区别的,都是通过 watcher 去实现的响应式  1.watch 实用于当数据变动时执行异步函数或者开销比拟大的操作应用    须要长时间期待操作能够放在watch  2. computed 其中能够应用异步函数,computed更适宜做一些同步计算。  }

2.initProps办法

  • 次要解决父组件传入props
  • /src/core/instance/state.js
  • 为props对象的每一个属性设置响应式,并代理到vm上
function initProps (vm: Component, propsOptions: Object) {      // 写法一    props: ['name']    // 写法二    props: {        name: String, // [String, Number]    }    // 写法三    props: {        name:{            type: String        }    }      propsData:父组件传入的实在props数据。  const propsData = vm.$options.propsData || {}  props:指向vm._props的指针,所有设置到props变量中的属性都会保留到vm._props中。  const props = vm._props = {}    // cache prop keys so that future props updates can iterate using Array  // instead of dynamic object key enumeration.    缓存props 的每个key,做性能优化    const keys = vm.$options._propKeys = []    isRoot:以后组件是否为根组件    const isRoot = !vm.$parent    // root instance props should be converted  if (!isRoot) {    toggleObserving(false)  }  // 遍历props 对象  for (const key in propsOptions) {    // key 是props的key    keys.push(key)    // 收集所有的key,存到空数组外面    const value = validateProp(key, propsOptions, propsData, vm)    /* istanbul ignore else */    if (process.env.NODE_ENV !== 'production') {      const hyphenatedKey = hyphenate(key)      if (isReservedAttribute(hyphenatedKey) ||          config.isReservedAttr(hyphenatedKey)) {        warn(          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,          vm        )      }            // 为props的每一个key是设置数据响应式            defineReactive(props, key, value, () => {        if (!isRoot && !isUpdatingChildComponent) {          warn(            `Avoid mutating a prop directly since the value will be ` +            `overwritten whenever the parent component re-renders. ` +            `Instead, use a data or computed property based on the prop's ` +            `value. Prop being mutated: "${key}"`,            vm          )        }      })    } else {      // 对props数据做响应式解决      defineReactive(props, key, value)    }    // static props are already proxied on the component's prototype    // during Vue.extend(). We only need to proxy props defined at    // instantiation here.    // 做代理,将props上的key代理到this下面    if (!(key in vm)) {      proxy(vm, `_props`, key)    }  }  toggleObserving(true)}

3.proxy 代理办法

  • 设置代理,将key代理到target上
  • /src/core/instance/state.js
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  }    次要应用defineproperty  对每一个key进行代理设置    Object.defineProperty(target, key, sharedPropertyDefinition)}

4.initMethods

  • 初始化代理办法methods
  • 代理到vm实例
function initMethods (vm: Component, methods: Object) {    获取 props 配置项    const props = vm.$options.props    遍历 methods 对象    for (const key in methods) {    if (process.env.NODE_ENV !== 'production') {      if (typeof methods[key] !== 'function') {            断定methods【key】中必须是一个函数      在非生产环境下判断如果methods中某个办法只有key而没有value,      即只有办法名没有办法体时,抛出异样:提醒用户办法未定义。        warn(          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +          `Did you reference the function correctly?`,          vm        )      }            断定 methods 的 key 和 props 不能反复      判断如果methods中某个办法名与props中某个属性名反复了,就抛出异样:提醒用户办法名反复了。      if (props && hasOwn(props, key)) {        warn(          `Method "${key}" has already been defined as a prop.`,          vm        )      }      判断如果methods中某个办法名如果在实例vm中曾经存在并且办法名是以_或$结尾的,      就抛出异样:提醒用户办法名命名不标准。      if ((key in vm) && isReserved(key)) {        warn(          `Method "${key}" conflicts with an existing Vue instance method. ` +          `Avoid defining component methods that start with _ or $.`        )      }    }    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)  }}

5.initData

  • src/core/instance/state.js
  • 初始化data
1.判重解决,data对象上属性和props、methods对象上的属性雷同2.代理 data 对象上的属性到 vm 实例3.为data对象上的数据设置响应式function initData (vm: Component) {  let data = vm.$options.data  获取到用户传入的data选项,赋给变量data    data = vm._data = typeof data === 'function'    ? getData(data, vm)    : data || {}    将变量data作为指针指向vm。data,判断data是不是一个函数    如果是,执行getData函数获取其返回值    如果不是,就将其自身保留到vmdata中    就是传入data选项是不是一个函数,他就应该是个函数      if (!isPlainObject(data)) {    data = {}    process.env.NODE_ENV !== 'production' && warn(      'data functions should return an object:\n' +      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',      vm    )  }        // proxy data on instance  const keys = Object.keys(data)  const props = vm.$options.props  const methods = vm.$options.methods    做判重解决,data对象上的属性不能和props、methods对象上的属性雷同  代理data对象上的属性到vm实例    let i = keys.length  while (i--) {    const key = keys[i]    if (process.env.NODE_ENV !== 'production') {      if (methods && hasOwn(methods, key)) {            反复提醒              warn(          `Method "${key}" has already been defined as a data property.`,          vm        )      }    }    if (props && hasOwn(props, key)) {      process.env.NODE_ENV !== 'production' && warn(        `The data property "${key}" is already declared as a prop. ` +        `Use prop default value instead.`,        vm      )    } else if (!isReserved(key)) {      proxy(vm, `_data`, key)    }  }  // observe data  // 为 data 对象上的数据设置响应式  observe(data, true /* asRootData */)}

6.initComputed

  • 初始化computed
  • 为computed【key】创立wathcer,默认是懒执行
  • 代理到vm实例上
计算属性的后果会被缓存,除非依赖的响应式属性变动才会从新计算。function initComputed (vm: Component, computed: Object) {  // $flow-disable-line  定义了一个watchers,赋值给空对象,同时将其作为指针指向vm._computedWatchers  const watchers = vm._computedWatchers = Object.create(null)  // computed properties are just getters during SSR  const isSSR = isServerRendering()  遍历computed    for (const key in computed) {    const userDef = computed[key]    判断userDef是不是一个函数    是函数        默认赋值给getter的取值器    不是函数        就是个对象,取对象的get属性作为取值器赋值给变量getter    const getter = typeof userDef === 'function' ? userDef : userDef.get        判断环境    if (process.env.NODE_ENV !== 'production' && getter == null) {      如果取值器getter没有取到值,则正告      提醒用户计算属性必须有取值器。      warn(        `Getter is missing for computed property "${key}".`,        vm      )    }        判断是不是服务器渲染        if (!isSSR) {          创立一个watcher实例      创立的实例作为值存入watchers对象中      // create internal watcher for the computed property.      watchers[key] = new Watcher(        vm,        getter || noop,        noop,        computedWatcherOptions      )    }                // component-defined computed properties are already defined on the    // component prototype. We only need to define computed properties defined    // at instantiation here.        判断以后的值是否存在vm的实例上  放弃key的唯一性        if (!(key in vm)) {            不存在 调用函数          defineComputed(vm, key, userDef)    } else if (process.env.NODE_ENV !== 'production') {            则在非生产环境下抛出正告          if (key in vm.$data) {        warn(`The computed property "${key}" is already defined in data.`, vm)      } else if (vm.$options.props && key in vm.$options.props) {        warn(`The computed property "${key}" is already defined as a prop.`, vm)      } else if (vm.$options.methods && key in vm.$options.methods) {        warn(`The computed property "${key}" is already defined as a method.`, vm)      }    }  }}

7.defineComputed

  • computed中的key如果不存在vm的整个实例中(也就是这个key是惟一的)
  • 执行defineComputed 办法
属性描述符const sharedPropertyDefinition = {  enumerable: true,  configurable: true,  get: noop,  set: noop}该办法承受三个参数   target、key和userDef为target上定义一个属性key,并且属性key的getter和setter依据userDef的值来设置export function defineComputed (  target: any,  key: string,  userDef: Object | Function) {  标识计算属性是否该有缓存  判断是否是服务器渲染  true 非服务器渲染  false 服务器渲染    const shouldCache = !isServerRendering()    判断userDef是否函数,就是判断getter的取值        if (typeof userDef === 'function') {    判断是否是服务器渲染       非服务端渲染环境下并没有间接应用userDef作为getter,而是调用createComputedGetter函数   userDef只是一个一般的getter,它并没有缓存性能,所以咱们须要额定创立一个具备缓存性能的getter  服务端渲染环境不须要缓存  然而服务器渲染没有setter,所以将sharedPropertyDefinition.set设置为noop。  设置getter和setter      sharedPropertyDefinition.get = shouldCache      ? createComputedGetter(key)      : createGetterInvoker(userDef)    sharedPropertyDefinition.set = noop  } else {    sharedPropertyDefinition.get = userDef.get      ? shouldCache && userDef.cache !== false        ? createComputedGetter(key)        : createGetterInvoker(userDef.get)      : noop    sharedPropertyDefinition.set = userDef.set || noop  }  if (process.env.NODE_ENV !== 'production' &&      sharedPropertyDefinition.set === noop) {    sharedPropertyDefinition.set = function () {      warn(        `Computed property "${key}" was assigned to but it has no setter.`,        this      )    }  }    调用Object.defineProperty办法将属性key绑定到target上  次要描述性就是sharedPropertyDefinition    Object.defineProperty(target, key, sharedPropertyDefinition)}
  • createComputedGetter()
function createComputedGetter (key) {   返回一个computedGetter  return function computedGetter () {      存储在以后实例上key所对应watcher实例        const watcher = this._computedWatchers && this._computedWatchers[key]            // 执行watcher.evalute 办法    // 执行computed.key的办法,失去函数的执行后果,赋值给watcher.value    // 将watcher.dirty 赋值为false            如果watcher存在,判断dirty是否为true    这个watcher只是会在数据变动后,才会变为true        if (watcher) {      if (watcher.dirty) {        watcher.evaluate()      }      if (Dep.target) {        watcher.depend()      }      return watcher.value    }  }}

在实例化Watcher类的时候,第四个参数传入了一个对象computedWatcherOptions = { computed: true },该对象中的computed属性标记着这个watcher实例是计算属性的watcher实例,即Watcher类中的this.computed属性,同时类中还定义了this.dirty属性用于标记计算属性的返回值是否有变动,计算属性的缓存就是通过这个属性来判断的,每当计算属性依赖的数据发生变化时,会将this.dirty属性设置为true,这样下一次读取计算属性时,会从新计算结果返回,否则间接返回之前的计算结果。

当调用watcher.depend()办法时,会将读取计算属性的那个watcher增加到计算属性的watcher实例的依赖列表中,当计算属性中用到的数据发生变化时,计算属性的watcher实例就会执行watcher.update()办法,在update办法中会判断以后的watcher是不是计算属性的watcher,如果是则调用getAndInvoke去比照计算属性的返回值是否产生了变动,如果真的发生变化,则执行回调,告诉那些读取计算属性的watcher从新执行渲染逻辑。

当调用watcher.evaluate()办法时,会先判断this.dirty是否为true,如果为true,则表明计算属性所依赖的数据产生了变动,则调用this.get()从新获取计算结果最初返回;如果为false,则间接返回之前的计算结果。

export default class Watcher {    constructor (vm,expOrFn,cb,options,isRenderWatcher) {        if (options) {            // ...            this.computed = !!options.computed            // ...        } else {            // ...        }         this.dirty = this.computed         初始值为true                        if (typeof expOrFn === 'function') {            this.getter = expOrFn        }         if (this.computed) {            this.value = undefined            this.dep = new Dep()        }    }     evaluate () {        if (this.dirty) {            this.value = this.get()            this.dirty = false        }        return this.value    }     /**     * Depend on this watcher. Only for computed property watchers.     */    depend () {        if (this.dep && Dep.target) {            this.dep.depend()        }    }     update () {        if (this.computed) {            if (this.dep.subs.length === 0) {                this.dirty = true            } else {                this.getAndInvoke(() => {                    this.dep.notify()                })            }        }    }     getAndInvoke (cb: Function) {        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            this.dirty = false            if (this.user) {                try {                    cb.call(this.vm, value, oldValue)                } catch (e) {                    handleError(e, this.vm, `callback for watcher "${this.expression}"`)                }            } else {                cb.call(this.vm, value, oldValue)            }        }    }}  const computedWatcherOptions = { computed: true }watchers[key] = new Watcher(    vm,    getter || noop,    noop,    computedWatcherOptions)

8.initWatch

  • 初始化watch
function initWatch (vm: Component, watch: Object) {  遍历watch的所有值  for (const key in watch) {      const handler = watch[key]        watch对象的值        if (Array.isArray(handler)) {          handler 为数组,遍历数组,获取其中的每一项,而后调用 createWatcher            for (let i = 0; i < handler.length; i++) {        createWatcher(vm, key, handler[i])      }          } else {          调用createWatcher,办法            createWatcher(vm, key, handler)    }  }}function createWatcher (  vm: Component,  expOrFn: string | Function,  handler: any,  options?: Object) {  如果handler是对象,则获取其中 handler 选项的值    if (isPlainObject(handler)) {    options = handler    handler = handler.handler  }    如果 hander 为字符串,则阐明是一个 methods 办法,获取 vm[handler]    if (typeof handler === 'string') {    handler = vm[handler]  }    return vm.$watch(expOrFn, handler, options)}定义vm.$watch创立watcher,返回一个unwatchFn Vue.prototype.$watch = function (    expOrFn: string | Function,    cb: any,    options?: Object  ): Function {    const vm: Component = this        兼容性解决,因为用户调用 vm.$watch 时设置的 cb 可能是对象            if (isPlainObject(cb)) {      return createWatcher(vm, expOrFn, cb, options)    }            options.user 示意用户 watcher,还有渲染 watcher    即 updateComponent 办法中实例化的 watcher                options = options || {}    options.user = true    const watcher = new Watcher(vm, expOrFn, cb, options)        如果用户设置了 immediate 为 true,则立刻执行一次回调函数        if (options.immediate) {      const info = `callback for immediate watcher "${watcher.expression}"`      pushTarget()      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)      popTarget()    }        返回一个unwatch函数,用于解除监听        return function unwatchFn () {      watcher.teardown()    }  }}