Vue 最独特的特性之一,是其非侵入性(不用调用Vue的api来实现数据更新)的响应式系统。
响应式:即数据改变,对应的视图也会改变。
响应式原理
:采用数据劫持结合发布-订阅者模式的方式,通过Object.defineProperty()来劫持data里面各个属性的setter和getter,在数据变动的时候,触发set方法,检测到数据发生变化,会发布消息给订阅者,触发相应的监听回调,生成新的虚拟DOM树,视图更新。
Object.defineProperty()
可以直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象。还可以设置一些额外隐藏的属性(例如是否可写writeable,是否可以枚举enumerate)。
当页面渲染时(render),触发data里面的数据(getter),watcher(观察者)会把接触过的数据记录为依赖。每个数据都有getter和setter,当你修改这个数据的时候,依赖的setter触发,然后通知到watcher,从而使关联这个数据的组件重新渲染。
实现一个Object.defineProperty()功能。
var obj={}
//defineReactive就是响应式
function defineReactive(data,key,val){ //data数据对象,key键(即属性),val值(用来周转,set里面修改值之后,设置val为新的值,get才能获取到新的val)
Object.defineProperty(data,key,{
//getter
get(){ //里面的get()和set()与外面的defineReactive构成了一个闭包环境(因为要内存函数使用到val,所以要用闭包)
console.log('访问对象的'+key+'属性')
return val; //要有返回值,才能获取到这个属性的值
},
//setter
set(newValue){
console.log('改变对象的'+key+'属性',newValue)
if(val==newValue){
return ; //如果传入的参数(属性要改为的值)和原来的值相等,就不做任何处理
}
val=newValue //如果传入的参数是一个新的值,就改变这个属性的值
}
})
}
defineReactive(obj,'a',10)
console.log(obj.a) //访问对象的属性 /n 10
obj.a=15
obj.a++ //先访问到val,再改变
console.log(obj.a) //改变对象的属性 /n 访问对象的属性 /n
defineReactive(obj,'a',10)
defineReactive(obj,'b',20)
console.log(obj.a)
console.log(obj.b)
obj.b=30
console.log(obj.b)
例:使用Object.defineProperty()实现一个响应式功能.
<div id="app">
<input type="text" id="a">
<span id="b">span>
div>
<script type="text/javascript">
var data = { //模拟vue里面的data属性
a:1,
b:2
};
var vm={} //模拟vue实例
function defineReactive(vm,key,val){ //defineReactive就是响应式
//实现数据双向绑定的方法————数据劫持:当访问或者设置vm中的某一个成员时,做一些干预操作。
Object.defineProperty(vm, key, { //参数1:vue实例,参数2:要劫持的属性,参数3:对象(用来获取和设置对象的属性值)
//getter
get: function() {
console.log('get vm '+key+' val:'+ val);
document.getElementById('a').value = val;
document.getElementById('b').innerHTML = val;
return val;
},
//setter
set: function(newVal) {
if(val===newVal){
return ;
}
val = newVal;
console.log('set vm '+key+' val:'+ val);
document.getElementById('a').value = val;
document.getElementById('b').innerHTML = val;
}
});
}
document.addEventListener('keyup', function(e) {//触发事件的时机,从而执行相应的操作
vm.a=e.target.value
});
Object.keys(data).forEach(k=>{ //data里面有多个属性时,遍历data的各个属性
defineReactive(vm,k,data[k])
})
script>
Vue2存在的问题:
响应式原理
:Vue3通过ES6的代理对象Proxy进行响应式,代理data对象里面所有的属性及数组,访问属性时触发get(),改变属性值时触发set(),然后发布消息给订阅者,重新渲染页面。
实现Proxy的功能。
<div id="app">
div>
<script>
let data={
msg:'Hello',
count:0,
arr:[1,2,3,4]
}
//模拟vue实例
const vm=new Proxy(data,{ //用Proxy,不用循环就可以遍历到data对象的所有属性,数组也可以更改数值,改变数组长度
//执行代理行为的函数
//当访问vm的成员会执行
get(target,key){ //target就相当于是data对象,key是对象的属性
console.log('get key:',key,target[key])
document.querySelector('#app').textContent=target[key]
return target[key]
},
//当设置vm的成员会执行
set(target,key,newValue){
console.log('set key',key,newValue)
if(target[key]===newValue){
return;
}
target[key]=newValue
document.querySelector('#app').textContent=target[key]
}
})
script>