vue3响应式原理 您所在的位置:网站首页 vue2数据劫持缺陷 vue3响应式原理

vue3响应式原理

2023-11-21 10:49| 来源: 网络整理| 查看: 265

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

1. 什么是响应式?

数据变化,视图(也就是DOM)会自动变化。

我们在使用vue差值表达式时,改变数据后,页面上相应数据会自动改变。

2. 实现一个响应式系统,需要做些什么?

我们要实现的是:数据A改变时,视图中用到数据A的所有地方,都要发生改变。

现在将上面的步骤拆为3步:

监听数据A的变化 --- 数据劫持 (当数据变化时,我们可以做一些特定的事情) 收集所有依赖于数据A的元素 --- 依赖收集 (我们要知道那些视图层的内容(DOM)依赖了哪些数据(state)) 通知上述这些依赖于数据A的元素更新 --- 派发更新 (数据变化后,如何通知依赖这些数据的DOM)

vue实现响应式主要做了这么几件事:

数据劫持:new Vue的时候遍历data对象,用Object.defineProperty给所有属性加上了getter和setter 依赖的收集:render的过程(执行render()时),会触发数据的getter,在getter的时候把当前的watcher对象收集起来 派发更新:setter的时候,遍历这个数据的依赖对象(watcher对象),进行更新 3.数据劫持的实现方式

方式一:Object.defineProperty()

方式二:ES6中新增内置对象:Proxy

Vue2的响应式数据劫持是利用Object.defineProperty()实现的,vue3改成了proxy。

注意:vue3中,基本数据类型的响应式仍是依靠Object.defineProperty()实现的,而对象类型数据使用proxy来实现。

4.为什么vue3使用了 Proxy 替换了原先的 Object.defineproperty 来实现数据响应

Object.defineproperty()的缺点:

1.无法检测到对象属性的新增或`删除(vue2提供了vue.set方法来解决) 2.当对某个数组arr[index] = val这样赋值时,无法监听数组变化, 3.当改变数组长度时,无法监听数组变化。 4.深度监听,层层处理,影响性能 5.Object.defineproperty()每调用一次都只能对对象的某一个属性进行数据劫持,所以要采用循环遍历,代码写起来比较麻烦。 5. Proxy实现响应式 Proxy内置对象

作用: Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法:

const p = new Proxy(target, handler)

参数:

target

要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

重要方法:

get()

作用: 属性读取操作的捕捉器。

语法:

var p = new Proxy(target, {   get(target, property, receiver) {     } });

参数:

target 目标对象。

property被获取的属性名。

receiverProxy或者继承Proxy的对象

返回值: get方法可以返回任何值。

set()

作用: 属性设置操作的捕捉器。新增属性操作的捕捉器。

语法:

const p = new Proxy(target, { set(target, property, value, receiver) { } });

参数:

target目标对象。

property将被设置的属性名或 Symbol。

value新属性值。

receiver

最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被 间接地调用(因此不一定是 proxy 本身)。

比如: 假设有一段代码执行 obj.name = "jen", obj 不是一个 proxy,且自身不含 name 属 性,但是它的原型链上有一个 proxy,那么,那个 proxy 的 set() 处理器会被调用,而此时,obj 会 作为 receiver 参数传进来。

返回值:

set() 方法应当返回一个布尔值

deleteProperty()

作用: delete 操作符的捕捉器。

语法:

var p = new Proxy(target, { deleteProperty(target, property) { } });

参数:

target目标对象。

property待删除的属性名。

返回值:

deleteProperty 必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。

简单实现响应式 // 源数据 let person = { name: '张三', age: 18 } //模拟vue3中实现响应式 cosnt p = new Proxy( person, { get( target, proName ) { console.log(`有人读取了p身上的${propName}属性`) return target[proName] }, // set在新增属性时,也会调用set set( target, proName, value ) { console.log(`有人修改了p身上的${propName}属性,更新页面!`) target[proName] = value }, deleteProperty( target, proName ) { console.log(`有人删除了p身上的${propName}属性,更新页面!`) return delete target[proName] //返回一个标识删除是否成功的布尔值 } } ) 加入Reflect

为什么要加入Reflect?

当使用Object.defineProperty追加属性时,不可以重复追加同一个属性。

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

上面的代码会报错,由于JS单线程,会导致整个程序中断,不再运行。

如果不想让程序中断,可以使用try/catch将代码包裹起来,但要多写一些代码,比较麻烦。

而通过Reflect,出现错误时不会报错,也不会导致程序中断;会返回一个false值告诉你操作失败了。

Reflect健壮性更好

// 源数据 let person = { name: '张三', age: 18 } //模拟vue3中实现响应式 cosnt p = new Proxy( person, { get( target, proName ) { console.log(`有人读取了p身上的${propName}属性`) return Reflect.get(target, proName) }, // set在新增属性时,也会调用set set( target, proName, value ) { console.log(`有人修改了p身上的${propName}属性,更新页面!`) return Reflect.set( target, proName, value ) }, deleteProperty( target, proName ) { console.log(`有人删除了p身上的${propName}属性,更新页面!`) return Reflect.deleteProperty(target, proName) //返回一个标识删除是否成功的布尔值 } } ) // 源数据 let person = { name: '张三', age: 18 } //模拟vue3中实现响应式 cosnt p = new Proxy( person, { get( target, proName ) { console.log(`有人读取了p身上的${propName}属性`) return Reflect.get(target, proName) }, // set在新增属性时,也会调用set set( target, proName, value ) { console.log(`有人修改了p身上的${propName}属性,更新页面!`) return Reflect.set( target, proName, value ) }, deleteProperty( target, proName ) { console.log(`有人删除了p身上的${propName}属性,更新页面!`) return Reflect.deleteProperty(target, proName) //返回一个标识删除是否成功的布尔值 } } )


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有