复习一下ES6中的Proxy

前言

最近一直在复习ES6+的一些新特性,刚好看到Proxy,结合Vue3也是由defineProperty转向了Proxy,因此再好好复习一下Proxy

Proxy是什么

Proxy中文名是代理的意思,它是ES6中提供的创建对象代理的方式,应用目标是对象,这也是Vue3相比Vue2实现响应式原理不同的地方,这个后面会详细说明。Proxy会允许操作者定义额外的行为来拦截或者改写对于目标对象的基本操作,这里说的针对对象的基本操作是指诸如get、set、delete、hasProperty、defineOwnProperty等内部方法,而不是我们常用的比如数组的splice,split,push方法,当然实际上他们内部调用的还是上面这些基本操作。因此Proxy可以在此基础上监听并过滤掉对对象的访问、属性赋值、方法调用等操作。

Proxy的基本使用

js 复制代码
// 创建一个Proxy对象
const proxy = new Proxy(target, handler);
  • target: 必选,被代理的目标对象,包括函数,毕竟在JS中函数也是一个对象
  • handler: 必选,一个处理器对象,用于定义各种代理陷阱行为,也就是上述针对对象的一些基本操作

现在尝试创建一个代理陷阱,在这个示例中通过Proxy 改写get操作实现了对数组的拦截

js 复制代码
const arr = [1, 2, 3]
const proxy = new Proxy(arr, {
    get(target, key) {
        key = Number(key)
        if(key === 2) {
            return 'syx'
        }
        return Reflect.get(target, key)
    },
})

console.log(proxy[2]) // 输出 'syx'

在这个例子中可以很清楚的看到通过Proxy确实是可以改写对象的基本操作,对proxy的a属性进行赋值的时候,不再调用内部的set方法,而是调用我们自定义的set方法(执行打印语句)通过这种方式开发者可以动态的修改对象的各种行为,增强了JS的灵活性和可拓展性。

应用场景

基于Proxy的灵活性,那么在日常开发中Proxy的应用场景有哪些呢?

  • 数据验证与拦截

实际上这是一个典型的动态验证,只有在运行代码执行赋值操作时才会抛出错误,能够统一处理所有属性的赋值校验,而无需在代码中的每个赋值点增加相同的逻辑判断。

  • 反应式编程
js 复制代码
const counter = { value: 0 };
const proxyCounter = new Proxy(counter, {
    get(target, prop, receiver) {
        return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
        if (prop === 'value' && value !== target[prop]) {
            console.log('Counter updated from', target[prop], 'to', value);
            // 更新视图或其他副作用
            updateView(value);
        }
        return Reflect.set(target, prop, value, receiver);
     }
});

function updateView(value) {
    // 更新视图的逻辑,例如更新DOM
    console.log('View updated:', value);
}
// 订阅变化
const subscription = () => {
    console.log('Subscription notified');
};
proxyCounter.value = 1; // 触发变化
subscription(); // 手动调用订阅函数通知变化
proxyCounter.value = 2; // 再次触发变化
subscription();

在这个例子中,proxyCounter是一个反应式计数器。每次修改value属性时,set拦截器会检测变化,并调用updateView来更新视图。采用这种方式程序会自动响应数据的变化,和Vue实现双向绑定的原理类似。

Proxy和DefineProperty

我们都知道Vue2升级到Vue3最大的一个特点就是用Proxy代替DefineProperty实现响应式,那他们之间有什么区别呢?Proxy的优势在哪儿呢?

1、Vue2实现响应式

Vue中实现响应式简单来说就是在读取属性和赋值属性的时候,我们要知道它正在读取或者赋值属性,并做一些额外的操作。因此必须要将getset包装成一个函数,在包装的函数中进行一些额外的操作,这就是object.defineProperty所起的作用。

Vue2针对的是某个对象的属性的监听,因此在Vue2中必须深度遍历对象中的每个属性。

js 复制代码
const obj = {

a: 1,

b: 2,

c: {

a: 3,

b:4

}

}

function isObject(obj) {

return typeof obj === 'object' && obj !== null

}

function obverse(obj) {

for(const k in obj) {

let v = obj[k]

if(isObject(obj[k])) {

obverse(obj[k])

}

Object.defineProperty(obj, k, {

get: function () {

console.log(k, `读取了属性${k}`) // 一些额外操作

return v

},

set: function (newValue) {

if(v !== newValue) {

console.log(k, `更改了属性${k}`) // 一些额外操作

v = newValue

}

}

})

}

}

obverse(obj)
obj.c.a
obj.c.a = 'syx'
console.log(obj.c.a)

c 读取了属性c
a 更改了属性a
c 读取了属性c
a 读取了属性a
syx

在create钩子函数之前就已经完成了监听,obverse的流程已经走完了,因此在vue2无法监听某个属性的新增和删除,因为不会触发gettersetter函数了。此外因为要深度遍历所有的属性,会有性能上的损失。

2、Vue3中使用Proxy的优势 在Vue3中使用了Proxy代替DefineProperty,不再针对属性进行监听,而是直接监听整个对象,也就是开头所提到的target。这样也就不需要进行深度遍历,只有读取到嵌套属性的时候才需要使用到递归操作,所以说使用Proxy后Vue3的性能得到了较大提升,所有的改动都通过Proxy创建的代理对象进行改动。

js 复制代码
const obj = {

a: 1,

b: 2,

c: {

a: 3,

b:4

}

}

function _isObject(obj) {

return typeof obj === 'object' && obj !== null

}

function obverse(obj) {

const proxy = new Proxy(obj, {

get(target, key) {

let v = target[key]

if(_isObject(v)) {

obverse(v) // 只有需要访问到嵌套对象的时候才会走递归

}

console.log('读', target[key])

return v

},

set(target, key, value) {

if(target[key] !== value) {

target[key] = value

console.log('写',target[key])

}

return true

},

deleteProperty(target, key) {

if(key in target) {

delete target[key]

console.log('删除', key)

}

return true

}

})

return proxy

}

const proxy = obverse(obj)

proxy.a

proxy.b = 0

proxy.xxxx = '6666'

delete proxy.b

输出如下:

这样通过Proxy实现响应式不仅带来效率的提升(不需要进行深度遍历),而且对也能监听到新增和删除操作

相关推荐
mldong8 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排8 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60619 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角10 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!10 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰10 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi12 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen12 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马13 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu13 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss