Vue 2 阅读理解(十六)之响应式系统(二)Observer

简介: Vue 2 阅读理解(十六)之响应式系统(二)Observer

响应式系统(二)


在上一节 响应式系统(一) 中,对 Vue 的数据响应式处理做了一点点介绍。整个数据的处理过程,即是通过 Object.defineProperty 方法来处理组件 实例化时传递并被 mergeOptions 方法处理成标准对象形式的参数 options 中的部分数据,例如 props,data 等。


Object.defineProperty 为了避免对原始的项目或者代码造成侵入性改变,内部依然沿用 默认的对象 getter/setter 方法,只是在 getter/setter 过程中增加了 对该属性的依赖收集与更新派发的处理。该过程位于函数 defineReactive() 方法内部,代码位于 src/core/observer/index.ts


Vue 2 的 v3 语法支持所使用的 ref、reactive 等相关方法,核心也是利用的 ObserverdefineReactive,而构造函数 Observer 一样使用 defineReactive 来完成数据响应式处理。


很多文章都说过 Vue 的响应式系统包括三个部分:Observer,Dep,Watcher。这里逐一介绍


1. Observer


作为一个构造函数(也可以称为“类”),官方定义是:附加(挂载)到每个被观察对象的观察者类(构造函数)。当附加结束后,观察者将目标对象的“属性-键”转换为可以实现“收集依赖项”和“调度更新”的 getter/setter。 该类接收一个必传参数 value,当然这个 value 必须是一个对象或者数组;在 2.7 版本之后增加了两个新参数 shadow 和 mock,用来处理 v3 语法的 浅层响应服务端响应式模拟


函数基本定义如下:


export class Observer {
  dep: Dep
  vmCount: number
  constructor(public value: any, public shallow = false, public mock = false) {
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (isArray(value)) {
      if (!mock) {
        if (hasProto) {
          ;(value as any).__proto__ = arrayMethods
        } else {
          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 {
      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)
      }
    }
  }
  observeArray(value: any[]) {
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], false, this.mock)
    }
  }
}


上面的代码中,在 Observer 类上声明了两个属性:dep 和 vmCount,用来存放依赖该对象的 watcher 和将该对象作为根数据的 vm 实例的数量。


new Observer() 时,大致过程如下:


  1. 给该对象增加一个 **非枚举属性 __ob__ **,用来标识该对象已经被响应式处理


  1. 区分对象/数组,分别进行响应式处理


  • 对象


作为对象时,会直接遍历对象的 key,通过 defineReactive 来处理每个属性;如果这个属性值没有初始值的话,还会初始化一个空对象作为默认值来进行处理


  • 数组


因为数组的某些特性虽然与对象类似,但是在使用时通常数组有更多的内置(Array 原型)方法,并且通过默认方法来更新数组的话没有办法触发整个响应式系统;所以 Vue 会重写数组原型链方法,并且将重写后的方法重新更新到数组上(如果可以直接访问 __proto__ 的话,则会直接修改原型链指向)。


然后,则是遍历整个数组,通过 observe 方法来处理每一项数组的元素


2. observe


该方法其实就是对数据进行一定校验,并返回一个 Observer 实例。官方解释为:尝试为一个值创建一个 Observer 观察者实例,如果成功则返回新观察者;如果该值已经有一个观察者实例,则返回现有观察者。


基本定义如下:


export function observe(value: any, shallow?: boolean, ssrMockReactivity?: boolean): Observer | void {
  if (!isObject(value) || isRef(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    (ssrMockReactivity || !isServerRendering()) &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value.__v_skip
  ) {
    ob = new Observer(value, shallow, ssrMockReactivity)
  }
  return ob
}


这个逻辑也很清晰,排除掉 ssr 等其他因素,简单介绍就是:


如果这个值 是个对象且已经包含一个 Observer 实例属性 __ob__,则直接返回原有的观察者 __ob__


如果这个值 是个对象或者数组 且这个值支持扩展属性,则创建一个新的 Observer 观察者实例并返回


当然细心的同学可能发现这里还有一个判断条件 shouldObserve,这个遍历是整个 observer.ts 文件中的一个 boolean 变量(也可以看做闭包保存的一个变量),会在整个过程中保持一个状态,并且可以通过 toggleObserving() 方法来改变,用来开启/关闭某些时刻的数据响应式处理。


3. defineReactive


这个方法其实在上一节中已经有了介绍,内部就是 Object.defineProperty 处理对象属性的过程,这里就不再赘述了。


但是,该方法会返回一个 依赖该属性数据变化的响应订阅器 Dep 实例,并且将该依赖添加到订阅列表 subs 中。


当然,Vue 为了避免收集多余依赖,在后面初始化渲染 watcher (computed、watch 当然也会)的时候,会对没有使用到的 dep 依赖订阅进行清理。


4. set 与 delete


总所周知,Vue 2 没有办法直接监听 对象的未声明属性直接赋值以及数组下标修改和删除,所以为了解决这种情况,提供了 set/del(delete) 两个方法来重新触发数据响应。


两个方法的基本定义如下:


export function set<T>(array: T[], key: number, value: T): T
export function set<T>(object: object, key: string | number, value: T): T
export function set(target: any[] | Record<string, any>, key: any, val: any): any {
    if (isReadonly(target)) {
        return
    }
    const ob = (target as any).__ob__
    if (isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        if (ob && !ob.shallow && ob.mock) {
            observe(val, false, true)
        }
        return val
    }
    if (key in target && !(key in Object.prototype)) {
        target[key] = val
        return val
    }
    if ((target as any)._isVue || (ob && ob.vmCount)) {
        return val
    }
    if (!ob) {
        target[key] = val
        return val
    }
    defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock)
    ob.dep.notify()
    return val
}
export function del<T>(array: T[], key: number): void
export function del(object: object, key: string | number): void
export function del(target: any[] | object, key: any) {
    if (isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1)
        return
    }
    const ob = (target as any).__ob__
    if ((target as any)._isVue || (ob && ob.vmCount)) return
    if (isReadonly(target)) return
    if (!hasOwn(target, key)) return
    delete target[key]
    if (!ob) return
    ob.dep.notify()
}


