【源码阅读笔记】vue2(一)—— Observer

53 阅读4分钟

从这篇文章开始阅读Vue2响应式源码,后续还会阅读Vue3响应式的源码来进行比较,特此记录。

function initState

响应式的起点在initState函数中。
initState函数接受传递进来的vue实例,在函数中主要是对实例上的props、methods、data、computed、watch做了不同的处理。
而在对data的处理中,调用了observe方法,observe就是响应式的开始。

//src\core\instance\state.ts
export function initState(vm: Component) {
  // vm:vue实例
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)

  // Composition API
  initSetup(vm)

  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
  //initData中也调用了observe
    initData(vm)
  } else {
    const ob = observe((vm._data = {}))
    ob && ob.vmCount++
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initState方法不做过多展开,之后再来填坑,接下来步入正题。

function observe

响应式源码主要在src\core\observer文件夹下,这篇文章来阅读其中的index.ts文件。
该文件主要实现了observe方法Observer类defineReactive方法
先来看看响应式的入口observe。

//响应式入口
export function observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  //__ob__属性是一个标记,为了避免重复创建响应式对象。class Observer中会讲到
  //拥有__ob__属性则直接返回,避免重复创建
  if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    return value.__ob__
  }
  if (
    //需要响应式
    shouldObserve &&
    (ssrMockReactivity || !isServerRendering()) &&
    //是数组或普通对象
    (isArray(value) || isPlainObject(value)) &&
    //判断对象是否可扩展
    Object.isExtensible(value) &&
    //不是无需响应的对象
    !value.__v_skip /* ReactiveFlags.SKIP */ &&
    //不是响应式对象
    !isRef(value) &&
    !(value instanceof VNode)
  ) {
    // 为value创建Observer响应式对象
    return new Observer(value, shallow, ssrMockReactivity)
  }
}

总结来说,observe方法主要的作用是判断传入的value是否需要做响应式处理,核心是new Observer

class Observer

//可观测对象Observer类
export class Observer {
  //记录依赖关系的容器
  dep: Dep
  vmCount: number // number of vms that have this object as root $data

  constructor(public value: any, public shallow = false, public mock = false) {
    //创建dep容器
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    //标记自己,避免重复创建,__ob__就是Observer对象
    def(value, '__ob__', this)
    //判断是对象还是数组
    if (isArray(value)) {
      //是数组
      //mock?
      if (!mock) {
        //__proto__是否可用 有些浏览器不支持
        if (hasProto) {
          //覆盖__proto__
          //arrayMethods是vue2重写后的Array.prototype,使原生数组方法引起的数组变化能够被监听
          ;(value as any).__proto__ = arrayMethods
        } else {
          //遍历数组方法,覆盖value数组上的数组方法
          //def方法内使用Object.defineProperty覆盖数组方法
          for (let i = 0, l = arrayKeys.length; i < l; i++) {
            const key = arrayKeys[i]
            def(value, key, arrayMethods[key])
          }
        }
      }
      //是否为浅式响应式
      if (!shallow) {
        //数组内每一个元素都创建observer
        this.observeArray(value)
      }
    } else {
      //是对象
      //遍历对象,对每个属性执行defineReactive方法
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
  //Observe a list of Array items.
  observeArray(value: any[]) {
    //遍历数组元素,调用observe
    //为了在数组嵌套或数组内有对象的情况下,能够深度监听
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], false, this.mock)
    }
  }
}

Observer类的构造函数中,对value的类型做了区分。
若value是数组,则只是用vue2框架重写的数组方法替换了数组的原生方法,这也就解释了为什么直接用数组下标修改数组不会触发响应式。
若value是对象则是遍历属性,调用defineReactive方法。

function defineReactive

//定义响应式属性
export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean,
  observeEvenIfShallow = false
) {
  //创建dep容器
  //这里的dep用于收集obj.key的依赖 //闭包
  //Observer类中的dep收集obj的依赖
  const dep = new Dep()
  //获取obj对象上的key属性的配置对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  //属性存在 && 属性不可被修改
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  //获取原生getter setter
  const getter = property && property.get
  const setter = property && property.set
  
  if (
    (!getter || setter) &&
    (val === NO_INITIAL_VALUE || arguments.length === 2)
  ) {
    val = obj[key]
  }
  //val是obj.key,如果val是对象,那么childOb就是一个Observer实例
  //有了孩子的Observer,当孩子改变时我们就能知道了
  let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)
  //在obj对象上覆写key属性的配置对象
  Object.defineProperty(obj, key, {
    //可被枚举
    enumerable: true,
    //可修改
    configurable: true,
    //重写getter
    get: function reactiveGetter() {
      // 原生方法获取value
      const value = getter ? getter.call(obj) : val
      //Dep.target是watcher对象
      //Dep.target是变化的 根据当前解析流程不停地指向不同的watcher
      //关于watcher的实现细节之后写
      if (Dep.target) {
        //环境判断
        //主要是调用了dep的depend方法
        //depend方法中调用watcher的addDep方法 
        //addDep方法又会调用dep的addSub方法
        //最终目的是向dep容器存入watcher实现依赖的收集 
        if (__DEV__) {
          dep.depend({
            target: obj,
            type: TrackOpTypes.GET,
            key
          })
        } else {
          dep.depend()
        }
        // childOb有值 说明该属性是数组或对象
        if (childOb) {
          //子元素的依赖收集 即将同一个watcher存入自身属性的dep和子元素的dep
          //childOb.dep这里的dep是子元素的Observer实例上的dep
          childOb.dep.depend()
          //是否为数组
          if (isArray(value)) {
            //遍历数组 调用数组内每一个元素的depend
            dependArray(value)
          }
        }
      }
      return isRef(value) && !shallow ? value.value : value
    },
    //对getter的理解我们可以假设一个场景,假设变量x的值依赖于data对象上的a属性,
    //在vue第一次获取x的值的时候就会调用data.a的getter,
    //这是watcher就像是一条登记信息,dep是一个登记本,在data.a维护的一本登记本上写下了x的信息,
    //这样在data.a变化时,就可以通过dep登记本上的信息,来通知x更新。
    //其实为了避免重复收集以及方便依赖重置,watcher也会维护一个数组用来记录自己依赖的dep。具体细节在watcher源码中讲
    
    //重写setter
    set: function reactiveSetter(newVal) {
      //原生方法获取value
      const value = getter ? getter.call(obj) : val
      //判断值是否变化 无变化直接返回
      if (!hasChanged(value, newVal)) {
        return
      }
      if (__DEV__ && customSetter) {
        customSetter() 
      }
      //有setter则执行原生setter
      if (setter) {
        setter.call(obj, newVal)
      } else if (getter) {
        // #7981: for accessor properties without setter
        return
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        //非浅式响应式 && value是响应式对象 && newVal不是响应式对象
        value.value = newVal
        return
      } else {
        val = newVal
      }
      // 如果newVal是对象,那么对newVal执行observe方法进行响应式处理
      childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock)
      //调用dep.notify
      //notify函数内:遍历dep容器内所有watcher ,用每一个watcher的update方法
      if (__DEV__) {
        dep.notify({
          type: TriggerOpTypes.SET,
          target: obj,
          key,
          newValue: newVal,
          oldValue: value
        })
      } else {
        dep.notify()
      }
    }
    //setter主要做的就是更新值然后调用notify方法派发更新
  })
  return dep
}

到这里,observer文件夹内index文件的内容就讲完了,vue重写了getter/setter实现了依赖收集与更新,从而完成响应式。
这里绕不开的两个类是DepWatcher,下一篇就来看看这两个类的实现。