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…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
