0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一文看懂Vue3响应式系统原理

马哥Linux运维 来源:马哥Linux运维 2023-12-07 10:55 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

响应式的基本概念

响应式是指当数据发生变化时,系统会自动更新与数据相关的 DOM 结构。

在 Vue2 中,响应式系统的实现基于Object.defineProperty。然而,Object.defineProperty有一些局限,如:无法监听数组的变化、需要遍历对象的每个属性进行监听、性能开销较大。

在 Vue3 中,响应式系统的实现基于 ES6 的Proxy对象。Proxy可以直接监听对象和数组的变化,而无需对每个属性进行监听,从而大大提高性能。同时,Proxy也可以解决Object.defineProperty无法监听数组的问题。

响应式的关键在于vue的依赖收集机制。

简化模型

为了更直观的理解vue依赖收集的模型,我们先来看一个“简单”的功能描述:

已知watcher函数,调用了一些“外部函数”:


function watcher () {
    console.log('watcher start')
    函数1(); 
    函数2();
    console.log('watcher end')
}

能否设计一个依赖收集系统,使这些“外部函数”运行时,watcher也会随之运行?

关键:如何判断函数间的调用关系?

看似有点难,实际一点也不简单,我们需要知道函数间调用关系。我们先看个例子:


function A() { console.log('A') }
function B() { console.log('B') }
function C() { console.log('C') }
...


function watcher () {
    console.log('watcher start!')
    /* *这里调用了上面的某些函数* */
    console.log('watcher end!')
}
/* *这里运行了某些函数* */
watcher();


- watcher start!
- A
- B
- wathcer end! 
- C

运行结果我们可以看出watcher内部一定调用了A、B函数:

为啥?js是单线程的。

C函数一定在watcher外面吗?不一定。例如:

function watcher () { console.log('start') A() B() setTimeout(()=>{ C() }) console.log('end') } watcher();

C函数这种咋办?不管!我们只管肯定没问题的!

我们由此可以确定

函数watcher执行期间,凡是运行过的函数,一定是watcher内部调用过的函数

根据这个原理,我们设计依赖收集系统如下:


// 当前的监听函数
let activeEffect = null
// 副作用函数
function effect (watcher) {
    activeEffect = watcher
    // watcher执行的期间就是依赖收集的阶段
    watcher(true)
    activeEffect= null
}
// isTracking:是否是依赖收集阶段
function A (isTracking = false) {
    if (isTracking) {
        // 依赖收集阶段,effects就是A的监听函数集合
        A.effects = A.effects || new Set()
        A.effects.add(activeEffect)
    } else {
        // 依赖运行阶段
        console.log('A触发了')
        A.effects.forEach(fn => fn(true))
    }
}
function B (isTracking = false) {
    /*** 与A类似 ***/
} 

测试一下效果

2160c52e-94a1-11ee-939d-92fbcf53809c.png

218bd12e-94a1-11ee-939d-92fbcf53809c.png

看起来达到了要求。

将上面代码优化一下,最终如下


let activeEffect = null;
function effect (watcher) {    
    activeEffect = watcher;    
    watcher(true);
    
    activeEffect = null;
}


const bucket = new WeakMap();


function track (target) {
    const effects = bucket.get(target) || new Set();
    activeEffect && effects.add(activeEffect);
    bucket.set(target, effects);
}




function trigger (target) {
    bucket.get(target)?.forEach?.(fn => fn(true));
}
function A (isTracking = false) {
    if (isTracking) {
        
        track(A);
    } else {
        console.log('A触发了')
        
        trigger(A);
    }
}
function B (isTracking = false) {
    
}

这里将之前 A.effects = A.effects || new Set();依赖收集流程提取成track函数,监听函数的触发流程抽离为trigger函数;这样,我们实现了一个简单的依赖收集系统。

Vue依赖收集模型

我们知道Vue3是通过Proxy实现的依赖收集流程,Proxy示例:

21b2e732-94a1-11ee-939d-92fbcf53809c.png

1. Proxy对象get监听,set触发

Vue3中,Proxy代理数据在被读取时“依赖收集”,在被赋值时会“触发依赖”;我们试一下上面完成的依赖收集系统,看下效果:


const data = {
    value: 1,
}
const proxyData = new Proxy(data, {
    get(target, key) {
        
        track(target);
        return target[key];
    },
    set(target, key, value) {
        
        trigger(target);
        target[key] = value;
    }
})

测试一下

测试代码如下:

21b99622-94a1-11ee-939d-92fbcf53809c.png

终端运行结果:

21bed538-94a1-11ee-939d-92fbcf53809c.png

看起来效果不错!但是下面的例子里有问题:

21e2b908-94a1-11ee-939d-92fbcf53809c.png

21e6c7be-94a1-11ee-939d-92fbcf53809c.png

一个无关的属性key的赋值也会触发监听函数!这不是我们想要的。为了精确监听,还需要细化依赖收集系统。

2. “key”级依赖

我们可以将对象的属性作为基本单位进行依赖收集。改造如下:


