之前说过,现在在用vue替换原来的jq老项目,采用的是分批次的重构,现在重构告一段落,准备进行一个阶段性的总结,其中让自己比较满意的点就是对Object.defineproperty和proxy的使用,提升了团队的开发体验,以及观察者模式的深入理解。废话不多说,开始正题!
项目背景:
原来的老项目全部使用jq写的,不可能一次性全部用vue重构,其中必然会出现vue和jq一起操作一份数据的情况。
众所周知,vue是响应式框架,核心理念就是数据驱动视图,不需要操作DOM,只需要操作数据即可使视图更新,
而jq则是充斥着大量的DOM及数据操作
项目需求
将jq的一部分操作替换为vue,那就需要将数据改为响应式
Object.defineproperty和proxy
Object.defineproperty
使用场景:通过点击按钮来控制组件的显示,隐藏以及提交数据的逻辑,比如在一个页面中,有三个选项卡,用户点击不同的选项卡,会显示不同的表单组件及提交逻辑
解决方法:在window上挂载一个字段,在setter中增加一些判断,通过不同的newValue来控制以上的逻辑,调用loading动画等操作
Object.defineProperty(window,"loadingControl",{
set(value){
//根据value的值进行判断
}
})
proxy
使用场景:在地图上增删坐标点的同时,要使vue写的列表组件一起联动,在列表增删数据的时候,地图也随之刷新并呈现给用户
解决方法:表面上是地图和列表的显示息息相关,实际上就是因为它俩操作的是同一份数据,与此同时,列表数据一般都是数组,综上所述,那就不得不提到proxy,proxy最大的优点就是可以监听整个对象(当然,它只能监听第一层,如果这个对象的值是一个对象,那么需要用proxy再监听一层,通常做法是递归遍历,如果值为对象,那么再次用proxy监听即可),那么我们在拿到数据的时候就对数据进行处理,并且将更新地图和列表的方法都放进去,这样的话,只要通过接口或是用户操作修改了这个数据,那么地图和列表就会被通知并作出相应的更新
流程图
伪代码实现
let timer=null
function deepProxy(obj, cb=()=>{console.log("操作视图")}) {
if (typeof obj === 'object') {
for (let key in obj) {
if (typeof obj[key] === 'object') {
obj[key] = deepProxy(obj[key], cb);
}
}
}
return new Proxy(obj, {
set: function (target, key, value, receiver) {
if(timer!==null){
clearTimeout(timer)
}
timer=setTimeout(() => {
cb()
}, 0);
console.log("key===="+key+"===value==="+value)
return Reflect.set(target, key, value, receiver);
},
});
}
总结
- 关于Object.defineproperty和proxy
从上面的描述中,我们可以明显的看出,Object.defineproperty在监听对象的某个属性的时候更方便,proxy可以直接监听对象而非属性,在多个组件共享一份数据的时候表现的更出色,尤其是在数据为数组的时候 - 关于设计模式
通过上面的描述,我们很明显看出,这两个api都可以在项目中轻易的实现一个观察者模式(定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知),比如在proxy用例中,地图和列表所用的是同一份数据,当数据改变时,立即通知到地图和列表并作出相应的操作 - 关于项目
我们通过Object.defineproperty和proxy把对数据修改和DOM操作部分解耦,让项目的结构看起来更加的合理,在后续维护的时候也可以更好的修改
对于vue源码的意外收获
在项目过程中,经常会遇到需要在vue外面调用vue里面的方法,当时想的比较简单,就是直接调用vm['内部的方法']
let vm=new Vue({
el: "#root",
data(){
return {
name:"test"
}
},
methods: {
getName(){
console.log(this==vm)
console.log(this.name)
}
}
});
console.log(vm.getName())
后面我觉得这种写法实在是别扭,看看有没有别的方法,于是我就去查看在new vue之后,到底对里面的methods做了哪些操作
当我们new Vue之后
第一步 初始化
在new Vue之后,代码走到了初始化这里,这里有生命周期,事件注册等
第二步 找到初始化methods方法
第三步 进入initMethods
然后就可以发现,原来在初始化的时候,vue对每个methods方法都进行了bind,使其this指向vm
简化操作如下
var vm={a:2, option:{getName(){console.log(this.a)}}}
vm.getName=vm.option.getName.bind(vm)
var z=vm.getName
z() //2
第四步 查看bind,vue为了兼容还是考虑的很周全的
在第二步的时候,我们发现此操作是在created之前,那么我们只需要在created之后将其赋值给外界即可
new Vue({
el: "#root",
data(){
return {
name:"test"
}
},
methods: {
getName(){
console.log(this==vm)
console.log(this.name)
}
},
created(){
getName=this.getName
}
});
getName()