关于响应式设计:初识VUE响应式原理

6次阅读

共计 2590 个字符,预计需要花费 7 分钟才能阅读完成。

作者:京东批发 吴静

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

vue2 的响应式原理

只管 Vue2 将于 2023 年 12 月 31 日进行保护,然而咱们仍然有很多我的项目是基于 Vue2.X 进行开发的,那么咱们先简略看一看 Vue2.X 是基于什么实现的吧~

Object.defineProperty

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

object.defineProperty() 办法会间接在一个对象上定义一个新属性,或者批改一个对象的现有属性,并返回此对象。

也就是说,咱们能够通过对象的这个办法准确的增加或者批改对象的属性。每个对象都具备 get/set 属性,当拜访 get 属性时,会调用 getter 办法,当对象的属性值被批改时,会调用 setter 办法,正式基于 getter 和 setter 办法,Vue 才能够利用 Object.defineProperty 来实现响应式零碎。

Object.defineProperty 在 Vue 中的应用

在 vue 中,当把一个一般的 JavaScript 对象传入 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 这个词的原意是代理,用在这里示意由它来“代理”某些操作,能够译为“代理器”。

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

var obj = new Proxy({}, {get: function (target, name) {return name;},
  set: function (target, key, val) {target[key] = val
    return target;
  }
});

咱们通过给每一个指标对象都建设一个对应的 Proxy 对象对其代理就能够补救 Object.defineProperty 对于新增对象无奈监听的缺点。

简略设计一个 Vue3 的响应零碎

实现一个简略的响应零碎的思路:

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

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

// 存储副作用函数的栈
const bucket = new Set()

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

// 注册副作用函数
function effect (fn) {
    // 存储副作用函数
    activeEffect = fn
    fn()}

// 副作用函数 fn
effect (() => {document.body.innerText = obj.text}
)

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

const Proxy = new Proxy(data, {get (target, key) {if (activeEffect) {bucket.add(activeEffect)
        }
        return target[key]
    },
    set (target, key, newVal) {target[key] = newVal
        bucket.forEach(fn => fn())
        return true
    }
})

到此,咱们会发现,有一个疑难,咱们怎么能保障批改一个属性之后触发的副作用函数是我预期想要触发的副作用函数呢?为了解决这个问题,咱们还须要建设副作用函数与指标对象的分割:

咱们仅须要用 WeakMap 代替 Set 数据结构:

const bucket = new WeakMap()

批改 Proxy 对象:

const Proxy = new Proxy(data, {get (target, key) {if (!activeEffect) return target[key]
        // 先从栈中取出 depsMap,depsMap 中保留指标对象和其相干副作用函数的一对多的关系        
        let depsMap = bucket.get(target)
        if (!depsMap) {bucket.set(target, (depsMap = new Map())
        }
        // 再依据 key 从 depsMap 中获得 deps,deps 保留所有与 key 相关联的副作用函数
        let deps = depsMap.get(key)
        if (!deps) {depsMap.set(key, (deps = new Set())
        }
        deps.add(activeEffect)
        
        return target[key] 
    }, 
    set (target, key, newVal) {target[key] = newVal 
        const depsMap = bucket.get(target)
        if (!depsMap) return
        const effects = depsMap.get(key)
        effects && effects.forEach(fn => fn())  
    } 
})

这样,咱们就实现了一个繁难的响应零碎。那么为什么要用 weakMap 而不是应用 Map 呢?就交给大家一起思考啦~

参考文献

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

《ECMAScript 6 入门》- 阮一峰

正文完
 0