Vue响应式数据
Vue中的响应式数据通过Object.defineProperty实现,但这个方法不能劫持数组。
首先我们定义一个要响应式的数据
let obj ={
name:'qc',
location:{adress:'hz'},
arr:[1]
}
在创建一个渲染notify函数,用来提示我们的数据是否被渲染提示。
function notify(){
console.log("视图更新了!")
}
接下来开始正式写代码,设想当我们修改了obj对象里的数据,命令窗口就会提示视图更新了,并且数据已被修改。
创建一个observer函数,目的是遍历此对象的属性。
function observer(obj){
if(typeof obj == 'object'){
for (const key in obj) {
defineReactive(obj,key,obj[key])
}
}
}
创建一个defineReactive函数,目的用来劫持数据。
function defineReactive(data,key,value){
// console.log(data,key,value);
Object.defineProperty(data,key,{
get(){
if(typeof value == 'object'){
observer(value)
}
return value
},
set(newValue){
notify()
value=newValue
}
})
}
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty具体查看mdn文档
通过observer函数遍历出obj属性,传给defineReactive,在此函数内用Object.defineProperty,get方式拿到当前的值value,set方式可以拿到设置的新的值,新值赋给老值就完成了,数据更新。
让我们看下效果
observer(obj)
obj.name='aa'
console.log(obj.name)
目前完成的只能遍历对象一层,若出现多层的话,我们可以采取递归劫持。
function defineReactive(data,key,value){
// console.log(data,key,value);
Object.defineProperty(data,key,{
get(){
if(typeof value == 'object'){
observer(value)
}
return value
},
set(newValue){
notify()
value=newValue
}
})
}
obj.name='aa'
obj.location.adress='bj'
console.log(obj.location.adress);
以上方式都不能对数组响应式,vue对数组采用了另一套方式,因为数组本身的方法就可以让数组更新例如push,splice。
首先我们先遍历出那些可以让数组更新的方法
let mothod =['pop','shift','unshift','sort','reverse','splice','push']
这里为了让大家看到notify函数执行视图更新了,我们copy一份数组的原型,在copy的上面去修改。
let arrayProto=Array.prototype//先获取到原来的原型上的方法 ,为了修改数组里原生方法 添加一个render函数操作
let proto =Object.create(arrayProto)//复制一个新原型对象 跟旧的无关
mothod.forEach(mothod=>{
proto[mothod]=function(){
notify()
arrayProto[mothod].call(this,...arguments)
}
})
在修改observer函数
function observer(obj){
if(Array.isArray(obj)){
obj.__proto__=proto
return
}
if(typeof obj == 'object'){
for (const key in obj) {
defineReactive(obj,key,obj[key])
}
}
}
修改defineReactive函数
function defineReactive(data,key,value){
// console.log(data,key,value);
Object.defineProperty(data,key,{
get(){
if(typeof value == 'object'){
observer(value)
}
if(Array.isArray(value)){
observer(value)
}
return value
},
set(newValue){
notify()
value=newValue
}
})
}
让我们去迫不及待的去看下效果
observer(obj)
obj.arr.push(22)
console.log(obj.arr);
//不支持 数组内容直接改变例如arr[1]=11,不支持数组length-- 都不会发生数据响应
这样我们就实现了简易的数据响应式
注意因为vue数据响应都是绑在data属性里面,所以你给一个对象添加一个新的属性时,是不会生效数据响应的,不过vue中提供了$set方法,可以动态的添加响应式数据,我们再次也可以去实现下。
function $set(data,key,value){
if(Array.isArray(data)){
return data.splice(key,1,value) //当前key 改一个 值是value 触发 数组更新
}
defineReactive(data,key,value)
}
动态添加属性
$set(obj,'a',1)
obj.a=2
console.log(obj.a);
动态添加数组
obj.arr.push(22)
$set(obj.arr,0,2)
console.log(obj.arr)
总结
vue响应式数据就是依靠Object.defineProperty来实现的,但是缺点是数组无法实现,所以vue当遇到数组时会启动另一套方法,这方法就是利用数组本身的方法例如 push,splice 来触发 数组更新。