vue2+ 源码分析(1)

284 阅读2分钟

vue源码分析

1数据响应式原理分析

1关键原理

Object.defineProperty,从get方法中添加监听依赖,在set方法中触发依赖

2 结合源码,详细分析

  1. 在vue初始化的时候,会触发initData函数,拿到this.$option.data值,此时的data并不是一个对象,而是函数,需要通过getData将其转成对象的形式,在根据在methodsprops是否已经有申明了data对象的值,对其进行校验,之后就是通过observe函数进行监听。observe(data, true /* asRootData */)

    // state.js
    function initData (vm: Component) {
      let data = vm.$options.data // 拿到vue的data函数
      // 这里的data如果是函数的话,会在getData中执行,data.call(vm,vm),返回一个对象
      // {name: '小明'}
      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 */)
    }
    
  2. observe函数,主要是判断data是否已经标志为观察了,如果没有,则实例化new Observe(value)

    // observe/index.js
    export function observe (value: any, asRootData: ?boolean): Observer | void {
      // 如果data不是对象的话,就返回
      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
    }
    
  3. Observe类中,实例化后会注册Dep()实例,Array.isArray(value)判断是否是数组,是的话执行observeArray,还是遍历执行observe函数,深度观察数组。对象则直接调用defineReactive(obj, key),即重新定义对象数据

    // observe/index.js
    class Observer {
      value: any;
      dep: Dep;
      vmCount: number; // number of vms that have this object as root $data
    
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        // Array.isArray(value)判断是否是数组,是的话执行observeArray,还是遍历执行observe函数,对象则直接调用defineReactive(obj, key) 
        if (Array.isArray(value)) {
          if (hasProto) {
            protoAugment(value, arrayMethods)
          } else {
            copyAugment(value, arrayMethods, arrayKeys)
          }
          this.observeArray(value) // 深度观察数组
        } else {
          this.walk(value)
        }
      }
    
      /**
       * Walk through all properties 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])
        }
      }
    }
    
  4. defineReactive重新定义响应式数据,定义Object.defineProperty,获取数据时,触发dep.depend,修改数据时,触发对应数值更新dep.notify()

    export function defineReactive (
      obj: Object,
      key: string,
      val: any,
      customSetter?: ?Function,
      shallow?: boolean
    ) {
      const dep = new Dep() // 实例化一个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) // 递归,如果是还是对象,则继续执行observe
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          const value = getter ? getter.call(obj) : val
          if (Dep.target) {
            dep.depend() // 收集依赖watcher
            if (childOb) {
              childOb.dep.depend() // 收集依赖watcher
              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()
          }
          // #7981: for accessor properties without setter
          if (getter && !setter) return
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = !shallow && observe(newVal)
          dep.notify() // 触发对应数值更新
        }
      })
    }
    
  5. 解决疑难点。

    这里为什么会判断Dep.target?

    if (Dep.target) {
        dep.depend() // 收集依赖watcher
        if (childOb) {
        	childOb.dep.depend() // 收集依赖watcher
        	if (Array.isArray(value)) {
        		dependArray(value)
        	}    }
    }
    
    // 这里的`Dep.target`为`true`是因为前面`notify()`调用了`pushTarget`
    
    function pushTarget (target: ?Watcher) {
      targetStack.push(target)
      Dep.target = target
    }
    

3 总结

2数组是如何被监测的

1关键原理

  • 在VUE原型链上重写了数组的方法pushpop等方法,这样在调用数组API的时候,会进行数据监测,通知依赖更新。如果数组中含有引用类型,会对引用类型进行监测。

2结合源码,详细分析

  1. Observe实例中,如果new Observer(value),则会对value进行判断,如果为数组的话,会对数组的操作方法进行重写

    // observe/index.js
    // Observe类中一截
    if (Array.isArray(value)) {
          if (hasProto) {
            protoAugment(value, arrayMethods) 
          } else {
            copyAugment(value, arrayMethods, arrayKeys) // arrayMethods为重写的数组的方法
          }
          this.observeArray(value) // 深度遍历数组,如果数组中还有对象,则继续走observe
        } else {
          this.walk(value)
        }
    
    // observeArray方法
    observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    
  2. 对数组方法重写,调用时触发dep.notify(),进行视图更新

    // observe/array.js
    const arrayProto = Array.prototype
    export const arrayMethods = Object.create(arrayProto)
    
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    /**
     * Intercept mutating methods and emit events
     */
    // 对原来的数组的方法进行重写,保留了原来的方法,扩展了观察。手动调用dep.notify(),更新视图
    methodsToPatch.forEach(function (method) {
      // cache original method
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })
    

vue异步渲染原理

1. 特点

vue如果不进行异步更新的话,会在每次数据更新时,重新渲染组件,造成性能问题。

2. 结合源码,详细分析

  1. dep.notify(),触发数据更新,通知watcher

    // dep.js中
    // 执行watcher中的update方法
    notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        if (process.env.NODE_ENV !== 'production' && !config.async) {
          // subs aren't sorted in scheduler if not running async
          // we need to sort them now to make sure they fire in correct
          // order
          subs.sort((a, b) => a.id - b.id)
        }
        // 遍历subs,触发watcher.js中的update
        // subs中存放的是多个watcher,会一并触发。
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    
  2. watcher类中的update触发,会将watch放在queueWatcher队列中

    update () {
        /* istanbul ignore else */
        if (this.lazy) {
          this.dirty = true
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
  3. queueWatcher中主要是过滤watcher,根据plushing状态去区分pushqueue中,还是移除。最后执行nextTick(flushSchedulerQueue)

    function queueWatcher (watcher: Watcher) {
      const id = watcher.id // 过滤watcher的id,如果存在则不处理,不存在则标记
      if (has[id] == null) {
        has[id] = true
        if (!flushing) { // 如果状态是!flushing的,则放入queue队列中
          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) // flushing则移除
        }
        // queue the flush
        if (!waiting) {
          waiting = true
    
          if (process.env.NODE_ENV !== 'production' && !config.async) {
            flushSchedulerQueue()
            return
          }
          nextTick(flushSchedulerQueue)
        }
      }
    }
    
  4. ``nextTick(flushSchedulerQueue)中执行watcher.run()`

    function flushSchedulerQueue () {
      currentFlushTimestamp = getNow()
      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')
      }
    }
    

3总结

  • 调用 notify() 方法,通知watcher 进行更新操作
  • 依次调用watcher 的 update 方法
  • 对watcher 进行去重操作(通过id),放到队列里
  • 执行完后异步清空这个队列, nextTick(flushSchedulerQueue) 进行批量更新操作