初识 VUE 响应式原理_对象_副作用_函数

来源 | OSCHINA 社区

作者 | 京东云开发者-京东零售 吴静

自从 Vue 发布以来,就受到了广大开发人员的青睐,提到 Vue,我们首先想到的就是 Vue 的响应式系统,那响应式系统到底是怎么回事呢?接下来我就给大家简单介绍一下 Vue 中的响应式原理。

vue2 的响应式原理

尽管 Vue2 将于 2023 年 12 月 31 日停止维护,但是我们依然有很多项目是基于 Vue2.X 进行开发的,那么我们先简单看一看 Vue2.X 是基于什么实现的吧~

Object.defineProperty

Vue2 的响应式原理是基于对象的 defineProperty 方法进行开发的,那么这个方法有什么作用呢?MDN 是这样介绍的:

object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

在 vue 中,当把一个普通的 Java 对象传入 Vue 实例作为 data 选项,Vue 会遍历此对象的所有属性,并使用 object.defineProperty 将这些属性转为 getter/setter,

getter/setter 可以追踪依赖,在属性被访问的时候通知视图变更。

Object.defineProperty(obj, 'targetObj', {

get{

// 完成依赖收集

set{

// 发生变更,同时通知相关依赖

}) vue3 的响应式原理

vue2.0 很好的实现了数据的双向绑定,但是也遗留了一个很重要的问题:由于 Vue 会在初始化实例时将 property 转化为 getter/setter,所以,property 必须在 data 对象上先存在才能让 Vue 将其转换为响应式数据。那么对于新增加的对象、或者某些需要特殊操作的数组想要转换为响应式数据就需要使用 Vue.set 等方法。

展开全文

Vue3 就很好的解决了这个问题。那么,Vue3 是如何解决的呢?让我们就一起看看吧~

Proxy

提到 Vue3 的数据拦截,我们首先要了解什么是 proxy?

Proxy 可以理解成,在目标对象之前架设一层 “拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来 “代理” 某些操作,可以译为 “代理器”。

Proxy 可以理解成,在目标对象之前架设一层 “拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来 “代理” 某些操作,可以译为 “代理器”。

原来,Vue3 用了 Proxy 代理代替了 Object.defineProperty 方法。同样的,在 proxy 中也有 get/set 方法,举个例子~

varobj = newProxy({}, {

get: function( target, name) {

returnname;

set: function( target, key, val) {

target[key] = val

returntarget;

我们通过给每一个目标对象都建立一个对应的 Proxy 对象对其代理就可以弥补 Object.defineProperty 对于新增对象无法监听的缺陷。

简单设计一个 Vue3 的响应系统

实现一个简单的响应系统的思路:

・读取(get)时,将副作用函数入栈;

・设置(set)时,将副作用函数出栈,执行副作用函数。

// 存储副作用函数的栈

constbucket = newSet

// 存储被注册的副作用函数

letactiveEffect

// 注册副作用函数

functioneffect( fn) {

// 存储副作用函数

activeEffect = fn

fn

// 副作用函数fn

effect (

=> {

document.body.innerText = obj.text

执行匿名函数 fn 方法时,会触发响应式数据 obj.text 的读取操作,进而触发代理对象 Proxy 的 get 拦截函数:

constProxy= newProxy(data, {

get(target, key) {

if(activeEffect) {

bucket.add(activeEffect)

returntarget[key]

set(target, key, newVal) {

target[key] = newVal

bucket.forEach( fn=> fn)

returntrue

到此,我们会发现,有一个疑问,我们怎样能保证修改一个属性之后触发的副作用函数是我预期想要触发的副作用函数呢?为了解决这个问题,我们还需要建立副作用函数与目标对象的联系:

我们仅需要用 WeakMap 代替 Set 数据结构:

constbucket = newWeakMap

修改 Proxy 对象:

constProxy= newProxy(data, {

get(target, key) {

if(!activeEffect) returntarget[key]

// 先从栈中取出depsMap,depsMap中保存目标对象和其相关副作用函数的一对多的关系

letdepsMap = bucket.get(target)

if(!depsMap) {

bucket.set(target, (depsMap = newMap)

// 再根据key从depsMap中取得deps,deps保存所有与key相关联的副作用函数

letdeps = depsMap.get(key)

if(!deps) {

depsMap.set(key, (deps = newSet)

deps.add(activeEffect)

returntarget[key]

set(target, key, newVal) {

target[key] = newVal

constdepsMap = bucket.get(target)

if(!depsMap) return

consteffects = depsMap.get(key)

effects && effects.forEach( fn=> fn)

这样,我们就实现了一个简易的响应系统。那么为什么要用 weakMap 而不是使用 Map 呢?就交给大家一起思考啦~

参考文献

《Vue.js 设计与实现_霍春阳》

《ECMA 6 入门》- 阮一峰

有奖问答 | 迁移上云或跨云迁移如何做到安全、高效、低成本?

OpenAI正式推出ChatGPT和Whisper的开发者API 办事不力、沟通无果,cURL作者公开指责微软

这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦

特别声明

本文仅代表作者观点,不代表本站立场,本站仅提供信息存储服务。

分享:

扫一扫在手机阅读、分享本文