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 = { price: 5, quantity: 2 }
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 = { price: 5, quantity: 2 }
6let total = 0
7let storage = { amount: 50, sale: 2 }
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
19< 8
20> proxiedProduct.price
21get
22< 8

  • 步骤二: 进一步封装

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({ price: 5, quantity: {a: 2} })
44let total = 0
45let effect = () => {
46  total = product.price * product.quantity.a
47}
48
49effect()
50
51// 运行
52> total
53< 10
54> product.price = 8
55< 8
56> total
57< 16

  • 步骤三: 移除非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



vue3响应式模式设计原理的更多相关文章

  1. 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)

    由浅入深,带你用JavaScript实现响应式原理 前言 为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数 ...

  2. 什么是响应式Web设计?怎样进行?

    http://beforweb.com/node/6/page/0/3 开始第一篇.老规矩,先无聊的谈论天气一类的话题.十一长假,天气也终于开始有些秋天的味道,坐在屋里甚至觉得需要热咖啡.话说两年前也 ...

  3. 最佳的 14 个免费的响应式 Web 设计测试工具

    一旦你决定要搭建一个网站就应该已经制定了设计标准.你认为下一步该做什么呢?测试!我使用“测试”这个词来检测你网站对不同屏幕和浏览器尺寸的响应情况.测试在响应式网页设计的过程中是很重要的一步.如果你明白 ...

  4. <HTML5和CSS3响应式WEB设计指南>译者序

    "不是我不明白,这世界变化快."崔健的这首歌使用在互联网领域最合适不过.只短短数年的功夫,互联网的浪潮还没过去,移动互联网的时代已经来临.人们已经习惯将越来越多的时间花在各种移动设 ...

  5. Intention.js – 动态重构 HTML 为响应式模式

    Intention.js 提供一个轻量级的和明确的方式,帮助你动态重组 HTML,成为响应式的方式.操作方法都放在了元素自己里面,所以灵活的布局看起来就似乎不会那么的抽象和凌乱. 您可以轻松地增加布局 ...

  6. 响应式Web设计 – 布局

    写在前面 去年上半年,我开始着手推动项目中响应式设计的落地.以官网优化需求为契机,主动去做了响应式的页面设计,也说服了产品.设计和开发的相关同事一起把它上线落实,但不幸的是,由于各种方面的原因,比如, ...

  7. 分享29个超赞的响应式Web设计

    原文自:http://www.csdn.net/article/2013-01-16/2813678-responsive-design-websites 最近几年,响应式Web设计不断印入人们眼帘, ...

  8. 初探响应式Web设计

    公司书柜有本<响应式Web设计:HTML5和CSS3实战>,大概就认真看了前面几章,后面大部分介绍css3(随便找本手册都可以了要你可用!) <响应式Web设计:HTML5和CSS3 ...

  9. 响应式网站设计(Responsive Web design)

    页面的设计与开发应当根据用户行为以及设备环境(系统平台.屏幕尺寸.屏幕定向等)进行相应的响应和调整.具体的实践方式由多方面组成,包括弹性网格和布局.图片.CSS media query的使用等.无论用 ...

随机推荐

  1. Anaconda 安装 国内镜像问题解决方案

    镜像下载.域名解析.时间同步请点击阿里云开源镜像站 遇到问题:安装2021版本后无法打开Anaconda Navigator 解决方案:使用管理员身份打开Avaconda Prompt,输入conda ...

  2. 使用MariaDB backup恢复主从

    安装 yum install MariaDB-backup 备份命令 工具需要直接操作数据目录,需要在数据库服务器上执行 mariabackup --backup --target-dir=/data ...

  3. 深度优先算法--对DFS的一些小小的总结(一)

    提到DFS,我们首先想到的是对树的DFS,例如下面的例子:求二叉树的深度 int TreeDepth(BinaryTreeNode* root){ if(root==nullptr)return 0; ...

  4. Nginx(一) 反向代理为何叫反向代理?

    与正向代理比起来,反向代理是什么东西反向了? 正向代理 A同学在大众创业.万众创新的大时代背景下开启他的创业之路,目前他遇到的最大的一个问题就是启动资金,于是他决定去找马云爸爸借钱,可想而知,最后碰一 ...

  5. 四种类型的数据节点 Znode?

    1.PERSISTENT-持久节点 除非手动删除,否则节点一直存在于 Zookeeper 上 2.EPHEMERAL-临时节点 临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与 zoo ...

  6. Azure DevOps (八) 通过流水线编译Docker镜像

    上一篇文章我们完成了最简单的传统部署:上传应用到服务器上使用守护进程进行应用的部署. 本篇文章我们开始研究容器化和流水线的协作. 在开始操作之前,我们首先需要准备一下我们的dockerfile,这里我 ...

  7. (stm32f103学习总结)—ADC模数转换实验

    一.STM32F1 ADC介绍 TM32F103 系列一般都有 3 个 ADC,这些 ADC 可以独立使用,也可 以使用双重(提高采样率).STM32F1 的 ADC 是 12 位逐次 逼近型的模拟数 ...

  8. Linux基础学习 | 用户及用户组

    Linux 用户及用户组 目录 一.用户    添加用户实例 二.用户组    添加用户组实例 三.用户及用户组文件 四.各命令参数对照 一.用户 Linux系统是一个多用户多任务的分时操作系统.任何 ...

  9. canvas菜鸟基于小程序实现图案在线定制功能

    前言 最近收到一个这样的需求,要求做一个基于 vue 和 element-ui 的通用后台框架页,具体要求如下: 要求通用性高,需要在后期四十多个子项目中使用,所以大部分地方都做成可配置的. 要求做成 ...

  10. java连接oracle数据库(转)

    在做导游通项目所用到 package org.javawo.test; import java.sql.Connection; import java.sql.DriverManager; /** * ...