// 依赖收集函数,这里精确到keyfunction track (target, key) {    const effects = bucket.get(target) || new Map();    const keyMap = effects.get(key) || new Set();    effects.set(key, keyMap);    bucket.set(target, effects);    activeEffect && keyMap.add(activeEffect);}// 依赖触发函数,这里精确到keyfunction trigger (target, key) {    const effects = bucket.get(target);    if (!effects) return;    const keyMap = effects.get(key);    if (!keyMap) return;    keyMap.forEach(effect => effect());}
const data = {    value: 1}const proxyData = new Proxy(data, {    get(target, key) {
        // 具体到key进行收集        track(target, key);        return target[key]    },    set(target, key, value) {
        // 触发到key        trigger(target, key);        target[key] = value    }})

这里试一下效果

21f8e12e-94a1-11ee-939d-92fbcf53809c.png

22082b5c-94a1-11ee-939d-92fbcf53809c.png

这样就实现了精确到属性的监听系统。看到这里,似乎完成的很不错了,但是看到下面的例子:

2211c0d6-94a1-11ee-939d-92fbcf53809c.png

这里value属性由false变为true后,属性data的就已不再参与监听函数内的逻辑了;监听函数不应该再响应data属性,但实际上并没有。因为依赖关系已经固化,data属性只要变化就一定会触发监听,不管是否真的需要:

222b1842-94a1-11ee-939d-92fbcf53809c.png

3. 分支切换

为了优化这一点,应将依赖关系实时更新,将多余的监听去除。为此,vue采取的策略是:

每次监听函数运行前,都要将自己的依赖关系清除;然后在运行期间重建依赖关系。(版权归掘金硬毛巾原作者所有,侵删)

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66863
  • DOM
    DOM
    +关注

    关注

    0

    文章

    18

    浏览量

    9831
  • 监听系统
    +关注

    关注

    0

    文章

    7

    浏览量

    6495

原文标题:Vue3响应式系统原理

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    解析Vue代码层面的优化

    项目首屏优化、Webpack 编译配置优化等问题,所以我们仍然需要去关注 Vue 项目性能方面的优化,使项目具有更高效的性能、更好的用户体验。本文是作者通过实际项目的优化实践进行总结而来,希望读者读完本文,有定的启发思考,从而对自己的项目进行优化起到帮助。本文内容分为以
    发表于 10-27 11:39

    基于TypeScript实现Vue3.0指令组件拖拽

    最近在用vue3重构后台的个功能。个弹窗组件,弹出个表单。然后点击提交。早上运维突然跑过来问我,为啥弹窗挡住了下边的表格的数据,我添加的时候,都没法对照表格来看了。你必须给我解决
    发表于 11-04 06:58

    关于vue如何去水印的解决方法的介绍

    很多人都懂些简单的电脑系统问题的解决方案,但是vue怎么去水印的解决思路却鲜为人知,小编前几天就遇到了vue怎么去水印的问题,于是准备整理
    发表于 03-24 17:33 3645次阅读

    关于React和Vue产生定的认知

    Vue2 相较 Vue3 版本而言牢牢占据着大部分 Vue 开发者的视野,但是因为 Vue 官方已经把 Vue3 作为默认的版本,所以在此同
    的头像 发表于 11-02 13:18 1287次阅读

    Vue入门之Vue定义

    Vue (读音 /vjuː/,类似于 view) 是套用于构建用户界面的渐进JavaScript框架。 Vue 的核心库只关注视图层,也就是只处理页面。
    的头像 发表于 02-06 16:41 1610次阅读
    <b class='flag-5'>Vue</b>入门之<b class='flag-5'>Vue</b>定义

    如何使用springboot+vue搭建个人网站3

    Vue.js(读音 /vjuː/, 类似于 view)是个构建数据驱动的 web 界面的渐进框架。Vue现在这么火,大家都懂。接下来让我们来认识
    的头像 发表于 02-14 16:05 1923次阅读
    如何使用springboot+<b class='flag-5'>vue</b>搭建个人网站<b class='flag-5'>3</b>

    搭建基于Vue3+Vite2+Arco+Typescript+Pinia后台管理系统模板

    今天我们就来快速搭建个基于Vue3+Vite2+Arco+Typescript+Pinia后台管理系统模板。这样可以帮大家快速制作自己的后台模板
    的头像 发表于 03-01 10:09 1471次阅读
    搭建基于<b class='flag-5'>Vue3</b>+Vite2+Arco+Typescript+Pinia后台管理<b class='flag-5'>系统</b>模板

    简单介绍Vue中的响应原理

    自从 Vue 发布以来,就受到了广大开发人员的青睐,提到 Vue,我们首先想到的就是 Vue响应
    的头像 发表于 03-13 10:11 1212次阅读

    使用Vue3时遇到的些问题

    Vue3 目前已经趋于稳定,不少代码库都已经开始使用它,很多项目未来也必然要迁移至 Vue3。本文记录我在使用 Vue3 时遇到的些问题,希望能为其他开发者提供帮助。
    的头像 发表于 09-13 10:16 1589次阅读
    使用<b class='flag-5'>Vue3</b>时遇到的<b class='flag-5'>一</b>些问题

    看懂FPGA芯片投资框架.zip

    看懂FPGA芯片投资框架
    发表于 01-13 09:06 4次下载

    看懂PCB天线、FPC天线的特性.zip

    看懂PCB天线、FPC天线的特性
    发表于 03-01 15:37 34次下载

    看懂BLE Mesh

    看懂BLE Mesh
    的头像 发表于 12-06 16:24 2432次阅读
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>看懂</b>BLE Mesh

    Vue3设计思想及响应源码剖析

    作者:京东物流 乔盼盼 Vue3结构分析 1、Vue2与Vue3的对比 •对TypeScript支持不友好(所有属性都放在了this对象上,难以推倒组件的数据类型) •大量的API
    的头像 发表于 12-20 10:24 741次阅读

    看懂电感、磁珠和零欧电阻的区别

    电子发烧友网站提供《看懂电感、磁珠和零欧电阻的区别.docx》资料免费下载
    发表于 01-02 14:48 3次下载

    Vue3组合式API最佳实践:从Options API到Composition API

    简介 在Vue.js中,Options API直是主流的开发方式。不过随着Vue3的推出,Composition API作为种全新的开发方式引起了广泛关注。本文将从Options
    的头像 发表于 10-20 13:36 184次阅读