响应式原理

源码目录:https://github.com/vuejs/vue-next/tree/master/packages/reactivity

模块

ref:

reactive:

computed:

effect:

operations:提供TrackOpTypes和TriggerOpTypes两个枚举类型,供其他模块使用

剖析

Vue2响应式原理

什么是响应式数据?即A依赖于B数据,当B值发生变化时,通知A。很显然,这里应该使用观察者模式

在vue2中的响应式原理:剖析Vue原理&实现双向绑定MVVM

上面的文章将整个Vue的大致实现都分析了,就响应式这块来说,大概的逻辑是这几个模块Observer,Watcher,Dep。

Observer负责通过defineProperty劫持数据Data,每个被劫持的Data都各自在闭包中维护一个Dep的实例,用于收集依赖着它的Watcher【即观察者】(都实现了一个update方法),被收集的Watcher存入Dep实例的subs数组中。如果Data是对象,则递归搜集。

Dep维护一个公共的Target属性,在触发劫持前,将Target设置为当前Watcher, 然后触发getter将Target(Watcher)收集到subs中。然后再将Target置为null

Data数据变更的时候触发setter,然后从Data维护的Dep实例的subs数组中将Watcher取出来一一执行其update方法。如果变更的值是对象,再劫持之。

用一个最简单的伪代码来说明(省略掉了对值是复杂数据的处理,原理是一样的)

// Vue2响应式原理的基本使用(伪代码)
data = { age: 10 };
new Observer(data) // 数据劫持,黑色箭头
new Wachter(target, 'age', function update() { ... }) // 添加观察者,绿色箭头
data.age = 20 // 被观察者变更,通知观察者, 红色箭头

对应的数据流程如下

就上面的过程,实际上还是有比较大的问题

1.如果Watcher使用的Data是对象类型,那么Data中所有的子属性都需要递归将Watcher收集,这是个资源浪费。

2.数据劫持和依赖收集是强耦合关系

3.对数组的劫持也没有做好,部分操作不是响应式的。

effect.ts

为了解决vue2的问题,依赖收集(即添加观察者/通知观察者)模块单独出来,就是现在的effect

用来生成/处理/追踪reactiveEffect数据,主要是收集数据依赖(观察者),通知收集的依赖(观察者)。

提供了三个函数主要函数:effect/track/trigger。

effect是将传入的函数转化为reactiveEffect格式的函数

track主要功能是将reactiveEffect添加为target[key]的观察者

trigger主要功能是通知target[key]的观察者(将观察者队列函数一一取出来执行)

effect(fn, options):ReactiveEffect

返回一个effect数据:reactiveEffect函数。

执行reactiveEffect即可将数据加入可追踪队列effectStack,并将当前数据设置为activeEffect,并执行fn,fn执行完毕之后恢复activeEffect。

【注意】:必须要在fn函数中执行track才能将reactiveEffect添加为target[key]的观察者,因为track内部只会处理当前的activeEffect,activeEffect没有值则直接返回

track(target, type, key)

将activeEffect添加为target[key]的观察者,如果activeEffect无值,则直接返回。target[key]数据被缓存到targetMap中以{target-> key-> dep}格式存储,优化内存开销。

当前activeEffect(在调用reactiveEffect函数时会将reactiveEffect设置为activeEffect)添加为target[key]的观察者,被添加到target[key]的观察者队列dep中【dep.add(activeEffect)】

当前target[key]的观察者队列dep也会被activeEffect收集【activeEffect.deps.push(dep)】

trigger(target, type, key, newValue, oldValue, oldTarget)

通知target[key]的观察者,即target-> key-> dep中存放的数据,全部一一取出来执行

如果观察者有提供scheduler则执行scheduler函数,否则执行观察者(函数类型)本身

流程是:

首先要将某个函数fn包裹一层为reactiveEffect函数。

当执行reactiveEffect函数时内部会将当前reactiveEffect函数标记为activeEffect,然后执行fn。

fn内部可以调用track,将activeEffect添加为target[key]的观察者,加入队列dep中。当然activeEffect也收集了target[key]的观察者队列dep。

这时,如果修改target[key]的值,然后调用trigger,触发通知target[key]的观察者。trigger中会将对应的观察者队列中的观察者一一取出执行。

import { effect, track, trigger } from 'vue'
let target = {
age: 10
}
const fn = () => {
// 将fn对应的reactiveEffect函数添加到target.age的观察者队列
track(target, 'get', 'age')
// 触发target.age的trigger【通知观察者】, 也会执行该函数
}
// 将fn函数包裹一层为reactiveEffect函数
const myEffect = effect(fn, { lazy: true })
// myEffect每次执行都会将自己设置为activeEffect,并执行fn函数
// fn内部会将对应的reactiveEffect函数添加到target.age的观察者队列
myEffect()
// 设置新值并手动通知target.age的所有观察者
target.age = 20
// 通知target.age的观察者
trigger(target, 'set', 'age')

结合流程说明看这段代码,数据流图

理论上来说,将reactiveEffect添加为target[key]的观察者不一定要在fn中进行。但不这样,用户需要手动为target[key]指定观察者,形如

activeEffect = reactiveEffect
track(target, 'get', 'age') // 内部会将activeEffect添加为target.age的观察者
activeEffect = null

为了简化处理,reactiveEffect内部处理为

// reactiveEffect 内部
try {
effectStack.push(effect)
// 当前effect设置为activeEffect
// 第一次track被调用时,该effect会被加入effectStack
activeEffect = effect
// 执行fn的过程中会对activeEffect做处理
return fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}

