乐趣区

关于vue.js:Vue源码解析二

二、响应式原理 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()
    }
  }
}
退出移动版