Vue3响应式原理初探
vue3响应式原理初探
- 为什么要使用proxy取代defineProperty
- 使用proxy如何完成依赖收集呢?
为什么要使用proxy取代defineProperty
原因1:defineproperty无法检测到原本不存在的属性。打个🌰
new Vue({data(){return {name:'wxs',age:25}}})
在vue2中,底层会通过definproperty来响应式data返回的对象,也就是{name:'wxs',age:25}
,具体的响应式监听如下。
const obj = {name:'wxs',age:25}Object.entries(obj).forEach(([key,value]) => {Object.defineProperty(obj,key,{get(){return value},set(newValue){console.log(`监听到属性${key}改变`)value = newValue}})})obj.name = 11obj.age = 22obj.ak47 = 'ak47'
看看控制台输出
可以看出,由于初始化中并没有初始化ak47,所以在vue2中对未初始化的对象key值的修改是无法监听到的。
再来看看es6新特性proxy的优越性
const obj = {name:'wxs',age:25}const prxoyTarget = new Proxy(obj,{get(target,key){return target.key},set(target,key,value){console.log(`监听到${key}需要改成${value}`)target[key] = value}})prxoyTarget.name = 11prxoyTarget.age = 22prxoyTarget.ak47 = 'ak47'
所以回答这个问题的思路就很清晰了
1、proxy可以完成对未初始化对象key的代理监听,而definproperty不可以,相比较之下,proxy更加优越。
2、由vue2的响应式原理可以看出,vue底层需要对vue实例的返回的每一个key进行get和set操作,无论这个值有没有被用到。所以在vue中定义的data属性越多,那么初始化开销就会越大。而proxy是一个惰性的操作,它只会在用到这个key的时候才会执行get,改值的时候才会执行set。所以在vue3中实现响应式的性能实际上要比vue2实现响应式性能要好。
使用proxy如何完成依赖收集呢?
首先看到vue3官网中给出的初始化一个vue实例的基础用法。
因为我们需要响应式一个对象,所以我们使用reactive api。
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script><div id="app">{{ message }}</div><script>const { createApp, reactive, toRefs } = VuecreateApp({setup() {const data = reactive({name: 'wxs'})return data}}).mount('#app')
</script>
由这个简单的实例可得知,vue对象内部首先暴露了一个createApp函数用于新建vue实例,那么大致写出Vue内部的执行逻辑,注意看以下代码,重点部分都有注释讲解
const Vue = {// 从官网示例可得,从vue中解构出了createApp函数,那么其内部肯定有createApp的定义createApp(options){// createApp实现细节后文补充return {// 在mount中执行初次挂载mount(domSelector){// 在vue3的源码中也会在mount中执行挂载任务,但是由于此文主要讲解的是vue3的响应式原理,那么AST解析和diff比较就略过,直接显示setup的返回值即可(也就是模拟vue的模版内容为 <div>{{ name }}</div>)。 document.querySelector(domSelector).innerHTML = '页面渲染'}}},// 响应式代理,在前一小节有介绍,此节不做赘述。reactive(target){return new Proxy(target,{get(target,key){// 代理对象的get方法return target[key]},set(target,key,newValue){// 代理对象的set方法target[key] = newValue}})}}
将上述代码执行之后,浏览器应该会出现如下页面
但是很明显,我们想要将响应式数据呈现在页面上。比如代码中定义到的name。那么我们可以将createApp做如下改写。具体思路是拿出setUp的返回值。然后将返回值渲染到页面。
createApp(options) {let dataSource = nullif (options.setup) {dataSource = options.setup()}return {// 在mount中执行初次挂载mount(domSelector) {document.querySelector(domSelector).innerHTML = dataSource.name}}},
这样就将响应式数据和template模版之间构建了联系。(但是千万不要被这个简化的代码所迷惑,实际上vue底层会执行diff算法来比较最小化更新之后在渲染页面。因为此文是介绍vue3的响应式原理,所以此过程略过)
完成createApp的补充之后,实际上我们已经完成了vue的初次渲染工作。那么剩下的就是需要完成vue组件的更新工作了(其实也就是说,在响应式数据更新的时候,重新执行一下mount里的代码完成页面刷新)
理清思路之后,稍微重构一下mount函数
mount(domSelector) {const update = () => document.querySelector(domSelector).innerHTML = dataSource.nameeffect(update)}
实际上,vue3是通过effect函数来将更新函数和响应式数据来建立链接。所谓的建立链接,也就是通过effect执行的函数中如果包含了响应式对象,如果响应式对象发生改变,函数就会重新执行。
来看看effect函数的实现细节。
let currentCallback = nullconst effect = (callback) => {currentCallback = callbackcallback()currentCallback = null}
有同学可能会问了,先存一下callback,然后执行一下更新函数,再置空,那么这个赋值不就是脱了裤子放屁-----多此一举吗?那么看看这两行中间夹缝生存的的这一行神奇的代码callback()
。执行一下传入的callback,那么也就是update函数。如果执行update函数必然会触发dataSource.name的get方法。那么我们就可以在这里完成依赖收集。将代理对象和key和更新函数之间建立如下的链接关系。
// 在get里面触发依赖收集get(target, key) {track(target,key)// 代理对象的get方法return target[key]},
// 从上图出发,定义的track函数const track = (target,key) => {let depMap = reactiveMap.get(target)if(!depMap){depMap = new Map()reactiveMap.set(target,depMap)}let watcherSet = depMap.get(key)if(!watcherSet){watcherMap = new Set();watcherSet.add(currentCallback)depMap.set(key,watcherSet)}}
// 依赖收集完成,定义触发函数 触发函数其实就是track函数的逆运算,把track存起来的东西全部取出来const trigger = (target, key) => {reactiveMap.get(target).get(key).forEach(cb => cb())}
最后将trigger写到set里面。
set(target, key, newValue) {// 代理对象的set方法target[key] = newValuetrigger(target, key)}
大功告成,可以直接改变对象值看看效果。
createApp({setup() {const data = reactive({name: 'wxs'})setTimeout(() => {data.name = '456'}, 1000)return data}}).mount('#app')
老规矩 贴上全部代码
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><!-- import CSS --><link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"><style>.pagination-container {position: sticky;bottom: 0;z-index: 100;}#box {width: 100%;height: 50%;background: black;}</style>
</head><body><div id="app">{{name}}</div>
</body><!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> --><script>let currentCallback = nullconst effect = (callback) => {currentCallback = callbackcallback()currentCallback = null}let reactiveMap = new Map();const track = (target, key) => {let depMap = reactiveMap.get(target)if (!depMap) {depMap = new Map()reactiveMap.set(target, depMap)}let watcherSet = depMap.get(key)if (!watcherSet) {watcherSet = [];watcherSet.push(currentCallback)depMap.set(key, watcherSet)}}const trigger = (target, key) => {reactiveMap.get(target).get(key).forEach(cb => cb())}const Vue = {createApp(options) {let dataSource = nullif (options.setup) {dataSource = options.setup()}return {// 在mount中执行初次挂载mount(domSelector) {const update = () => document.querySelector(domSelector).innerHTML = dataSource.nameeffect(update)}}},reactive(target) {return new Proxy(target, {get(target, key) {track(target, key)// 代理对象的get方法return target[key]},set(target, key, newValue) {// 代理对象的set方法target[key] = newValuetrigger(target, key)}})}}const { createApp, reactive } = VuecreateApp({setup() {const data = reactive({name: 'wxs'})setTimeout(() => {data.name = '456'}, 1000)return data}}).mount('#app')</script>
</html>
各位看官稍安勿躁,全部代码算上css才100行,当然,vue内部实现肯定比我这个要复杂,比如它内部存更新函数是用的set等等,但是对于响应式原理而言,我们只需要拿出最精华的部分即可。第一遍看不懂没关系,我看视频也是看了七八遍才看懂,各位coder们一定要有耐心,拿下vue3响应式!
相关文章:

Vue3响应式原理初探
vue3响应式原理初探 为什么要使用proxy取代defineProperty使用proxy如何完成依赖收集呢? 为什么要使用proxy取代defineProperty 原因1:defineproperty无法检测到原本不存在的属性。打个🌰 new Vue({data(){return {name:wxs,age:25}}})在vue…...

firewalld常用的基础配置
firewalld防火墙是centos7系统默认的防火墙管理工具,取代了之前的iptables防火墙,也是工作在网络层,属于包过滤防火墙。 支持IPv4、IPv6防火墙设置以及以太网桥支持服务或应用程序直接添加防火墙规则接口拥有两种配置模式:临时模…...

功率放大器如何驱动超声波换能器
驱动超声波换能器的功率放大器在超声波应用中起着至关重要的作用。它能够提供足够的功率和精确的信号控制,使换能器能够有效地将电能转换为超声波能量。下面安泰电子将介绍功率放大器如何驱动超声波换能器的原理和关键要点。 首先,让我们了解一下超声波换…...

LiveGBS流媒体平台GB/T28181常见问题-安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口
LiveGBS流媒体平台GB/T28181常见问题-安全控制HTTP接口鉴权勾选流地址鉴权后401 Unauthorized如何播放调用接口? 1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.2.1、携带 Co…...

红帽认证笔记2
文章目录 1.配置系统以使用默认存储库1.调试selinux2.创建用户账户3.配置cron4. 创建写作目录5. 配置NTP6.配置autofs配置文件权限容器解法1.修改journal配置文件2.重启服务3.拷贝文件到指定目录4.修改拥有人所属组5.修改umask6.切换elovodo用户7.登录容器仓库8.拉取镜像9.运行…...
程序开发中表示密码时使用 password 还是 passcode?
password 和 passcode 是两个经常在计算机和网络安全中使用的术语,两者都是用于身份验证的机制,但它们之间还是存在一些区别的。 password password 通常是指用户自己设置的一串字符,用于保护自己的账户安全。密码通常是静态的,…...
html5 文字自动省略,html中把多余文字转化为省略号的实现方法方法
单行文本: .box{width: 200px;background-color: aqua;text-overflow: ellipsis;overflow: hidden;white-space: nowrap; }多行文本 1.利用-webkit-line-clamp属性 .box{width: 200px;overflow : hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-l…...

6.SNMP报错-Error opening specified endpoint “udp6:[::1]:161“处理
启动SNMP服务 /etc/init.d/snmpd start 出现以下报错信息 [....] Starting snmpd (via systemctl): snmpd.serviceJob for snmpd.service failed because the control process exited with error code. See "systemctl status snmpd.service" and "journalctl…...

集合的进阶
不可变集合 创建不可变的集合 在创建了之后集合的长度内容都不可以变化 静态集合的创建在list ,set ,map接口当中都可以获取不可变集合 方法名称说明static list of(E …elements)创建一个具有指定元素集合list集合对象staticlist of(E…elements)创…...

【LeetCode刷题(数据结构与算法)】:数据结构中的常用排序实现数组的升序排列
现在我先将各大排序的动图和思路以及代码呈现给大家 插入排序 直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个…...

【HTML+CSS】零碎知识点
公告滚动条 <!DOCTYPE html> <html><head><title>动态粘性导航栏</title><style>.container {background: #00aeec;overflow: hidden;padding: 20px 0;}.title {float: left;font-size: 20px;font-weight: normal;margin: 0;margin-left:…...

嵌入式开发学习之STM32F407串口(USART)收发数据(三)
嵌入式开发学习之STM32F407串口(USART)收发数据(三) 开发涉及工具一、选定所使用的串口二、配置串口1.配置串口的I/O2.配置串口参数属性3.配置串口中断4.串口中断在哪里处理5.串口如何发送字符串 三、封装串口配置库文件1.创建头文…...

python:talib.BBANDS 画股价-布林线图
python 安装使用 TA_lib 安装主要在 http://www.lfd.uci.edu/~gohlke/pythonlibs/ 这个网站找到 TA_Lib-0.4.24-cp310-cp310-win_amd64.whl pip install /pypi/TA_Lib-0.4.24-cp310-cp310-win_amd64.whl 编写 talib_boll.py 如下 # -*- coding: utf-8 -*- import os impor…...
ESP32网络开发实例-自定义主机名称
自定义主机名称 文章目录 自定义主机名称1、软件准备2、硬件准备3、代码实现ESP32 的默认主机名是 expressif。 但是,如果正在使用多个 ESP32 设备,并且在某些时候希望在软接入点模式下使用它们时通过名称来区分设备。 例如,在基于物联网的项目中有多个节点,例如温度、湿度…...

【ELK 使用指南 3】Zookeeper、Kafka集群与Filebeat+Kafka+ELK架构(附部署实例)
EFLKK 一、Zookeeper1.1 简介1.2 zookeeper的作用1.3 Zookeeper的特点1.5 Zookeeper的数据结构1.6 Zookeeper的应用场景1.7 Zookeeper的选举机制(重要)1.7.1 第一次启动时1.7.2 非第一次启动时 二、Zookeeper集群部署2.1 安装前准备2.2 安装 ZookeeperSt…...

手写redux的connect方法, 使用了subscribe获取最新数据
一. 公共方法文件 1. connect文件 import React, { useState } from "react"; import MyContext from "./MyContext"; import _ from "lodash";// 模拟react-redux的 connect高阶函数 const connect (mapStateToProps, mapDispatchToProps) &…...

数据结构--B树
目录 回顾二叉查找树 如何保证查找效率 B树的定义 提炼 B树的插入和删除 概括B树的插入方法如下 B树的删除 导致删除时,结点不满足关键字的个数范围时(需要借) 如果兄弟不够借,需要合体 回顾B树的删除 B树 B树的查找 …...

【音视频|ALSA】基于alsa-lib开发ALSA应用层程序--附带源码
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…...

嵌入式养成计划-43----QT QMainWindow中常用类的使用--ui界面文件--资源文件的添加--信号与槽
一百零九、QMainWindow中常用类的使用 109.1 菜单栏 QMenuBar 菜单栏 QMenuBar 最多只能有一个 109.2 工具栏 QToolBar 工具栏 QToolBar 可以有多个 109.3 状态栏QStatusBar 状态栏 QStatusBar 最多只能有一个 109.4 浮动窗口QDockWidget 浮动窗口 可以有多个 109.5 代…...
【Yarn】清除Yarn的缓存,更新Yarn本身、更新项目的依赖项
要清除Yarn的缓存,可以运行以下命令: yarn cache clean这将清除Yarn的缓存目录。 要更新Yarn本身,可以运行以下命令: yarn self-update这将下载并安装最新版本的Yarn。 如果要更新项目的依赖项,可以运行以下命令&a…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...