美文网首页
Vue源码03-响应式原理

Vue源码03-响应式原理

作者: 熊少年 | 来源:发表于2020-05-22 14:04 被阅读0次

    这节将专门讲解vue MVVM响应式处理

    image.png
    Vue采用的是数据劫持+观察者模式实现数据的响应式

    数据劫持

    Observer类
    export class Observer {
      value: any;
      dep: Dep;
      vmCount: number; 
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this) // 这里添加__ob__标记
        if (Array.isArray(value)) {
          ...// 保留重要代码
          protoAugment(value, arrayMethods) // 劫持数组的原型方法
          this.observeArray(value) // 劫持数组
        } else {
          this.walk(value) // 劫持对象
        }
      }
    
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
    
      observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }
    
    • Observer会给每一个劫持的数据添加‘ob’属性
    • Observer在给数组进行劫持的时候会先劫持数组原型上面的 methodsToPatch这几个修改数组方法,每一行代码都添加了注释
    const arrayProto = Array.prototype  //获取Array的原型
    export const arrayMethods = Object.create(arrayProto) // 继承Array的原型
    
    const methodsToPatch = [ // 需要拦截的方法
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    methodsToPatch.forEach(function (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)  //如果存在新添加的,则响应式拦截新的数据
        ob.dep.notify()  // 修改完以后视图更新
        return result
      })
    })
    
    
    • 数组的劫持是对数组的每一项在添加劫持
    • 对象的劫持defineReactive
    辅助函数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__') ) {
        ob = value.__ob__
      } else if (
       ....
      ) {
        ob = new Observer(value)
      }
      if (asRootData && ob) {
        ob.vmCount++
      }
      return ob
    }
    
    • 通过ob判断当前对象是否劫持过
    • 如果没有这添加劫持
    辅助函数defineReactive定义具体拦截
      const dep = new Dep()  // 声明一个Dep观察者收集器
      ....
      let childOb = !shallow && observe(val)
      Object.defineProperty(obj, key, {  // 通过defineProperty拦截
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {  // 拦截get
          const value = getter ? getter.call(obj) : val
          if (Dep.target) {
            dep.depend()  // 添加观察者
            ...
          }
          return value
        },
        set: function reactiveSetter (newVal) { // 拦截set
          const value = getter ? getter.call(obj) : val
          ....
          childOb = !shallow && observe(newVal)  // 对新的val添加observe
          dep.notify() // 通知watch更新
        }
      })
    
    暴露set方法
    export function set (target: Array<any> | Object, key: any, val: any): any {
    
      if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        return val
      }
    // 数组的splice已经被拦截了
      if (key in target && !(key in Object.prototype)) {
        target[key] = val
        return val
      }
    // 这里的set已经在oberve里面劫持了
      const ob = (target: any).__ob__
    
      if (!ob) {
        target[key] = val
        return val
      }
      defineReactive(ob.value, key, val)
      ob.dep.notify()
      return val
    }
    

    暴露delete

    export function del (target: Array<any> | Object, key: any) {
    
      if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1)
        return
      }
    
      const ob = (target: any).__ob__
    
      if (!hasOwn(target, key)) {
        return
      }
      delete target[key]
      if (!ob) {
        return
      }
      ob.dep.notify()
    }
    

    Dep 配合拦截器添加Wacher,触发Wacher

    let uid = 0
    
    export default class Dep {
      static target: ?Watcher;
      subs: Array<Watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
     
      addSub (sub: Watcher) {  // 收集Watcher
        this.subs.push(sub)
      }
    
      removeSub (sub: Watcher) { // 删除Watcher
        remove(this.subs, sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)  // Watcher添加Dep
        }
      }
    
      notify () {
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()  // Watcher更新
        }
      }
    }
    
    Dep.target = null
    const targetStack = []
    
    export function pushTarget (target: ?Watcher) { 
      targetStack.push(target)
      Dep.target = target
    }
    export function popTarget () {
      targetStack.pop()
      Dep.target = targetStack[targetStack.length - 1]
    }
    // 这两个函数是为了保证同时只能有一个watcher在求值
    

    Watcher

    export default class Watcher {
      ....
      constructor (
        vm: Component, // vue实例
        expOrFn: string | Function, // 观察的对象
        cb: Function, // 更新回调
        options?: ?Object, // 参数配置
        isRenderWatcher?: boolean
      ) {
        this.vm = vm
        if (isRenderWatcher) {  // 在对watch进行观察的时候需要
          vm._watcher = this
        }
        vm._watchers.push(this)
        ...
        this.cb = cb
        this.dirty = this.lazy // 主要用于computed的watcher
        this.expression = process.env.NODE_ENV !== 'production'
          ? expOrFn.toString()
          : ''
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        } else {
          this.getter = parsePath(expOrFn)
        }
        this.value = this.lazy  // computed不会立即求值
          ? undefined
          : this.get()
      }
    
      get () {  // 求值
        pushTarget(this)
        ...
          value = this.getter.call(vm, vm)
        ...
        popTarget()
        return value
      }
      
      // 添加Sub
      addDep (dep: Dep) {
         ....
         dep.addSub(this)
      }
    // 清除Sub
      cleanupDeps () {
       ...
       dep.removeSub(this)
      }
    
      // 更新
      update () {
        if (this.lazy) {
          this.dirty = true  // computed只需要修改dirty
        } else if (this.sync) {
          this.run() // 异步
        } else {
          queueWatcher(this)  // 队列更新
        }
      }
    
     // 通过cb更新视图
      run () {
         ...
          const value = this.get()
          ...
              this.cb.call(this.vm, value, oldValue)  
    
      }
    
     // computed求值函数
      evaluate () {
        this.value = this.get()
        this.dirty = false
      }
    
      /**
       * Depend on all deps collected by this watcher.
       */
      depend () {
        let i = this.deps.length
        while (i--) {
          this.deps[i].depend()
        }
      }
    
      /**
       * Remove self from all dependencies' subscriber list.
       */
      teardown () {
        if (this.active) {
          let i = this.deps.length
          while (i--) {
            this.deps[i].removeSub(this)
          }
          this.active = false
        }
      }
    }
    
    • 这里先说一下Watcher有三种 data,computed,watch下节将会逐步讲解
    • 把sub的收集放在这里实现主要是利用dep.id过滤
    • 更新也分了3种

    这里总结一下响应式流程
    1 在数据初始化的时候进行数据劫持,这里劫持的有data,props,computed,watch,劫持的是这些数据的get和set
    2 在get里面我们会通过Dep收集watcher
    3 在set里面我们回去触发watcher

    那么有一个问题是watcher是在什么时候初始化的呢?下节将会把这个流程将的更加完善

    相关文章

      网友评论

          本文标题:Vue源码03-响应式原理

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