在fn执行之前已经将reactiveEffect设置为activeEffect,并且fn执行完毕之后会恢复activeEffect,

这样fn中只需要调用一下track,就将fn对应的reactiveEffect添加为target.age的观察者了,代码如下

// fn
const fn = () => {
...
track(target, 'get', 'age')
return get.age
}

我们将最开始的那个例子改造成一个更加真实的的例子

import { effect, track, trigger } from 'vue'
let target = {
_age: 10,
set age(val) {
this._age = val
trigger(this, 'set', 'age')
}
}
const watcher = () => {
console.log('target.age有更改,则通知我')
}
const fn = () => {
if(!target._isTracked){
target._isTracked = true
track(target, 'get', 'age')
console.log('添加fn的reactiveEffect函数添加到target.age的观察者队列')
}else{
watcher()
}
}
fn._isTracked = false const myEffect = effect(fn, { lazy: true })
myEffect() //打印: '添加fn的reactiveEffect函数添加到target.age的观察者队列'
target.age = 20 //打印: '触发target.age的trigger【通知观察者】, 进入此处'

欢迎造访本人剖析vue3的github仓库

vue3剖析:响应式原理——effect的更多相关文章

  1. Vue3.0响应式原理

    Vue3.0的响应式基于Proxy实现.具体代码如下: 1 let targetMap = new WeakMap() 2 let effectStack = [] //存储副作用 3 4 const ...

  2. 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)

    由浅入深,带你用JavaScript实现响应式原理 前言 为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数 ...

  3. Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理

    1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...

  4. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  5. vue2.0与3.0响应式原理机制

    vue2.0响应式原理 - defineProperty 这个原理老生常谈了,就是拦截对象,给对象的属性增加set 和 get方法,因为核心是defineProperty所以还需要对数组的方法进行拦截 ...

  6. [切图仔救赎]炒冷饭--在线手撸vue2响应式原理

    --图片来源vue2.6正式版本(代号:超时空要塞)发布时,尤雨溪推送配图. 前言 其实这个冷饭我并不想炒,毕竟vue3马上都要出来.我还在这里炒冷饭,那明显就是搞事情. 起因: 作为切图仔搬砖汪,长 ...

  7. Vue响应式原理及总结

    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 ...

  8. Vue2 响应式原理

    我们经常用vue的双向绑定,改变data的某个属性值,vue就马上帮我们自动更新视图,下面我们看看原理. Object的响应式原理: 可以看到,其实核心就是把object的所有属性都加上getter. ...

  9. Vue响应式原理的实现-面试必问

    Vue2的数据响应式原理 1.什么是defineProperty? defineProperty是设置对象属性,利用属性里的set和get实现了响应式双向绑定: 语法:Object.definePro ...

随机推荐

  1. XCTF-WEB-高手进阶区-Web_python_template_injection-笔记

    Web_python_template_injection o(╥﹏╥)o从这里开始题目就变得有点诡谲了 网上搜索相关教程的确是一知半解,大概参考了如下和最后的WP: http://shaobaoba ...

  2. C#LeetCode刷题之#665-非递减数列( Non-decreasing Array)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3732 访问. 给定一个长度为 n 的整数数组,你的任务是判断在最 ...

  3. [luogu4140] 奇数国

    题目 在一片美丽的大陆上有100000个国家,记为1到100000.这里经济发达,有数不尽的账房,并且每个国家有一个银行.某大公司的领袖在这100000个银行开户时都存了3大洋,他惜财如命,因此会不时 ...

  4. 设计模式实战系列之@Builder和建造者模式

    前言 备受争议的Lombok,有的人喜欢它让代码更整洁,有的人不喜欢它,巴拉巴拉一堆原因.在我看来Lombok唯一的缺点可能就是需要安装插件了,但是对于业务开发的项目来说,它的优点远远超过缺点. 我们 ...

  5. 因网络时代与云端应用而生的AGPL-3.0授权条款

    ​ 此篇文章转载自:因應網路時代與雲端應用而生的 AGPL-3.0 授權條款 如你所见,原文为繁体,我将其转为简体并将"网路"替换为"网络",方便阅读.并未修改 ...

  6. day4 列表 字典 元组

      元组  不能修改里面的数据       字典是无序的集合  通过键名来访问元素       列表是有有序的  通过下标来访问    可以进行修改       列表  []   是python中使用 ...

  7. 【HAOI2015】树上染色 - 树形 DP

    题目描述 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 . 将所有点染色后,你会获得黑点两两之 ...

  8. Python实现电脑控制,这个库让你可以控制和监控输入设备

    前言 这个库让你可以控制和监控输入设备.对于每一种输入设备,它包含一个子包来控制和监控该种输入设备:pynput.mouse:包含控制和监控鼠标或者触摸板的类.pynput.keyboard:包含控制 ...

  9. VM 添加硬盘,分区,挂载

    添加硬盘后使用>df -h 命令 VM安装linux系统之后,发现我们的硬盘不够,可通过两种方式添加硬盘 方式一:选择虚拟机,点击右键,设置,点击硬盘,点击添加,输入新添加的硬盘大小,保存与虚拟 ...

  10. 通俗易懂的 Java 位操作运算讲解

    所有数值都是2进制 软件开发者都知道 10 进制.16 进制.8 进制. 比如数字 10 的各位进制形式表现如下. 十进制:10 八进制:012 十六进制:0x0a 二进制:1010 原码 反码 补码 ...