zoukankan      html  css  js  c++  java
  • vue3响应式模式设计原理

    vue3响应式模式设计原理

    为什么要关系vue3的设计原理?了解vue3构建原理,将有助于开发者更快速上手Vue3;同时可以提高Vue调试技能,可以快速定位错误

    1.vue3对比vue2

    1. vue2的原理是通过 Object.defineProperty() 来劫持各个属性,在数据变动时发布消息给订阅者,触发相应的监听回调。

    defineProperty不具备监听数组的能力,无法检测到对象属性的添加和删除,只有在初始化实例时对data对象转换响应式,后面新增的需要手动转换,深度监听需要一次性递归,性能不好

    1. vue3的原理基于ES6新特性Proxy对象代理

    可以监听原生数组,不需要一次性遍历data的属性,提高性能,vue3将响应式模块分离封装,可以随时对对象进行代理,为组合式api提供了可能,因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11

    可以看出vue3的响应式给开发者带来极大的便利,深层次的对象监听中,再也不怕层级问题或者新增属性问题带来的监听失效

    好消息:2021.05.19微软官方博客发布 公告 表示:2022-06-15微软将正式不再支持IE,现在我们有理由让客户去下载谷歌浏览器了

    2.针对编程语言的编程

    vue的响应式设计,使用了ES6的新特性,如:Proxy,WeakMap,Reflect…,从而对javascript进行了编程

    • 步骤一: 追踪一个变量
     1const price = 5
    2let quantity = 2
    3let total = 0
    4let storage = 0
    5
    6// Set对象是值的集合 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
    7let dep = new Set() 
    8
    9let effect1 = () => { total = price * quantity }
    10let effect2 = () => { storage = 100 - quantity }
    11
    12function track({
    13  dep.add(effect1)
    14  dep.add(effect2)
    15}
    16function trigger({ dep.forEach(effect => effect()) }
    17
    18track()
    19effect1()
    20effect2()
    21
    22// 运行
    23> total
    2410
    25> storage
    2698
    27> quantity = 5
    285
    29> total
    3010
    31> storage
    3298
    33> trigger()
    34undefined
    35> total
    3625
    37> storage
    3895
    • 步骤二: 追踪对象中的属性
     1let product = { price5quantity2 }
    2let total = 0
    3
    4// Map 对象保存键值对。任何值都可以作为一个键或一个值
    5const depsMap = new Map() 
    6
    7let effect = () => {
    8  total = product.price * product.quantity
    9}
    10
    11function track(key{
    12  let dep = depsMap.get(key)
    13  if (!dep) {
    14    depsMap.set(key, (dep = new Set()))
    15  }
    16  dep.add(effect)
    17}
    18function trigger(key{
    19  let dep = depsMap.get(key)
    20  if (dep) {
    21    dep.forEach(effect => effect())
    22  }
    23}
    24
    25track('price')
    26track('quantity')
    27effect()
    28
    29// 运行
    30> total
    3110
    32> product.quantity = 8
    338
    34> total
    3510
    36> trigger('price')
    37undefined
    38> total
    3916
    40> product.quantity = 4
    414
    42> total
    4316
    44> trigger('quantity')
    45undefined
    46> total
    4732
    • 步骤三: 追踪多个对象

    tips: javascript 中垃圾回收的算法
    JavaScript 中的内存管理是自动执行的,程序中不需要使用的数据或者程序无法访问到的数据都是垃圾,JavaScript会自己清理无用数据给其他数据清理空间
    JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
    一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

     1// WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
    2// 原生的 WeakMap 持有的是每个键对象的“弱引用”,不影响js垃圾回收机制。
    3const targetMap = new WeakMap()
    4
    5let product = { price5quantity2 }
    6let total = 0
    7let storage = { amount50sale2 }
    8let remain = 48
    9
    10let effect = () => {
    11  total = product.price * product.quantity
    12  remain = storage.amount - storage.sale
    13}
    14
    15function track(target, key{
    16  let depsMap = targetMap.get(target)
    17  if (!depsMap) {
    18    targetMap.set(target, (depsMap = new Map()))
    19  }
    20  let dep = depsMap.get(key)
    21  if (!dep) {
    22    depsMap.set(key, (dep = new Set()))
    23  }
    24  dep.add(effect)
    25}
    26function trigger(target, key{
    27  let depsMap = targetMap.get(target)
    28  if (!depsMap) return
    29  let dep = depsMap.get(key)
    30  if (dep) {
    31    dep.forEach(effect => effect())
    32  }
    33}
    34
    35track(product, 'price')
    36track(storage, 'sale')
    37effect()
    38
    39// 运行
    40> total
    4110
    42> remain
    4348
    44> product.price = 8
    458
    46> storage.sale = 5
    475
    48> total
    4910
    50> remain
    5148
    52> trigger(product, 'price')
    53undefined
    54> trigger(storage, 'sale')
    55undefined
    56> total
    5716
    58> remain
    5945

    现在我们有了一套可以记录多个对象并根据对象变化追踪更新的机制,只要再加上自动记录以及自动触发更新就可以完善功能了

    3.对象代理Proxy

    • 步骤一: Proxy和Reflect

    tips:
    1.Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。
    Reflect对象有4个意义:
    从Reflect对象上可以拿到语言内部的方法。
    操作对象出现报错时返回false
    让操作对象都变为函数式编程
    保持和proxy对象的方法一一对象
    2.Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

     1let product = { price: 5, quantity: 2 }
    2
    3let proxiedProduct = new Proxy(product, {
    4  get(target, key, receiver) {
    5    console.log('get')
    6    // return target[key]
    7    // 注意我们的get有一个额外的参数receiver,我们将它作为参数发送到Reflect.get中。这确保了当我们的对象从另一个对象继承了值/函数时,可以使用正确的this。这就是为什么我们总是在代理内部使用Reflect。
    8    return Reflect.get(target, key, receiver)
    9  },
    10  set(target, key, value, receiver) {
    11    console.log('set')
    12    return Reflect.set(target, key, value, receiver)
    13  }
    14})
    15
    16// 运行
    17> proxiedProduct.price = 8
    18set
    198
    20> proxiedProduct.price
    21get
    228
    • 步骤二: 进一步封装
     1const targetMap = new WeakMap()
    2
    3function track(target, key{
    4  let depsMap = targetMap.get(target)
    5  if (!depsMap) {
    6    targetMap.set(target, (depsMap = new Map()))
    7  }
    8  let dep = depsMap.get(key)
    9  if (!dep) {
    10    depsMap.set(key, (dep = new Set()))
    11  }
    12  dep.add(effect)
    13}
    14function trigger(target, key{
    15  let depsMap = targetMap.get(target)
    16  if (!depsMap) return
    17  let dep = depsMap.get(key)
    18  if (dep) {
    19    dep.forEach(effect => effect())
    20  }
    21}
    22
    23function reactive(target{
    24  const handler = {
    25    get(target, key, receiver) {
    26      let result = Reflect.get(target, key, receiver)
    27      track(target, key)
    28      return typeof result === 'object' ? reactive(result) : result
    29    },
    30    set(target, key, value, receiver) {
    31      let oldValue = target[key]
    32      let result = Reflect.set(target, key, value, receiver)
    33      if (oldValue !== value) {
    34        trigger(target, key)
    35      }
    36      return result
    37    }
    38  }
    39  return new Proxy(target, handler)
    40}
    41
    42
    43let product = reactive({ price5quantity: {a2} })
    44let total = 0
    45let effect = () => {
    46  total = product.price * product.quantity.a
    47}
    48
    49effect()
    50
    51// 运行
    52> total
    5310
    54> product.price = 8
    558
    56> total
    5716
    • 步骤三: 移除非effect下的track,封装ref
     1let activeEffect = null
    2
    3function track(target, key
    {
    4  // 新增判断activeEffect,不是每次get都要track
    5  if (activeEffect) {
    6    let depsMap = targetMap.get(target)
    7    if (!depsMap) {
    8      targetMap.set(target, (depsMap = new Map()))
    9    }
    10    let dep = depsMap.get(key)
    11    if (!dep) {
    12      depsMap.set(key, (dep = new Set()))
    13    }
    14    dep.add(activeEffect)
    15  }
    16}
    17// effect函数
    18function effect(eff{
    19  activeEffect = eff
    20  activeEffect()
    21  activeEffect 
    null
    22}
    23
    24function ref(raw{
    25  const r = {
    26    get value({
    27      track(r, 'value')
    28      return raw
    29    },
    30    set value(newValue{
    31      if (raw !== newValue) {
    32        raw = newValue
    33        trigger(r, 'value')
    34      }
    35    }
    36  }
    37  return r
    38}
    39
    40let product 
    = reactive({ price: 5, quantity: 2 })
    41let salePrice = ref(0)
    42let total = 0
    43
    44effect(() => {
    45  total = salePrice.value * product.quantity
    46})
    47effect(() => {
    48  salePrice.value = product.price * 0.9
    49})
    • 步骤四: 封装computed
     1function computed(getterOrOptions{
    2  let getter
    3  let setter
    4  if (typeof getterOrOptions === 'function'
    {
    5    getter = getterOrOptions
    6    setter = () => {
    7      console.warn('Write operation failed: computed value is readonly')
    8    }
    9  } else {
    10    getter = getterOrOptions.get
    11    setter = getterOrOptions.set
    12  }
    13  return {
    14    get value({
    15      let result = ref()
    16      effect(() => {result.value = getter()})
    17      return result.value
    18    },
    19    set value(newValue{
    20      setter(newValue)
    21    }
    22  }
    23}
    24
    25let product = reactive({ price: 5, quantity: 2 })
    26let salePrice = computed({
    27  get() {
    28    return product.price * 0.9
    29  },
    30  set(val) {
    31    product.price = val / 0.9
    32  }
    33})
    34let total = computed(() => {
    35  return salePrice.value * product.quantity
    36})

    这样我们就封装好了vue3的三种基础的响应性API (reactive,ref,computed),其他的响应性API都是基于此做的二次封装,了解了原理我们就能更好的使用这些Api


  • 相关阅读:
    把A库aa表结构复制到B库中生成bb表
    C #登录代码
    C# 跟查询有关的代码
    C# 输出用“*”组成的菱形
    POJ 3696 The Luckiest number 数论,GCD
    用一句SQL取出第 m 条到第 n 条记录的方法
    Web Services的基本原理
    常用SQL语句
    什么应用适合Web Services
    应该了解的垃圾收集机制(一)
  • 原文地址:https://www.cnblogs.com/qinyuandong/p/14968786.html
Copyright © 2011-2022 走看看