美文网首页
Vue源码阅读、七

Vue源码阅读、七

作者: C脖子 | 来源:发表于2018-07-24 19:57 被阅读48次

    响应式原理

    Vue实现响应式的核心是利用了Object.defindProperty为对象的属性添加getter和setter方法,接下来我们从源码层面来看具体的实现方式

    initState

    Vue在初始化的过程中会调用initState方法,他定义在core/instance/state.js:

    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    

    initState方法对vm.$options中的props, methods,data,computed,watch这些属性做了初始化操作。

    • initProps:
    function initProps (vm: Component, propsOptions: Object) {
      const propsData = vm.$options.propsData || {}
      const props = vm._props = {}
      // cache prop keys so that future props updates can iterate using Array
      // instead of dynamic object key enumeration.
      const keys = vm.$options._propKeys = []
      const isRoot = !vm.$parent
      // root instance props should be converted
      if (!isRoot) {
        toggleObserving(false)
      }
      for (const key in propsOptions) {
        keys.push(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
            )
          }
          defineReactive(props, key, value, () => {
            if (vm.$parent && !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 {
          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.
        if (!(key in vm)) {
          proxy(vm, `_props`, key)
        }
      }
      toggleObserving(true)
    }
    

    initProps方法对props中的每一个key调用了defineReactive方法,然后调用proxy方法将属性代理到vm实例上。

    • initData
      function initData (vm: Component) {
      let data = vm.options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : 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 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
      observe(data, true /* asRootData */)
      }
    `initData`方法在开发环境下检查data中的属性是否已经在props和methods里重复定义,然后调用
    `proxy`方法将这些属性代理到vm实例上。最后调用`observe`方法
    
    那么我们重点看一下`observe`方法和`defindReactive`方法做了什么
    ## `observe`
    `observe`方法定义在`core/observer/index.js`中:
    

    export function observe (value: any, asRootData: ?boolean): Observer | void {
    if (!isObject(value) || value instanceof VNode) {
    return
    }
    let ob: Observer | void
    if (hasOwn(value, 'ob') && value.ob instanceof Observer) {
    ob = value.ob
    } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
    ) {
    ob = new Observer(value)
    }
    if (asRootData && ob) {
    ob.vmCount++
    }
    return ob
    }

    `observe`方法为非VNode的对象类型值添加了一个`Observer`对象,`Observer`的定义:
    

    export class Observer {
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that has this object as root $data

    constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, 'ob', this)
    if (Array.isArray(value)) {
    const augment = hasProto
    ? protoAugment
    : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
    } else {
    this.walk(value)
    }
    }

    /**

    • Walk through each property and convert them into
    • getter/setters. This method should only be called when
    • value type is Object.
      */
      walk (obj: Object) {
      const keys = Object.keys(obj)
      for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
      }
      }

    /**

    • Observe a list of Array items.
      */
      observeArray (items: Array<any>) {
      for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
      }
      }
      }
    `Observer`的构造函数创建了一个`Dep`对象,然后通过`def(value, '__ob__', this)`将这个对象的`__ob__`属性设置为自身,这会使`__ob__`属性的descriptor中的`enumerable`为false。接下来调用`walk`方法对这个对象的每一个属性调用`defineReactive`方法。
    ## `defineReactive`
    

    export function defineReactive (
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
    ) {
    const dep = new Dep()

    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
    return
    }

    // cater for pre-defined getter/setters
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
    }

    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
    dep.depend()
    if (childOb) {
    childOb.dep.depend()
    if (Array.isArray(value)) {
    dependArray(value)
    }
    }
    }
    return value
    },
    set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    /* eslint-disable no-self-compare /
    if (newVal === value || (newVal !== newVal && value !== value)) {
    return
    }
    /
    eslint-enable no-self-compare */
    if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter()
    }
    if (setter) {
    setter.call(obj, newVal)
    } else {
    val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
    }
    })
    }

    `defineReactive`的作用是将对象上的一个属性定义为响应式属性,它的逻辑也十分清晰。首先创建了一个`Dep`对象,然后对这个属性的值递归地调用`observe`方法,使这个属性的值也成为响应式的,最后用`Object.defineProperty`为这个属性的descriptor定义了`get`和`set`方法
    
    ## getter
    我们来看getter部分的逻辑,它会调用`dep.depend()`方法,那么我们首先来看`Dep`是什么。
    
    ### Dep
    `Dep`的定义是在`core/observer/dep.js`
    

    export default class Dep {
    static target: ?Watcher;
    id: number;
    subs: Array<Watcher>;

    constructor () {
    this.id = uid++
    this.subs = []
    }

    addSub (sub: Watcher) {
    this.subs.push(sub)
    }

    removeSub (sub: Watcher) {
    remove(this.subs, sub)
    }

    depend () {
    if (Dep.target) {
    Dep.target.addDep(this)
    }
    }

    notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
    }
    }
    }

    `Dep`表示dependence,它是用来记录有那些组件依赖了特定的属性。每个响应式的属性都有一个Dep对象,Dep对象的`subs`属性就表示subscript,订阅。`subs`数组中放的是`Watcher`类型的值。
    
    ### `Watcher`
    `Watcher`的定义在`core/observer/watcher.js`,代码太长就不贴了。
    `Watcher`的构造函数定义了一些`Dep`的数组
    
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    

    // ...
    if (this.computed) {
    this.value = undefined
    this.dep = new Dep()
    } else {
    this.value = this.get()
    }

    如果不是计算属性,`Watcher`的构造函数最后调用了`this.get()`方法。
    ### 流程
    `Watcher`的创建是在Vue挂载的时候,在`mountComponent`方法中有这么一段逻辑: 
    
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    

    // ...
    new Watcher(vm, updateComponent, noop, {
    before () {
    if (vm._isMounted) {
    callHook(vm, 'beforeUpdate')
    }
    }
    }, true /* isRenderWatcher */)
    hydrating = false

    当一个组件挂载的时候,创建`Watch`,执行`this.get()`方法,首先调用
    

    pushTarget(this)

    pushTarget 的定义在 src/core/observer/dep.js 中:
    ···
    export function pushTarget (_target: Watcher) {
      if (Dep.target) targetStack.push(Dep.target)
      Dep.target = _target
    }
    ···
    就是讲当前的`Dep.target`入栈,然后将`Dep.target`设置为当前的watcher。接着执行了
    

    value = this.getter.call(vm, vm)

    `this.getter`也就是传入的`updateComponent`方法,对组件进行了一次渲染更新。在渲染更新的时候会访问到vm上的数据,也就调用了响应式属性的getter,getter中会调用`dep.depend()`
    

    depend () {
    if (Dep.target) {
    Dep.target.addDep(this)
    }
    }

    又调用了`Dep.target.addDep(this)
    

    addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
    dep.addSub(this)
    }
    }
    }

    `addDep`方法检查了dep是否重复,如果没有重复就将这个dep添加到`this.newDeps`数组,并且经这个watcher添加到dep的subs数组中
    
    `updateComponet`的过程中会触发所有数据的getter,也就完成了一个依赖收集过程,记录了当数据发生变化,有哪些组件需要更新。
    
    最后,`Watcher`的`get()`方法执行了
    
      popTarget()
      this.cleanupDeps()
    
    将Dep.Target恢复成父级组件的Watcher,并清空已经不再依赖的数据的Dep
    
    ## setter
    设置响应式属性的值的时候,它的setter会被调用。
    setter的逻辑有这么几点:
    1. 如果旧的值和新的值相等或都是NaN,则返回
    2. 调用
    
      childOb = !shallow && observe(newVal)
    
    递归地将为新值添加getter/setter
    3. 调用
    
      dep.notify()
    

    notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
    }
    }

    调用每个订阅此属性的Watcher的`update`方法
    

    class Watcher {
    // ...
    update () {
    /* istanbul ignore else */
    if (this.computed) {
    // A computed property watcher has two modes: lazy and activated.
    // It initializes as lazy by default, and only becomes activated when
    // it is depended on by at least one subscriber, which is typically
    // another computed property or a component's render function.
    if (this.dep.subs.length === 0) {
    // In lazy mode, we don't want to perform computations until necessary,
    // so we simply mark the watcher as dirty. The actual computation is
    // performed just-in-time in this.evaluate() when the computed property
    // is accessed.
    this.dirty = true
    } else {
    // In activated mode, we want to proactively perform the computation
    // but only notify our subscribers when the value has indeed changed.
    this.getAndInvoke(() => {
    this.dep.notify()
    })
    }
    } else if (this.sync) {
    this.run()
    } else {
    queueWatcher(this)
    }
    }
    }

    如果不是计算属性获知this.sync为false,就会调用`queWatcher(this)`
    

    export function queueWatcher (watcher: Watcher) {
    const id = watcher.id
    if (has[id] == null) {
    has[id] = true
    if (!flushing) {
    queue.push(watcher)
    } else {
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
    i--
    }
    queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue)
    }
    }
    }

    `queueWatcher`将watcher加入到一个队列,在nextTick调用`flushSchedulerQueue`
    

    function flushSchedulerQueue () {
    flushing = true
    let watcher, id

    // Sort queue before flush.
    // This ensures that:
    // 1. Components are updated from parent to child. (because parent is always
    // created before the child)
    // 2. A component's user watchers are run before its render watcher (because
    // user watchers are created before the render watcher)
    // 3. If a component is destroyed during a parent component's watcher run,
    // its watchers can be skipped.
    queue.sort((a, b) => a.id - b.id)

    // do not cache length because more watchers might be pushed
    // as we run existing watchers
    for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
    watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
    circular[id] = (circular[id] || 0) + 1
    if (circular[id] > MAX_UPDATE_COUNT) {
    warn(
    'You may have an infinite update loop ' + (
    watcher.user
    ? in watcher with expression "${watcher.expression}"
    : in a component render function.
    ),
    watcher.vm
    )
    break
    }
    }
    }

    // keep copies of post queues before resetting state
    const activatedQueue = activatedChildren.slice()
    const updatedQueue = queue.slice()

    resetSchedulerState()

    // call component updated and activated hooks
    callActivatedHooks(activatedQueue)
    callUpdatedHooks(updatedQueue)

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
    devtools.emit('flush')
    }
    }

    `flushSchedulerQueue`首先按照id从小到大对队列中的watcher进行排序,这么做是为了要确保以下几点:
    1.组件的更新由父到子;因为父组件的创建过程是先于子的,所以 watcher 的创建也是先父后子,执行顺序也应该保持先父后子。
    2.用户的自定义 watcher 要优先于渲染 watcher 执行;因为用户自定义 watcher 是在渲染 watcher 之前创建的。
    3.如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都可以被跳过,所以父组件的 watcher 应该先执行。
    然后调用每个watcher的`run`方法。
    

    run () {
    if (this.active) {
    this.getAndInvoke(this.cb)
    }
    }

    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)
    }
    }
    }

    `run`方法实际上就是调用了`getAndInvoke`方法。而`getAndInvoke`有调用了`this.get()`方法,`this.get()`方法中会调用`this.getter`,也就是`vm._update(vm._render(), hydrating)`去重新渲染更新组件。
    
    ## 总结
    Vue的响应式原理实际上就是利用了`Object.defineProperty`为对象的属性定义了getter/setter,用`Dep`和`Watcher`配合完成了依赖收集和派发更新的过程。每一个响应式的属性对应一个Dep,而每一个组件对应一个Watcher。当属性更新是就通知依赖这个属性的组件去更新。

    相关文章

      网友评论

          本文标题:Vue源码阅读、七

          本文链接:https://www.haomeiwen.com/subject/xfcymftx.html