Vue3响应式对象是如何实现的?

概述

Vue3的发布已过去3年时间,其更小的体积和更快的渲染机制给开发带来了更好的体验,它向下兼容 Vue2.x 版本,优化了主要核心双向绑定原理和体积大小,并且更加友好的兼容ts语法。在性能方面相对于Vue2做了以下改进:

①重写虚拟DOM的实现,并引入Tree-Shaking将打包体积减少41%。

②引入可以按需使用的Composition API,多余勾子配置不用再次打包。

③初次渲染快55%,更新渲染快133%,内存减少54%。

④使用Proxy代替defineProperty实现响应式。

⑤在源码方面,移除了一些冷门API,比如filter、inline-template。

本文将在对Vue3进行简介的基础上比较Vue2与Vue3响应式特性的不同,并着重介绍Vue3响应式对象的实现原理。

Vue2与Vue3的

响应式对象实现

在Vue2中对象响应式基于Object对象上的defineProperty()实现,通过defineProperty方法对对象的已有属性值的读取和修改进行拦截,通过重写数组更新数组一系列更新元素的方法来实现元素修改的拦截。例如:

Object.defineProperty(data, \'count\', {    get () {},    set () {}})

使用Object对象上的静态方法defineProperty为data的每一个属性值设置存取器函数,就能在属性读取与修改时作出相应的反应。

但这样的实现方式存在一些不足:

①在目标对象上新增和删除属性时无法进行拦截,因此界面不会响应式地更新。

②通过下标修改数组元素或是修改数组的length属性,也无法进行拦截和响应。

对此,Vue3针对对象响应式进行了新的实现。

Vue3响应式的核心原理是对需要进行响应式处理的对象整体进行代理操作,而不再是对对象已有属性进行存取器设置。具体来讲,是通过Proxy(代理)和Reflect(反射)实现:通过Proxy拦截对data任意属性的任意(13种)操作,包括属性值的读写、属性的添加、属性的删除等;并通过 Reflect动态地对被代理对象的相应属性进行相应操作。

ECMAScript6新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力,即可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作施加影响之前,可以在代理对象中对这些操作加以控制。

Vue3响应式对象是如何实现的?

Proxy代理器

01

创建代理

Proxy代理是ES6中新增的基础性语言能力,在ES6之前,ECMAScript中并没有类似代理的特性。在代理对象上执行的所有操作都会传播到目标对象,在任何可以使用目标对象的地方,都可以通过同样的方式来使用与之关联的代理对象。

代理是使用Proxy构造函数创建的,它接收两个参数:目标对象和处理程序对象,要创建空代理可以传一个简单的对象字面量作为处理程序对象,但不可以缺省处理程序对象。如下所示,在代理对象上执行的任何操作实际上都会应用到目标对象:

const company= {   name: \'eb\' }

const handler = {}

const proxy = new Proxy(company, handler)

//name属性会访问同一个值 console.log(company.name) //\'eb\' console.log(proxy.name) //\'eb\'

//给目标属性赋值会反映在两个对象上 company.name = \'ebChina\' console.log(company.name) //\'ebChina\' console.log(proxy.name) //\'ebChina\'

//给代理属性赋值也会反映在两个对象上 proxy.name = \'ebChinaTech\' console.log(company.name) //ebChinaTech console.log(proxy.name) //ebChinaTech

//严格相等可以用来区分代理和目标 console.log(company=== proxy) //false

02

定义捕获器

代理的主要目的是可以在处理程序对象中定义捕获器。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。

例如,可以定义一个get()捕获器,在ECMAScript操作以某种形式调用get时触发。

const company= {   name: \'ebChina\' }

const handler = { get(){ return \'ebChinaTech\' } }

const proxy = new Proxy(company, handler)

console.log(company.name) //ebChina console.log(proxy.name) //ebChinaTech

当通过代理对象执行get()操作时,就会触发定义的get()捕获器,只有在代理对象上执行相应操作才会触发捕获器,在目标对象上执行这些操作仍然会产生正常的行为。

03

反射API

所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为,但开发者并不需要手动费时费力重建原始行为,而是可以通过调用全局Reflect对象上的同名方法来轻松重建。

const company= {   name: \'ebChina\' }

const handler = { get(){ return Reflect.get(...arguments) } }

const proxy = new Proxy(company, handler)

console.log(company.name) //ebChina console.log(proxy.name) //ebChina

通过Proxy代理对象可以拦截对目标对象的13种操作,并通过Reflect对象上相应的同名方法重建操作。在此基础上开发者可以用最少的代码修改捕获的方法,实现对应的业务逻辑。

例如:通过捕获get、set和has等操作,可以知道属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过。

const company = {   name: \'ebChinaTech\' }const handler = {   get(target, property, receiver){      console.log(`Getting ${property}`)      return Reflect.get(...arguments)   },   set(target, property, value, receiver){      console.log(`Setting ${property} = ${value}`)      return Reflect.set(...arguments)   }} const proxy = new Proxy(company , handler) proxy.name      //Getting name proxy.age = 3   //Setting age = 3Proxy代理对象还可用于在前端对用户隐藏某些对象属性,例如:const hiddenProperties = [\'address\', \'manager\'] const company = {   name: \'ebChinaTech\',   address: \'Beijing\',   manager: \'Li\' } const handler = {   get(target, property){      if (hiddenProperties.includes(property)){         return undefined      } else {         return Reflect.get(...arguments)      }   },   has(target, property){      if (hiddenProperties.includes(property)){         return false      } else {         return Reflect.has(...arguments)      }   } } const proxy = new Proxy(company, handler) console.log(proxy.address)  //undefined console.log(proxy.manager)  //undefined console.log(proxy.name)  //ebChinaTech console.log(\'address\' in proxy)  //false console.log(\'manager\' in proxy)  //false console.log(\'name\' in proxy)  //true

代理的应用场景是不可限量的,开发者使用它可以创建出各种编码模式,比如跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定以及可观察对象。由于proxy的性能比defineproperty好,因此vue3通过使用proxy重写对象响应式的同时,也提升了vue架构中高频使用的数据特性的性能,大大提升了开发体验。而vue3的其他方面优化,例如源码体积的优化以及打包工具的优化,则为部署提供了更好的解决方案,也成为超越vue2的前端框架。

Vue3响应式对象是如何实现的?

原创文章,作者:EBCloud,如若转载,请注明出处:https://www.sudun.com/ask/32587.html

(0)
EBCloud的头像EBCloud
上一篇 2024年4月2日 下午3:28
下一篇 2024年4月2日 下午3:28

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注