【vue2】响应式相关

定义

所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制

vue2响应式初始化

主要涉及vue2中的observe、Oberver构造函数、defineReactive函数主要流程如下:
【vue2】响应式相关_第1张图片
以上流程描述数据如何被递归的转换成响应式数据,但不包含里面所有的执行细节

observe函数

主要判断传入对象是否应该具有Observer实例,并返回获取的Oberver对象
具体源代码如下:

observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  /** 
   * 当值不是对象,或为ref的值,或为虚拟节点时,直接结束函数 
   * 也就是说,并不会处理这三种类型的值
   */
  if (!isObject(value) || isRef(value) || value instanceof VNode) {
    return
  }
  /** 确认此value值的__ob__对象,如果没有,则创建一个,最后返回此ob对象 */
  let ob: Observer | void
  /** 查看value值是否存在__ob__对象,有则返回 */
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve && /** 是否允许创建Observer对象 */
    (ssrMockReactivity || !isServerRendering()) && // 当前为SSR或不在SSR渲染时
    (isArray(value) || isPlainObject(value)) && // value为对象或数组
    Object.isExtensible(value) && // 对象是可以进行扩展的,即描述符writerble为ture
    !value.__v_skip /* ReactiveFlags.SKIP ??? */
  ) {
    /** 创建Obsere对象  */
    ob = new Observer(value, shallow, ssrMockReactivity)
  }
  return ob
}

Observer构造函数

主要功能:

  • 对数组的方法进行劫持
  • 遍历数组项,进行响应式转化
  • 遍历对象属性,进行响应式转化
    具体源代码如下:
 class Observer {
  dep: Dep
  vmCount: number // number of vms that have this object as root $data(将此对象作为根$data的虚拟机数)
  constructor(public value: any, public shallow = false, public mock = false) {
    // this.value = value
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    /** 向value对象,添加__ob__指向自身创建的Observer对象 */
    def(value, '__ob__', this)
    if (isArray(value)) {
      if (!mock) {
        /** 判断当前环境对象中,是否含有__proto__属性 */
        if (hasProto) {
          /** 
           * 结合array.ts来看,arrayMethods是一个继承了数组原型对象的对象 
           * 所以在访问劫持的对应方法时,会先访问到arrayMethods中的方法
          */
          /* eslint-disable no-proto */
          ;(value as any).__proto__ = arrayMethods
          /* eslint-enable no-proto */
        } else {
          /** 
           * 当不存在宿主环境中,并不存在对象默认的__proto__属性时,
           * 则遍历的将arrayMethods上的方法付给此对象
           * 所以尽可能在创建的数组对象中,不要重新写入新的同名方法
           * 即pop,push,shift,unshift,srot,reverse,splice
           */
          for (let i = 0, l = arrayKeys.length; i < l; i++) {
            const key = arrayKeys[i]
            def(value, key, arrayMethods[key])
          }
        }
      }
      /** 当创建的值,非浅拷贝的情况下,进行深层次的监听 */
      if (!shallow) {
        this.observeArray(value)
      }
    } else {
      /**
       * Walk through all properties and convert them into(浏览所有属性并将其转换为)
       * getter/setters. This method should only be called when(接受者/接受者。只有在以下情况下才应调用此方法:)
       * value type is Object.(值类型为对象。)
       * 遍历对象的属性,并进行响应式处理
       */
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        defineReactive(value, key, NO_INIITIAL_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中__ob__是否存在,且此value值是否符合创建的条件*/
      observe(value[i], false, this.mock)
    }
  }
}

defineReactive函数

主要功能

  • 将对应值进行observer转换
  • 进行属性劫持
    具体源代码如下:(去除了dev环境部分)
function defineReactive(
  obj: object, /** 监听的对象 */
  key: string, /** 对应的属性 */
  val?: any,  /** 对应属性值 */
  customSetter?: Function | null, /** 开发环境执行的函数,可不需要关注*/
  shallow?: boolean, /** 是否需要深层递归 */
  mock?: boolean /** 是否是ssr环境 */
) {
  /** 创建依赖收集对象 */
  const dep = new Dep()
  /** 获取key在obj本身的属性,而不是通过原型链获取的 */
  const property = Object.getOwnPropertyDescriptor(obj, key)
  /** key对应的属性不存在,或不可设置时,离开函数 */
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  /** 缓存默认的getter,settter函数 */
  const getter = property && property.get
  const setter = property && property.set
  /** 
   * 如果只存在setter函数,且传入的参数为2个 
   * 此时,使用obj[key]覆盖val值
   * */
  if (
    (!getter || setter) &&
    (val === NO_INIITIAL_VALUE || arguments.length === 2)
  ) {
    val = obj[key]
  }
  /** 获取childOb,根据此判断是否进行深层监听 */
  let childOb = !shallow && observe(val, false, mock)
  /** 定义obj对应key的描述符,默认可枚举,可修改设置 */
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    /** 定义的getter函数 */
    get: function reactiveGetter() {
      /** 通过缓存的getter函数获取其值 */
      const value = getter ? getter.call(obj) : val
      /** 当前设置的观察者对象 */
      if (Dep.target) {
        /** dep收集依赖 */
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (isArray(value)) {
            /** 数组的每一项,进行依赖收集 */
            dependArray(value)
          }
        }
      }
      /** 如果此值为ref值,且并非浅监听,则返回value.value值
       *  否则返回val值
       */
      return isRef(value) && !shallow ? value.value : value
    },
    /** 定义的setter函数 */
    set: function reactiveSetter(newVal) {
      /** 通过原来的getter函数获取值 */
      const value = getter ? getter.call(obj) : val
      /** 判断新旧值是否为同一个值,同一个值不会触发实际set处理 */
      if (!hasChanged(value, newVal)) {
        return
      }
      /**如果具有setter函数,则通过原来的setter函数更新数据 */
      if (setter) {
        setter.call(obj, newVal)
      } else if (getter) {
        /** 如果原来的getter函数存在,则不做任何处理 */
        // #7981: for accessor properties without setter
        return
      /** 如果非浅监听,且旧值为ref,新值非ref,则更新value.value的值 */
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        value.value = newVal
        return
      /** 直接更新val值 */
      } else {
        val = newVal
      }
      /** 获取childOb */
      childOb = !shallow && observe(newVal, false, mock)
      /** 进行通知 */
      dep.notify()
    }
  })
  return dep
}

