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…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
