Vue3.0「十七」-- vue3.0升级新特性及Proxy重写响应式讲解

vue3.0 升级内容

全部用TS重写的(响应式、vdom、模本编译)
性能提升,减少代码量
会调整部分API
Proxy重写响应式

vue2.x 马上要过时了吗

vue3.0从正式发布到推广,还需要一段时间
vue2.x应用范围广,有大量项目需要维护升级
proxy存在兼容性问题,且不能ployfill

社区热门知识点:Proxy重写响应式讲解

回顾vue2.*的响应式原理 [object.defindeProperty]

object.defindeProperty缺点:

  • 深度监听需要一次性递归
  • 无法监听新增属性/删除属性(vue.set/vue.delete)
  • 无法原生监听数组,需要特殊处理

vue3_Proxy实现响应式原理

前置知识

Proxy ES6语法 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
Proxy 可以理解成, 在目标对象之前架设一层“ 拦截”, 外界对该对象的访问, 都必须先通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤和改写。

语法:const p = new Proxy(target, handler)
target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler 一个通常以函数作为属性的对象, 各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
handler.get() 方法用于拦截对象的读取属性操作。
handler.set() 方法是设置属性值操作的捕获器。
handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法这些方法与proxy handlers的方法相同。

Reflect.get(target, propertyKey[, receiver])
Reflect.deleteProperty(target, propertyKey)
Reflect.set(target, propertyKey, value[, receiver])
target: 需要取值的目标对象; key: 需要获取的值的键值;value::设置的值。
如果target对象中指定了getter, receiver则为getter调用时的this值

1. Proxy对数据拦截监听的基本使用

1、用Proxy将目标对象data进行包装拦截处理如下:

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        const result = Reflect.get(target, key, receiver)
        console.log('get', key) //监听
        return result //返回结果
    },
    set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver)
        console.log('set', key, value) //set age 30
        return result //是否设置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key)
        console.log('delete property', key) //delete property name
        return result //是否删除成功
    },
})

2、定义data为对象,并对data对象进行操作时

const data = {
    name: 'lili',
    age: 20,
}
// 对象操作
proxyData.age // get操作  :  get age
proxyData.age = 30 // set操作 :  set age 30
proxyData.sex = "女" // set操作 :  set sex 女
delete proxyData.name // 删除操作 : delete property name

不足:
在对象设置属性时,无法确定是新增属性还是原有属性;

3、定义data为数组,并对data数组进行操作

const data =['a','b','c']

proxyData.push('d') 
// get push  push()方法触发
// get length //获取数组长度
// set 3 d //设置值
// set length 4 设置数组长度

不足:
给数组添加元素时,没必监听 原型的属性,如push(),只需要监听本身(非原型)的属性,
set 3 d,set length 4 为重复处理同一个数据,set length 4多余

2. Proxy对数据拦截监听使用的优化

针对以上问题,对Proxy对数据拦截监听使用进行优化

1.在对象设置属性时,无法确定是新增属性还是原有属性
在set方法中判断

const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
    console.log('已有的 key') //监听
} else {
    console.log('新增的 key')
}

2、在监听属性时,只监听本身(非原型)的属性

在get方法中,判断如果是自身的属性,才进行监听

// Reflect.ownKeys()方法可以返回包含Symbol属性在内的自有属性。
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
    console.log('get',key)//监听
}

3、重复的数据不处理

在 set方法中,重复数据不处理

const oldVal=target[key]
if (value === oldVal) {
    return true
}

以上问题,Proxy对数据拦截监听使用的优化后:

