Vue2响应式原理
vue2中响应式原理利用了Object.defineProperty()。
接受参数:
- 添加属性的对象
- 定义或修改的属性名称
- 定义或修改的属性描述符
例子:
let person = {}
let name = 'cyl'
Object.defineProperty(person,'name',{
get(){
console.log(`读取了属性${name}`)
return name
},
set(value){
console.log(`修改属性${name}为${value}`)
name = value
}
})
console.log(person.name)
person.name = 'hh'
console.log(person.name)
复制代码
监听对象上的多个属性
let animal = {
name:'cat',
age:3
}
Object.keys(animal).forEach((key)=>{
Object.defineProperty(animal, key, {
enumerable: true, //可枚举
configure: true, //可删除的
writable :true, //可修改的
get(){
return animal[key]
},
set(value){
animal[key] = value
}
})
})
console.log(animal.name)
复制代码
执行发现控制台报错,栈溢出。这是由于读取animal上的属性是,触发get方法,但是在返回animal[name]的时候,又一次触发了get方法,形成一个无限递归的过程。
因此,我们在get中不能直接return animal[name]。
function defineProperty(obj,key,val){
Object.defineProperty(obj,key,{
get(){
return val
},
set(newVal){
val = newVal
}
})
}
function Observer(obj){
Object.keys(obj).forEach( (key) =>{
defineProperty(obj,key,obj[key])
})
}
Observer(animal)
复制代码
深度监听一个对象
上面只是简单的对象,如果是一个层层嵌套的对象呢?答案是递归!
//在defineProperty()中对传入的属性进行递归
function defineProperty(obj,key,val){
Observer(val)
Object.defineProperty(obj,key,{
get(){
return val
},
set(newVal){
val = newVal
}
})
}
//在Observer中加一个递归停止的条件
function Observer(obj){
if (typeof obj !== "object" || obj === null) {
return
}
Object.keys(obj).forEach( (key) =>{
defineProperty(obj,key,obj[key])
})
}
复制代码
这里还有另一个小问题:如果原本的属性是一个基本的数据类型,但是我们在修改该属性的时候修改为一个对象,需要在set中进行判断。
function defineProperty(obj,key,val){
Object.defineProperty(obj,key,{
get(){
return val
},
set(newVal){
if(newVal === val) return
Observer(newVal)
val = newVal
}
})
}
复制代码
在vue2中这里还有些许不完美的地方,那就是无法监听到对象的增删。这里也可以使用vue2中的Vue.$set()
,Vue.$delete()
或者是vm.$set()
,vm.$delete()
。
监听数组
let arr = [1,2,3]
let obj = {}
Object.defineProperty(obj,'arr',{
get(){
console.log('触发了get方法')
return arr
},
set(newVal){
console.log("触发了set方法")
arr = newVal
}
})
obj.arr = [] //触发了set方法
obj.arr.push(1) //触发了get方法
obj.arr[0] = 1 //触发了get方法
obj.arr.pop() //触发了get方法
obj.arr = [1,2,3] //触发了set方法
复制代码
我们可以看到数组的一些方法例如pop、push为数组添加或者删除元素时,不能触发属性arr的setter,无法对属性arr中元素进行实时监听,为此vue2重写了这些数组。
同时我们注意到的上面的push、pop直接通过索引为数组添加元素时会触发arr的getter方法,这是由于这些操作的返回值。比如使用push为数组添加元素时,push方法返回值总是添加之后数组的新长度,当访问数组新长度时自然会触发settet方法。
Vue3响应式原理
监狱vue2中监听数据时的一些弊端,vue3监听数据的时候弃用了Object.defineProperty()
,而使用了Proxy()
。
语法
const p = new Proxy(target,handler)
参数:
- target:代理的对象
- handler:一个以函数为属性的对象,其中定义了代理对象的各种操作
let animal = {
name:'cat',
age:3
}
let handler = {
get(obj, key){
return key in obj ? obj[key] ? 'hello'
},
set(obj,key,val){
obj[key] = val
return true
}
}
const p = new Proxy(animal, handler)
console.log(p.name)
console.log(p.sex)
p.name = 'dog'
console.log(p.name)
复制代码
注意:set函数必须返回一个布尔值表示设置成功,返回false则报错TypeError
。
与Object.defineProperty()差别:
- Proxy代理的是整个对象,而不是对象的某个特定属性。
- 代理之后操作的是实例对象,而不是原对象。
Proxy如何解决vue2中的问题
深度监听
let animal = {
maoke:{
name:'cat'
},
age:3
}
let handler = {
get(obj,key){
return key in obj ? obj[key] :'hello'
},
set(obj,key,val){
obj[key] = val
}
}
let p = new Proxy(animal,handler)
console.log(p.maoke.name) //cat
console.log(p.age) //3
p.maoke.name = 'tiger'
console.log(p.maoke.name) //tiger
复制代码
对象增删属性
let handler ={
//删除
deleteProperty(obj,key){
delete obj[key]
},
get(obj,key){
//不存在返回默认值
return key in obj ? obj[key] : 'value'
}
}
复制代码
数组无法监听
let arr = []
let handler = {
get(obj,key){
console.log('触发了get方法')
return key in obj ? obj[key] : 'value'
},
set(obj,key,value){
console.log('触发了set方法')
obj[key] = val
return true
}
}
let p = new Proxy(arr,handler)
p.push(1)
//触发了get方法 获取数组arr的push方法
//触发了get方法 获取数组arr的length属性
//触发了set方法 设置arr[0] = 1
//触发了set方法 设置数组长度为1
p[0] = 1
//触发了set方法
复制代码
除了上述改动之外,vue3其实还用到了Reflect来搭配Proxy进行使用。