依赖收集

主要涉及Vue2中的defineReactive、dependArray与Dep构造函数,主要流程如下:
【vue2】响应式相关_第2张图片
其中的dep即是每个属性或每个对象所具有的Dep实例

dependArray函数

主要逻辑:

  • 遍历收集传入数组对象并调用数组项dep.depend函数收集依赖
  • 数组项为数组类型时,递归调用dependArray
    具体源码如下:
function dependArray(value: Array<any>) {
  /** 数组项收集依赖,如果数组项为数组,则递归执行dependArray */
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    if (e && e.__ob__) {
      e.__ob__.dep.depend()
    }
    if (isArray(e)) {
      dependArray(e)
    }
  }
}

Dep构造函数

问题

为什么需要响应式,解决了什么问题

MVVM框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用程序,数据变化,视图更新,要做到这点就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理

有什么好处

以vue为例,通过数据响应式和虚拟DOM和patch算法,开发人员只需要操作数据,关心业务,完全不用接触繁琐的DOM操作,从而大大提升开发效率,降低开发难度

vue的响应式是怎么实现的?优缺点是什么?

vue2

vue2中的数据响应式会根据数据类型不同来做不同处理,对象采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖数组对象原型的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题

  • 缺点
  1. 初始化时的递归遍历会造成性能损失
  2. 新增或删除属性时,需要用户使用Vue.set/delete特殊的api才能生效
  3. 对于es6中新产生的Map、Set这些数据结构不支持等

vue3

vue3利用了ES6的Proxy代理来响应化数据

  • 优点
  1. 编程体验是一致的,不需要使用特殊的api
  2. 初始化性能和内存消耗得到了大幅的改善
  3. 响应化的实现代码抽取为独立的reactivity包,可以更灵活的做第三方扩展

你可能感兴趣的:(Vue笔记,vue.js,前端,前端框架)