const proxyData = new Proxy(data, {
  // target:目标对象、 key:被捕获的属性名、receiver:Proxy或者继承Proxy的对象
  get(target, key, receiver) {
    // 只监听 处理本身(非原型)的属性
    const ownKeys = Reflect.ownKeys(target)
    if (ownKeys.includes(key)) {
      console.log('get', key) //监听
    }
    const result = Reflect.get(target, key, receiver)
    return result //返回结果
  },
  // value 新属性值。
  set(target, key, value, receiver) {
        const ownKeys = Reflect.ownKeys(target)
    if (ownKeys.includes(key)) {
      console.log('已有的 key') //监听
    } else {
      console.log('新增的 key')
    }
    // 重复的数据不处理
    if (value === target[key]) {
      return true
    }
    const result = Reflect.set(target, key, value, receiver)
    console.log('set', key, value) //set age 30
    return result //是否设置成功
  },
  deleteProperty(target, key) {
    const result = Reflect.deleteProperty(target, key)
    console.log('delete property', key) //delete property name
    return result //是否删除成功
  },
})

定义data为数组,并对data数组进行操作时

const data =['a','b','c']

proxyData.push('d') 
// get length //获取数组长度
// set 3 d //设置值

实现了只保留了对自身属性的监听,重复数据没有重复设置

3. Proxy实现响应式

实现思路:
① 创建响应式方法reactive(data),该方法可以传入需要处理的数据对象data
函数内逻辑:
② 判断 data 是否为 对象或者数组,不是直接返回
③ 创建 Proxy 代理对象,Proxy对象中传入data
Proxy 代理对象中的方法配置:
④ 在get()方法对数据的进行监听:只监听 处理本身(非原型)的属性;在返回结果中采用递归调用reactive(),实现对数据的深度监听
⑤ 在 set() 方法中进行数据的新增和更新:判断是否是新增数据;重复的数据不处理;
⑥ 在 deleteProperty() 方法中对数据进行删除操作;
实例:
⑦ 定义数据data,传入响应式方法中,返回的值proxyData为实现响应式的可操作数据

// 创建响应式
function reactive(target = {}) {
  if (typeof target != 'object' || target == null) {
    // 不是对象或者数组,则返回
    return target
  }
  // 代理配置 生成代理对象
  return observed = new Proxy(target, {
    // target:目标对象、 key:被捕获的属性名、receiver:Proxy或者继承Proxy的对象
    get(target, key, receiver) {
      // 只监听 处理本身(非原型)的属性 ,如push()
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        console.log('get', key) //监听
      }
      const result = Reflect.get(target, key, receiver)
      // return result //返回结果
      // 深度监听
      // 性能如何提升的?
      return reactive(result) //递归get处理 实现深度监听
    },
    // value 新属性值。
    set(target, key, value, receiver) {
      // 判断是否是新增属性
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        console.log('已有的 key') //监听
      } else {
        console.log('新增的 key')
      }
      // 重复的数据不处理
      const oldVal = target[key]
      if (value === oldVal) {
        return true
      }
      const result = Reflect.set(target, key, value, receiver)
      console.log('set', key, value) //set age 30
      return result //是否设置成功
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key)
      console.log('delete property', key) //delete property name
      console.log('result', result) //result true
      return result //是否删除成功
    },
  })

}


// 测试数据
const data = {
  name: 'lili',
  age: 20,
  info: {
    city: "beijing"
  }
}
const proxyData = reactive(data)

vue3.0 与 vue2.* 实现响应式 比较

vue3.0 基于 Proxy 实现响应式
vue2.0 基于 Oject.defineProperty 实现响应式

相较于vue2,Proxy 实现响应式:

  • 深度监听,性能更好
  • 可监听新增删除的属性
  • 可监听数组变化

总结:

  • Proxy 能规避 Oject.defineProperty的问题
  • Proxy无法兼容所有浏览器,无法 polyfill

Reflect的作用总结

  • 1.和 Proxy 能力一一对应

  • 2.规范化、标准化、函数式

const obj={a:100,b:200}
'a' in obj //true
Reflect.has(obj,'a')//true

delete obj.b //true
Reflect.deleteProperty(obj,'b') //true
  • 3.替代掉Object上的工具函数
const obj={a:100,b:200}
Object.getOwnPropertyNames(obj) //["a", "b"]
Reflect.ownKeys(obj) // ["a", "b"]

你可能感兴趣的:(Vue3.0「十七」-- vue3.0升级新特性及Proxy重写响应式讲解)