美文网首页程序员
vue 响应式原理解析,

vue 响应式原理解析,

作者: 臣以君纲 | 来源:发表于2019-01-31 13:34 被阅读0次

    vue 的数据驱动渲染逻辑大家已经清楚,下面我们来研究下vue 的数据改变驱动视图重新渲染原理

    记得第一节在讲vue 初使化发生了什么,提到Vue._init 然后执行了initstate,

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

    重点是执行了observe(vm._data = {}, true);vm._data 是我们定义在组件data中的数据,返回的是一个对象,然后我们进入observe,这便是响应式的核心所在,

    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
    }
    

    首先判断是否是对象,不是直接返回,这个函数最后返回了一个new Observe,来看下Observe的定义

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

    这里判断了observe的对象如果是个数组的话,调用observeArray,如果是对象的话,调用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()
          }
          // #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()
        }
      })
    }
    

    我们可以看到耳熟能详的,响应式核心,Object.defineProperty 函数在这之前还执行了observe(val)这里是对对象的属性值进行循环observe,如果对象值仍然是个对象,那么把子对象的属性的get set 也进行劫持,有关于Object.defineProperty 这个函数大家可以自行查阅,是属于es5的一个函数,定义访问器属性,当对属性值赋值时,会调用对他定义的set方法,当取这个属性值时,会调用对他定义的get方法,我们来看对劫持属性get set方法的定义。get函数中,主要执行了dep.depend方法,下面的childOb是对调用$set 方法时提供的依赖收集,这里先不说,dep 时defineReactive开头new Dep()得来的,我们来看Dep的定义

    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()
        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)
        }
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    

    我们找到 depend方法,可以看到,调用了Dep.target.addDep方法,而Dep.target又是在哪定义的呢,我们先回顾下,组建的渲染过程,mountComponent,new Watcher,render,_update,patch, 我们定义的data中对象值的get函数,是在执行render过程中被调用的,而当我们在执行new Wathcer过程中,会执行pushTarget,把当前Watcher 赋值给Dep.target,,所以,执行Dep.target.addDep也就是传入的Watcher的addDep方法,我们进入Watcher中定义的addDep方法,

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

    执行了传入参数的addSub方法,我们知道,我们来看这个参数是什么
    调用的地方

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

    就是调用depend方法那个dep,所以说,绕了一大圈,最后其实就是把当前组件的Watcher存入到dep的subs中去,最后调用的是dep.addSub,而传的参数是当前活跃的watcher也就是此劫持属性所处组件的Watcher,也就是,get方法的劫持就是把当前组件的Watcher存放到此属性值定义的时候初始化的dep的subs数组内,,然后我们来看set的劫持,最后执行了这个dep的notify。我们来看notify

    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)
        }
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    

    对subs数组中所存放的所有Watcher对象执行update方法,也就是执行每个组件的Watcher的update方法,也就是页面重新渲染的具体逻辑,这里有一些优化合并的逻辑,我们下一节来讲

    相关文章

      网友评论

        本文标题:vue 响应式原理解析,

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