Vue2和Vue3响应式的对比

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()差别:

  1. Proxy代理的是整个对象,而不是对象的某个特定属性。
  2. 代理之后操作的是实例对象,而不是原对象。

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进行使用。


© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享