欢迎光临散文网 会员登陆 & 注册

Vue3中的响应式原理

2023-07-15 19:06 作者:烧鱼c  | 我要投稿

响应式原理

Vue2中使用的是Object.defineProperty,而Vue3使用的是ES6新增的Proxy

先复习下它俩的相关知识。

Object.defineProperty

Object.defineProperty() 是直接在对象上定义新属性,或修改对象上的现有属性,然后返回该对象。

语法如下:

descript有两种形式:数据描述符和存取描述符。一个描述符只能是这两者中的一个,不能同时是两者,意味着使用了数据描述符就不能同时使用存取描述符。

  • 数据描述符:决定属性的值以及该值是否可写,可选键值有:

    1. value:默认值为 undefined ,决定该属性所对应的值,可以是任何有效值。

    2. writable:默认值为 false ,只有当键值为 true 时,value 才能被赋值运算符改变。

    具体使用:

  • 存取描述符:由 gettersetter 函数所描述的属性,可选键值有:

    1. get:默认值为 undefined ,值应为一个 getter() 函数,当访问该属性时会自动调用该函数,返回的返回值会被用作属性的值。

    2. set:默认值为 undefined ,值应为一个 setter() 函数,当该属性被修改时,会调用此函数。

    具体使用:


输出的结果


由此可见使用 Object.defineProperty() 代理监听不到 对象新增属性 与 数组的新增修改 

差点忘了说,两种描述符都是对象形式。他们还共享以下属性:

  1. configurable(默认false) 是否可配置,为 true 时,属性描述符才能够被改变,同时属性可以从对应对象上被删除。

  2. enumerable(默认false) 是否可枚举,为 true 时,属性才会出现在对象的枚举属性中。

Proxy

Proxy 主要用于改变对象的默认访问行为,实际上是在访问对象之前增加一层拦截 感觉与servlet里的拦截器差不多    在任何对对象的访问行为都会通过这层拦截,如对对象属性的读、写、删除等。在这层拦截中我们可以增加自定义的行为。

基本语法:


有一句话叫打狗还要看主人,这里狗就是target,其主人就是对应的Proxy实例。打狗这个行为,可以看作是对狗的一个交互行为。你要打狗之前,得先看看狗主人的看法。狗主人能够决定你能不能打狗,决定的过程就是handler。

一个简单实例:

我们通过 Proxy 构造器创建了 target 的代理 yushen ,此时对 yushen 属性的访问都会转发到 target 上,通过自定义的 handler 对象进行相应的拦截操作。

代理可以拦截JavaScript引擎内部的底层对象操作,这些操作被拦截后会触发相应特定操作的陷阱函数,如例子中的get与set就是陷阱函数。

总结一下二者区别:

  • Proxy是对整个对象的代理,而Object.defineProperty只能代理某个属性。

  • 对象上新增属性,数组新增修改。Proxy可以监听到,Object.defineProperty不能。

  • 若对象内部属性要全部递归代理,Proxy可以只在调用的时候递归,而Object.definePropery需要一次完成所有递归,性能比Proxy差。

  • Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。

  • Proxy使用上比Object.defineProperty方便多。

Vue3中的响应式:


让我们来看看这个场景:

可以发现两次输出的总价都是10,因为改变 price 并不会让total再跑一次。

我们想要做的,就是让 price 改变时,让与 price 相关的语句都重新再跑一次。该怎么做呢?

由于是运行同样的语句,我们很容易想到将这段语句提取出来封装成一个函数,并将其保存起来以供之后的调用。

我们要把 effect 保存起来,需要用到 track() 函数,并在保存后运行一次 effect() 。在之后的某个时刻,调用 trigger() 来运行所有以及存储了的代码。

那么如何保存 effect 呢?

我们可以使用 Set 来保存,Set 不允许重复值,如果我们尝试添加相同的 effect,它不会变成两个。

运行下试试:

这样我们就完成了 price 这个属性的依赖关系。但一个对象并不止有一个属性,要实现一个对象的响应式,其每个属性都应该有自己的 dep(依赖关系),或者说 effect Set 集合。那么这又该如何存储呢?

我们可以使用一个 map 集合。每个属性的属性名作为 key ,属性对应的 effect Set 集合作为 map value

现在我们对不同的属性有了跟踪依赖关系的方法。但如果我们有多个响应式对象呢?

我们可以使用 WeakMap ,将每一个对象名(target)作为 keydepsMap 作为 value

WeakMap:key必须是对象,对于对象是弱引用。没用的对象就不会影响垃圾回收机制,优化了性能。

整个数据结构如下:


但我们发现,track() trigger() 现在还需要我们自己手动去调用,这太麻烦了。能不能让其自动调用呢?

让我们想想,一般我们在读取属性值的时候需要其去调用track(),在改变属性值后需要调用  trigger() 。对应的行为即 get set 。是不是很熟悉,没错,这时候就要使用我们的 Proxy 了。

这里模仿了Vue3的 reactive 函数,其返回一个 Proxy 实例。

可以发现,这个对象已经变为响应式对象了。改变 price 或是 quantity 的值时,total 值都会实时更新。

但是,我们还应该把 effect 进行集中管理,不然当一个属性对应的 effect 变多时,代码会变得冗余。并且在添加 effect 时,effect 应该自动运行一次。

改造后的代码如下:

这样我们就大概实现了一个基本的响应式功能。让我们去测试一下。

测试 html 代码如下:

运行结果:

    

刚打开页面
1s后,name属性发生变化
2s后,action属性发生变化

最后

感谢你能看到这里 虽然我感觉我写成这b样也没人看   。如果有什么疑惑或是发现了什么错误,欢迎评论区交流噢。

Vue3中的响应式原理的评论 (共 条)

分享到微博请遵守国家法律