三、【Vue3】——Vue2与Vue3响应式原理区别

1,182 阅读2分钟

本文已参与【新人创作礼】活动,一起开启掘金创作之旅。

前言:Vue2实现响应式原理使用的是Object.defineProperty()对对象中某个属性的读取、修改进行拦截,存在新增和删除缺陷。而Vue3实现响应式原理使用的是Proxy代理,拦截对象中任意属性的变化;

一、回顾Vue2响应式原理

实现原理:

  • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

  • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。 存在问题:

  • 当对象中属性过多时Object.defineProperty()需针对每个属性进行遍历实现响应式,效率不高;

  • 新增属性、删除属性, 界面不会更新;

  • 只有configurable为true时候,该属性才能从对应的对象上被删除,但源数据不会响应删除;

  • 直接通过下标修改数组, 界面不会自动更新。

<script>
        // 源数据
        let person = {
            name: '张三',
            age: 18
        }
        let p = {}
        // 对源数据中的每个属性进行遍历,使其变为响应式
        Object.defineProperty(p, 'name', {
            // **只有configurable为true时候,该属性才能从对应的对象上被删除,但person不会响应删除**
            configurable:true, 
            get() {
                return person.name
            },
            set(value) {
                person.name = value
            }
        })
        Object.defineProperty(p, 'age', {
            get() {
                return person.age
            },
            set(value) {
                person.age = value
            }
        })
    </script>

新增属性代码运行演示: image.png 删除属性代码运行演示:

image.png

二、Vue3响应式原理

实现原理:

<script>
        // 源数据
        let person = {
            name: '张三',
            age: 18
        }
        ProxyReflectwindow上内置的函数
        let p = new Proxy(person, {
            get(target, propName) {
                console.log(`有人读取了p身上的${propName}属性`)
                return Reflect.get(target, propName)
            },
            set(target, propName, value) {
                console.log(`有人新增/修改了p身上的${propName}属性,我要去更新界面了!`)
                Reflect.set(target, propName, value)
            },
            deleteProperty(target, propName) {
                console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
                return Reflect.deleteProperty(target, prop)
            }
        })
    </script>

下面来详细介绍下Proxy(代理)和Reflect(反射):

Proxy:

  • 语法:let p = new Proxy(target, handler)
    • target:要使用 Proxy 包装的目标对象,此处为person源数据;
    • handler:一个对象。可以只传一个空对象,也能实现增删改查操作let p = new Proxy(person, {});可以向上述代码一样传入含有的get set deleteproperty函数来拦截对象中任意属性的变化;

get函数:

get(target, propName) {
    // target:目标对象,也就是person源数据;
    // propName:被获取的属性名;
    console.log(`target, propName`, target, propName)
    return target[propName]
},

image.png

set函数:

// set函数对新增或修改一个属性都可以拦截到
set(target, propName, value) {
   // value:新属性值
   console.log(`target, propName,value`, target, propName,value)
   target[propName] = value
},

image.png

deleteProperty函数:

deleteProperty(target, propName) {
  console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
  return delete target[propName]
}

image.png 有人注意到,我在此处展示函数阶段没有用到Reflect函数,但最开始展示原理时候却是用到的,以下解释一下Reflect作用。

Reflect:

Reflect函数身上的一些方法与Object相同,比如:

  1. Reflect.get(target,propName)相当于get函数中直接target[propName]
  2. Reflect.set(target,propName,value)相当于set函数中target[propName] = value
  3. Reflect.deleteProperty(target,propName)相当于deleteProperty函数中的delete target[propName] 当然,Reflect函数身上还有Reflect.defineProperty方法,与Object.defineProperty有所不同:
  4. Object.defineProperty对同一个对象同一个属性重复操作时,系统会报错代码运行不下去,但Reflect.defineProperty不会报错只会运行第一条结果并继续执行后边代码;

Object.defineProperty:

let obj = { a: 1, b: 2 }
        Object.defineProperty(obj, 'c', {
            get() {
                return 3
            }
        })
        Object.defineProperty(obj, 'c', {
            get() {
                return 4
            }
        })

image.png

Reflect.defineProperty:

let obj = { a: 1, b: 2 }
        const x1 = Reflect.defineProperty(obj, 'c', {
            get() {
                return 3
            }
        })
        console.log('x1',x1)

        const x2 = Reflect.defineProperty(obj, 'c', {
            get() {
                return 4
            }
        })
        console.log('@@@@@') 

image.png

  1. 以上代码演示可以看出,Reflect.defineProperty拥有返回值,且是一个布尔值,为后续逻辑判断做铺垫,不用向Object.defineProperty一样进行try...catch捕获。

注:一般组件封装用Reflect函数比较多,宽容度好,组件更健壮