vue3的mvvm实现原理是基于Proxy实现的,可比vue2的Object.defineProperty简明扼要很多,也很巧妙,demo不难,一步步实现吧,总共就一个js文件,不超过100行代码,要什么自行车。
1 勾勒出测试框架,大体分为几个部分 :
-
更新视图的方法
-
把数据变为响应式数据的方法
-
测试数据
-
改变测试数据 触发更新视图方法
// 用最简化的模型来模式vue3的mvvm实现原理 // 用这个方法来模式视图更新 function updateView() { console.log('触发视图更新啦') } // 把原目标对象 转变 为响应式的对象 function reactive(target) { // todo 具体如何转变,以及绑定视图更新方法 let proxyed = new Proxy(target, options) return proxyed } // 测试数据 let obj = { name: 'Ace7523', array: ['a', 'b', 'c'] } // 把原数据转变响应式的数据 let reactivedObj = reactive(obj) // 改变数据,期望会触发updateView() 方法 从而更新视图 reactivedObj.name = 'change'
小结 准备工作算是做完了,接下来就是实现reactive方法,然后修改一下obj对象,看是否会触发updateView()方法
2 实现把原数据转变为响应式数据的reactive方法
-
利用 new Proxy(target, options) 对target对象,进行数据改造
-
完善 options
// 用最简化的模型来模式vue3的mvvm实现原理 // 用这个方法来模式视图更新 function updateView() { console.log('触发视图更新啦') } // 把原目标对象 转变 为响应式的对象 const options = { set(target, key, value, reciver) { updateView() return Reflect.set(target, key, value, reciver) }, get(target, key, reciver) { return Reflect.get(target, key, reciver) }, deleteProperty(target, key) { return Reflect.deleteProperty(target, key) } } function reactive(target) { // todo 具体如何转变,以及绑定视图更新方法 let proxyed = new Proxy(target, options) return proxyed } // 测试数据 let obj = { name: 'Ace7523', array: ['a', 'b', 'c'] } // 把原数据转变响应式的数据 let reactivedObj = reactive(obj) // 改变数据,期望会触发updateView() 方法 从而更新视图 reactivedObj.name = 'change'
小结 其实本质上就是不用Object.defineProperty 而改用别的方式来重写对象的get set方法了,也就是Proxy 的方式
3 继续完善reactive方法, 不就是递归嘛,拿去
- 利用递归 对测试数据深层次代理
- 例如 本测试数据中 obj.array也是对象,所以在获取obj.array的时候,判断是否是对象,如果是的话,对这个对象也执行reactive方法,让这对象也成为响应式对象,以此类推,就可以把测试数据全部转变为响应式对象
添加的代码如下isObject()方法判断是否是对象,修改options.get()
function isObject(t) {
return typeof t === 'object' && t !== null
}
// 把原目标对象 转变 为响应式的对象
const options = {
set(target, key, value, reciver) {
updateView()
return Reflect.set(target, key, value, reciver)
},
get(target, key, reciver) {
const res = Reflect.get(target, key, reciver)
if(isObject(target[key])){
return reactive(res)
}
return res
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key)
}
}
小结 这里比较巧妙,建议对比下vue2中的Object.defineProperty的方法对比着看。
4 继续完善reactive()方法 && 添加缓存 -- 完整代码如下
-
防止对数组添加数据时候,重复触发更新视图的方法
-
利用weakMap 记录原数据是否进行过代理,如果代理过,则返回记录值,不再重复代理
// 用最简化的模型来模式vue3的mvvm实现原理 // 用这个方法来模式视图更新 function updateView() { console.log('触发视图更新啦') } function isObject(t) { return typeof t === 'object' && t !== null } // 把原目标对象 转变 为响应式的对象 const options = { set(target, key, value, reciver) { if(target.hasOwnProperty(key)){ updateView() } return Reflect.set(target, key, value, reciver) }, get(target, key, reciver) { const res = Reflect.get(target, key, reciver) if(isObject(target[key])){ return reactive(res) } return res }, deleteProperty(target, key) { return Reflect.deleteProperty(target, key) } } // 用来做缓存 const toProxy = new WeakMap() function reactive(target) { if(!isObject(target)){ return target } // 如果已经代理过了这个对象,则直接返回代理后的结果即可 if(toProxy.get(target)){ return toProxy.get(target) } let proxyed = new Proxy(target, options) toProxy.set(target, proxyed) return proxyed } // 测试数据 let obj = { name: 'Ace7523', array: ['a', 'b', 'c'] } // 把原数据转变响应式的数据 let reactivedObj = reactive(obj) // 改变数据,期望会触发updateView() 方法 从而更新视图 reactivedObj.name = 'change'
小结 完整版代码暂时就这么多,没有考虑过多的边界条件,感兴趣的朋友可以复制出来玩玩看,改改测试数据看看会不会触发更新视图的方法~~~