Vue源码分析及实现-响应系统-序章

113 阅读2分钟

前面我们主要学习了框架的基础概念并且完成了自定义vue项目的架子

现在我们来开启任务一,响应系统

什么是响应系统

会影响视图变化的数据称为响应数据,当响应式数据发生变化时,视图也会相应的变化

这就引出了常见面试题:vue2vue3的响应式实现方式为什么不同:

  • 那么vue中的响应式数据是如何实现的呢?
  • vue2和vue3实现上有什么变化呢,为什么会变化呢?

Vue2

vue2是以Object.defineProperty()方法实现响应式,该方法会直接在一个对象上定义一个新属性,或者修改一个对象的属性,并返回此对象,以下为商品和价格实现响应性

// 定义一个商品
let quantity=2
let product={
    price:10,
    quantity:quantity
}
// 总价格
let total=0
// 计算总价格的函数
let effetc=()=>{
    total=product.price*product.quantity
}

// 第一次打印
effect()
console.log(`总价格:${total}`)

Object.defineProperty(product,'quantity',{
    set(newVal){
        console.log('setter')
        quantity=newVal
        // 触发effect
        effect()
    },
    get(){
        console.log('getter')
        return quantity
    }
}

Object.defineProperty存在一个致命的缺陷:不能检测对象和数组的变化 详细

  • 当为对象新增一个没有在data中声明的属性时,没办法监听对象的变化
  • 当为数组新增一个新下标的时候,没办法监听数组的变化

因为object.defineProperty是为指定对象指定属性赋予响应性,而后面新增的属性未实现响应性,能打印出来,但是视图不会变化

Vue3

vue3实现响应性核心api为proxy,该对象可以创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找)

语法:

const p=new Proxy(target,handler)

重写商品实例:

// 定义一个商品
let quantity=2
let product={
    price:10,
    quantity:quantity
}

const proxyProduct=new Proxy(product,{
    set(target,key,newVal,receiver){
        // target:被代理对象
        // key:使用的对象字段 如 'price'
        target[key]=newVal
        effect()
        return true
    },
    get(target,key,receiver){
       return Reflect.get(target,key,receiver)
    }
})

// 总价格
let total=0
// 计算总价格的函数
let effetc=()=>{
    total=proxyProduct.price*proxyProduct.quantity
}

// 第一次打印
effect()
console.log(`总价格:${total}`)

为什么需要使用Reflect进行拦截呢

如下代码,我们控制台使用proxyPerson.fullName的时候,只触发了一次get,这肯定不应该吧,因为fullName函数里面还触发了两次,为什么没有打印呢,因为fullName中的this指向的是person,而我们实现响应的thisproxyPerson,所以只触发一次,但是当然不应该只触发一次了。

所以需要指定this,让我们得到期望的触发get三次,所以需要使用Reflect拦截,第三个参数即代表this的指向

const person={
      firstName:"John",
      lastName:"Doe",
      get fullName(){
        return this.firstName+" "+this.lastName;
      },
    }

    const proxyPerson=new Proxy(person,{
      get(target,key,receiver){
        console.log("get",key);
        return target[key]
      },
    })

    const proxyPerson2=new Proxy(person,{
      get(target,key,receiver){
        console.log("get",key);
        return Reflect.get(target,key,receiver)
      },
    })

image.png

当我们期望监听代理对象的gettersetter时,不应该使用target[key],因为他在某些时刻,即部分this指向为被代理对象的时候,是不可靠的,应该使用Reflect借助他的get和set方法,使用receiver作为this

区别

proxy

  • Proxy将代理一个对象(被代理对象),得到一个新的对象(代理对象),同时拥有被代理对象中所有的属性
  • 当想要修改对象的指定属性时,我们应该使用代理对象进行修改
  • 代理对象的任何一个属性都可以触发handler的gettersetter

Object.defineProperty

  • Object.defineProperty指定对象的制定属性设置属性描述符
  • 当想要修改对象的指定属性时,可以使用原对象进行修改
  • 通过属性描述符,只有被监听的指定属性,才可以触发gettersetter