上面的代码省略了开发环境的错误提示


其实两个方法实现的功能都很单一:更新/删除对应的属性,通过 dep.notify() 触发更新。


当然,这两个方法也有一点基础判断:


set


  1. 只读属性禁止更新,直接返回


  1. 更新数组数据,会校验下标,并调用 splice 更新原数组(这里为什么直接 return?因为 Vue 重写了数组的 splice 方法,在执行 target.splice 就会触发更新),如果不是浅层响应,还会重新对传入的属性值通过 observe 方法进行初始化


  1. 对于 已经定义的非原型链属性,会直接设置新值


  1. 如果更新的值是一个 Vue 组件实例或者根数据对象,也会直接return


  1. 原对象不是一个响应对象,当然也直接返回


  1. 最后的情况就是,原对象是一个响应式对象,并且新的对象属性没有被定义过,则通过 defineReactive 重新处理新属性和属性值,并派发数据更新


del(delete)


  1. 删除数组元素,调用 splice


  1. 如果原对象是一个 Vue 组件实例或者根数据对象,也会直接return


  1. 只读数据或者无法找到的属性,直接 return


  1. 删除对象属性,如果原对象是一个响应式数据,则触发 dep.notify() 派发数据更新,否则直接 return


目录
相关文章
|
2天前
|
JavaScript API
Vue3的响应式原理
Vue 3 中的响应式原理是通过使用 ES6 的 `Proxy 对象`来实现的**。在 Vue 3 中,每个组件都有一个响应式代理对象,当组件中的数据发生变化时,代理对象会立即响应并更新视图。
|
2天前
|
JavaScript 前端开发
深入了解前端框架Vue.js的响应式原理
本文将深入探讨Vue.js前端框架的核心特性之一——响应式原理。通过分析Vue.js中的数据绑定、依赖追踪和虚拟DOM等机制,读者将对Vue.js的响应式系统有更深入的理解,从而能够更好地利用Vue.js构建灵活、高效的前端应用。
|
2天前
|
JavaScript 测试技术 开发者
Vue 3 Vuex:构建更强大的状态管理系统
Vue 3 Vuex:构建更强大的状态管理系统
25 1
|
2天前
|
JavaScript 前端开发 开发者
Vue的响应式原理:深入探索Vue的响应式系统与依赖追踪
【4月更文挑战第24天】Vue的响应式原理通过JavaScript getter/setter实现,当数据变化时自动更新视图。它创建Watcher对象收集依赖,并通过依赖追踪机制精确通知更新。当属性改变,setter触发更新相关Watcher,重新执行操作以反映数据最新状态。Vue的响应式系统结合依赖追踪,有效提高性能,简化复杂应用的开发,但对某些复杂数据结构需额外处理。
|
2天前
|
JavaScript 前端开发 UED
Vue工具和生态系统: Vue.js和服务器端渲染(SSR)有关系吗?请解释。
Vue.js是一个渐进式JavaScript框架,常用于开发单页面应用,但其首屏加载较慢影响用户体验和SEO。为解决此问题,Vue.js支持服务器端渲染(SSR),在服务器预生成HTML,加快首屏速度。Vue.js的SSR可手动实现或借助如Nuxt.js的第三方库简化流程。Nuxt.js是基于Vue.js的服务器端渲染框架,整合核心库并提供额外功能,帮助构建高效的应用,改善用户体验。
21 0
|
2天前
|
JavaScript 搜索推荐 前端开发
Vue工具和生态系统:Vue CLI是什么?它的作用是什么?
【4月更文挑战第17天】Vue CLI是官方的Vue.js开发加速器,它包含交互式项目模板和@vue/cli-service,基于webpack并预设配置。支持个性化配置和插件扩展,拥有大量官方插件,整合最佳前端工具。还提供图形化界面用于项目管理和创建。
11 0
|
2天前
|
Web App开发 JavaScript 开发者
Vue工具和生态系统:什么是Vue DevTools?如何使用它?
Vue Devtools是Vue.js官方的浏览器扩展,用于简化应用调试和优化。可在Chrome和Firefox等浏览器上安装,集成到开发者工具中。安装步骤包括下载源码、npm安装、修改manifest.json并加载编译后的扩展。启用后,开发者能查看Vue组件树,检查属性,并在允许的情况下编辑data,提升开发效率。
18 0
|
2天前
|
JavaScript 前端开发 开发者
浅谈Vue 3的响应式对象: ref和reactive
浅谈Vue 3的响应式对象: ref和reactive
|
2天前
Vue3 响应式数据 reactive使用
Vue3 响应式数据 reactive使用
|
2天前
|
JavaScript
Vue 响应式数据的判断
Vue 响应式数据的判断