Vue3的响应式原理解析
Vue2响应式原理回顾
// 1.对象响应化:遍历每个key,定义getter、setter
// 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto);['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {arrayProto[method] = function () {originalProto[method].apply(this, arguments)notifyUpdate()}})
function observe (obj) {if (typeof obj !== 'object' || obj == null) {return}// 增加数组类型判断,若是数组则覆盖其原型if (Array.isArray(obj)) {Object.setPrototypeOf(obj, arrayProto)} else {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {const key = keys[i]defineReactive(obj, key, obj[key])}}
}
function defineReactive (obj, key, val) {observe(val) // 解决嵌套对象问题Object.defineProperty(obj, key, {get () {return val},set (newVal) {if (newVal !== val) {observe(newVal) // 新值是对象的情况val = newValnotifyUpdate()}}})
}
function notifyUpdate () {console.log('页面更新!')
}
vue2响应式弊端:
响应化过程需要递归遍历,消耗较大
新加或删除属性无法监听
数组响应化需要额外实现
Map、Set、Class等无法响应式
修改语法有限制
Vue3响应式原理剖析
vue3使用ES6的Proxy特性来解决这些问题。
function reactive (obj) {if (typeof obj !== 'object' && obj != null) {return obj}// Proxy相当于在对象外层加拦截// http://es6.ruanyifeng.com/#docs/proxyconst observed = new Proxy(obj, {get (target, key, receiver) {// Reflect用于执行对象默认操作,更规范、更友好// Proxy和Object的方法Reflect都有对应// http://es6.ruanyifeng.com/#docs/reflectconst res = Reflect.get(target, key, receiver)console.log(`获取${key}:${res}`)return res},set (target, key, value, receiver) {const res = Reflect.set(target, key, value, receiver)console.log(`设置${key}:${value}`)return res},deleteProperty (target, key) {const res = Reflect.deleteProperty(target, key)console.log(`删除${key}:${res}`)return res}})return observed
}
//代码测试
const state = reactive({foo: 'foo',bar: { a: 1 }
})
// 1.获取
state.foo // ok
// 2.设置已存在属性
state.foo = 'fooooooo' // ok
// 3.设置不存在属性
state.dong = 'dong' // ok
// 4.删除属性
delete state.dong // ok
嵌套对象响应式
测试:嵌套对象不能响应
// 设置嵌套对象属性
react.bar.a = 10 // no ok
添加对象类型递归
// 提取帮助方法const isObject = val => val !== null && typeof val === 'object'function reactive (obj) {//判断是否对象if (!isObject(obj)) {return obj}const observed = new Proxy(obj, {get (target, key, receiver) {// ...// 如果是对象需要递归return isObject(res) ? reactive(res) : res},//...}
避免重复代理
重复代理,比如
reactive(data) // 已代理过的纯对象
reactive(react) // 代理对象
解决方式:将之前代理结果缓存,get时直接使用
const toProxy = new WeakMap() // 形如obj:observedconst toRaw = new WeakMap() // 形如observed:objfunction reactive (obj) {//...// 查找缓存,避免重复代理if (toProxy.has(obj)) {return toProxy.get(obj)}if (toRaw.has(obj)) {return obj}const observed = new Proxy(...)// 缓存代理结果toProxy.set(obj, observed)toRaw.set(observed, obj)return observed}// 测试效果console.log(reactive(data) === state)console.log(reactive(state) === state)
✨原创不易,还希望各位大佬支持一下\textcolor{blue}{原创不易,还希望各位大佬支持一下}原创不易,还希望各位大佬支持一下
👍 点赞,你的认可是我创作的动力!\textcolor{green}{点赞,你的认可是我创作的动力!}点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!\textcolor{green}{收藏,你的青睐是我努力的方向!}收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!\textcolor{green}{评论,你的意见是我进步的财富!}评论,你的意见是我进步的财富!