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"]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容