知识点:
1. Vue2的数据响应式原理(defineProperty)
2. Vue3的数据响应式原理(Proxy)
Vue2的响应式主要是基于defineProperty实现的
defineProperty其实并不是核心的为一个对象做数据的双向绑定,而是去给对象的属性做描述符,只不过属性描述符里的get和set实现了响应式
属性描述符如图所示:
defineProperty简单的使用示例1:
var obj1 = {
a:1,
b:2
}
Object.defineProperty(obj1,'a',{
writable:false
})
console.log(Object.getOwnPropertyDescriptor(obj1,'a'))//方法返回指定对象上一个自有属性对应的属性描述符
以上代码在控制台打印结果如图所示:
简单的使用示例2:
var obj1 = {
a:1,
b:2
}
var _value = obj1.a;
Object.defineProperty(obj1,'a',{
get:function(){
console.log('a is be get')
return _value
},set:function(newVal){
console.log('a is be set')
_value = newVal
return _value
}
})
以上代码在控制台打印结果如图所示:
由此可见,从obj1中取a的时候会触发get方法,给obj1.a重新设值的时候会触发set方法,且get和set里必须要有返回值,返回什么值取到的和设置的就是什么值,这种方式需要借助设值外部变量来进行中转,不友好
简单的使用示例3(手写一个简约版的vue):
test.js中:
function vue(){
this.$data = {
a:1
}
this.el = document.getElementById('app')
this.virturaldom = ''
this.observer(this.$data)
this.render()
}
vue.prototype.observer = function(obj){//在vue源码中是通过defineReactive?1方法实现的监听get和set
var value;
var self = this;
for(var key in obj){
value = obj[key]
if(typeof value=='object'){
self.observer()
}else{
Object.defineProperty(obj,key,{
get:function(){//进行数据依赖,优点是:避免更新不相干组件的视图
return value
},
set:function(newVal){
value = newVal
self.render()
}
})
}
}
}
vue.prototype.render = function(){
this.virturaldom = 'i am'+this.$data.a
this.el.innerHTML = this.virturaldom
}
index.html中
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id='app'></div>
<script src="./test.js"></script>
<script>
var vm = new vue()
setTimeout(function(){
console.log(vm.$data.a)
vm.$data.a = '新值'
},2000)
</script>
</body>
</html>
页面结果: 一开始是 i am1,2s后变为i am新值
问题来了:defineProperty定义的get和set是对象的属性,那么数组怎么办?
对数组的处理其实就是做了一个装饰者模式,原理如下:
var arrayPro = Array.prototype;//先取数组的原型
var arrCopyPro = Object.create(arrayPro)//把取到的原型拷贝一份给arrCopyPro,防止污染原型
var arr = ['push','pop','shift'];//存储要被装饰的方法
arr.forEach((method,index)=>{
arrCopyPro[method] = function(){//在拷贝的原型链对象上重写方法
var ret = arrayPro[method].apply(this,arguments)//调用原本的方法
dep.notyfy();//触发视图更新
}
}
最后要把数组的prototype替换成新的prototype(arrCopyPro)
Vue3的响应式主要是基于Proxy实现的
Proxy对象用于定义基本操作的自定义行为
(和defineProperty类似,功能几乎一样,用法上有不同)
使用proxy的好处:
1.defineProperty只能监听某个属性,不能监听全对象
2.不会污染原对象
3.不用去做for循环,节省性能,提高效率
4.不用借助变量中转
5.可以监听数组,不用单独的去对数组进行特异性操作
Proxy简单的使用示例1:
var person = {// 源对象
name:'张三',
age:12
}
var p = new Proxy(person,{
get(target,property,receiver){//查的时候触发
//target就是person,property就是要获取的属性
//receiver就是代理对象p=Proxy{name:'张三',age:12}
//console.log("读取了!")
//console.log(target,property,receiver)
return Reflect.get(target,property)//等价于target[property]
},
set(target,property,newVal){//新增、修改的时候触发
//console.log("设置了!")
//console.log(target,property,newVal)
return Reflect.set(target,property,newVal)//等价于 target[property] = newVal;
},
deleteProperty(target,property){//删除的时候触发
//console.log("删除了!!!")
//console.log(target,property)
return Reflect.deleteProperty(target,property)//等价于 delete target[property]
}
})
接着在控制台中输入:person.name 打印出来张三,
但是get方法却没被触发,这是为什么呢?
这是因为proxy代理了源对象,给出一个新的代理对象
操作新对象才会触发get和set方法。
如下图所示:
使用proxy改写上面简约版的vue
vue.prototype.observer = function(obj){
var self = this;
this.$data = new Proxy(obj,{
get(target,key,receiver){
return target[key]
},
set(target,key,newVal,receiver){
target[key] = newVal
self.render()
}
})
}