Redian新闻
>
Vue3响应式系统原理

Vue3响应式系统原理

公众号新闻


响应式的基本概念


响应式是指当数据发生变化时,系统会自动更新与数据相关的 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类似 ***/} 


测试一下效果

看起来达到了要求。


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


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示例:


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;    }})


测试一下


测试代码如下:

终端运行结果:


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


一个无关的属性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    }})

这里试一下效果



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



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



3. 分支切换


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

每次监听函数运行前,都要将自己的依赖关系清除;然后在运行期间重建依赖关系。



微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
感恩注意!此事关乎Bellevue未来住房、交通、公园、经济机会,本周六景美小学见!这家华人和老美都超爱的轻奢医美,来Bellevue啦!坐上Bellhop在Bellevue街头秀一秀 ,这款对所有人免费开放的电动车你知道怎么坐吗?Bellevue华人常去Plaza治安恶化,天没黑就开抢,十岁孩子遭扔下车!能挣钱的,开源 SpringBoot 和 Vue 的企业级项目,代码很规范!千刀万剐的微服务,我们到底应该如何应对分布式系统的挑战和风险Bellevue植物园50万盏灯光点亮节日夜,今年新增限时特惠票,只需5美元!Vue.js 组件的复用性:真正可复用还是伪装的可复用?Vue 2生命周期即将结束Bellevue警察开枪打死一名持刀男子!就在有中餐馆、沃尔玛,东区华人常去的那个购物中心西雅图失宠!亚马逊大迁徙首批两千名员工打包搬到Bellevue!Seattle14岁少年游泳馆被枪杀,Bellevue枪杀案告破,两名嫌犯分别16和18岁。青少年枪支犯罪为何愈演愈烈?Bellevue、Redmond等地11家Rite Aid要关门了! 美国第三大药店连锁店申请破产,全美关闭154家门店盗车猖獗!本周六Bellevue警方向居民免费赠送方向盘锁和安全提示,需要的务必早点去!JavaScript 全栈解决方案比较:Angular、React、Vue.js 的对比Vue 3.4 “灌篮高手” 发布大批警察封锁Bellevue购物中心,持刀男子遭当场击毙!Vue 3.4 发布《歌德堡变奏曲1367》长篇小说《谷雨立夏间》13 唯有时间九剑一魂 - 第25回 玉石砥砺 初试锋芒(五)AIGC 在前端 Web 开发中的应用:响应式设计和 Tailwind 配置的完美搭档Bellevue植物园50万盏灯光点亮节日夜,新增限时特惠票,只需$5!SpringCloud 微服务架构:实现分布式系统的无缝协作看看亚马逊在Bellevue新建的办公楼园区-8栋新建筑+招聘25000名新员工西雅图看房日记|98004宇宙中心Bellevue的Enatai豪宅(Beaux Arts Village)无中介费|最高免一个月房租【E3】近BU/BC豪华高级公寓E3 studio 2480+ 1b 3039+ 2b 4341+牛排辣子兔开席,龙虾大蟹镇场,酒酿汤圆收尾...Bellevue这顿大宴,太巴适了!Bellevue新开美学中心,全项目低至6折!错过就没了…取代 Vue 和 React?新框架 Nue JS,能将代码量减少 10 倍!突发!Bellevue著名火锅店 [乐洵] 遭车辆撞击入店!西雅图地区出租价格一览:Bellevue最贵,一居每月$2340;Seatac涨幅最高,同比上涨28.7%营业到凌晨2点!Bellevue又多一个夜宵好去处人类历史上发行量最高的几本书
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。