Vue3.0 开始用 Proxy 代替 Object.defineProperty了,这篇文章结合实例教你如何使用Proxy

本篇文章同时收录【前端知识点】中,链接直达

阅读本文您将收获

  • JavaScript 中的 Proxy 是什么?能干什么?
  • Vue3.0 开始为什么用 Proxy 代替 Object.defineProperty

Proxy 是什么

解释参考MDN,链接直达

名词解释

  • Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)
  • Proxy 用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改

语法

  • const p = new Proxy(target, handler)

    • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
    • handler: 对该代理对象的各种操作行为处理(为空对象的情况下,基本可以理解为是对第一个参数做的一次浅拷贝)
  • 简而言之:target 就是你想要代理的对象;而 handler 是一个函数对象,其中定义了所有你想替 target 代为管理的操作对象,包含了:
    • *handler.has(target, prop): in 操作符的捕捉器,拦截HasProperty操作
    • *handler.get(target, prop): 属性读取操作的捕捉器
    • *handler.set(target, prop, value): 属性设置操作的捕捉器
    • *handler.apply(target, object, args): 函数调用操作的捕捉器,拦截函数的调用、call和apply操作
    • handler.getPrototypeOf(): Object.getPrototypeOf 方法的捕捉器
    • handler.setPrototypeOf(): Object.setPrototypeOf 方法的捕捉器
    • handler.isExtensible(): Object.isExtensible 方法的捕捉器
    • handler.preventExtensions(): Object.preventExtensions 方法的捕捉器
    • handler.getOwnPropertyDescriptor(): Object.getOwnPropertyDescriptor 方法的捕捉器
    • handler.defineProperty(): Object.defineProperty 方法的捕捉器
    • handler.deleteProperty(): delete 操作符的捕捉器
    • handler.ownKeys(): Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
    • handler.construct(): new 操作符的捕捉器
  • 注意:如果一个属性不可配置 || 不可写,则该属性不可被代理,通过 Proxy 访问该属性会报错。
  • * 标记的trap为本文都要涉及到的

Proxy 能干什么?

当你想进行以下操作时proxy模式通常会很有用:

  • 拦截或控制对某个对象的访问
  • 通过隐藏事务或辅助逻辑来减小方法/类的复杂性
  • 防止在未经验证/准备的情况下执行重度依赖资源的操作

一:javascript中真正的私有变量/拦截has...in...操作/给出提示信息或是阻止特定操作

传统方法私有变量可获取可修改

Proxy 设置私有变量
  • 针对私有变量,可以使用一个proxy来截获针对某个属性的请求并作出限制或是直接返回 undefined
  • 还可以使用 has trap 来掩盖这个属性的存在

  • has 方法拦截的是 hasProperty 操作,不是 hasOwnProperty,所以 has...in 方法不判断一个属性是自身属性还是继承的属性

  • 注意: has...in 可以拦截到,for...in 拦截不到

  • 阻止其他人删除属性,想让调用方法的人知道该方法已经被废弃,或是想阻止其他人修改属性

  • 注意: 要是 Proxy 代理起作用,必须针对 Proxy 的实例进行操作,而不是针对目标对象进行操作

二:数据校验(看代码)

  • 利用 Proxy 代理进行简单数据校验

  • 校验逻辑直接加在代理处理函数中过于繁重,我们可以把校验模块直接抽离出来,只需要去处理校验的逻辑,代理层面后续不需要改动

三:利用proxy进行记录对象访问

  • 针对那些重度依赖资源,执行缓慢或是频繁使用的方法或接口,统计它们的使用或是性能

  • 可以记录各种各样的信息而不用修改应用程序的代码或是阻塞代码执行。并且只需要在这些代码的基础上稍事修改就可以记录特性函数的执行性能

  • 以上例子就是一个监听函数执行的代理,可以将其进行扩展为打点函数

  • 这里面 Proxy 的 trap 为什么使用 get 而不是 apply ? 答案

四:普通函数与构造函数的兼容

  • 构造函数调用没有使用new关键字来调用的话,Class对象会直接抛出异常
  • 使用 Proxy 进行封装让构造函数也能够直接进行函数调用

五:深层取值判断(看代码)

  • 需要解决的几个问题

    1. 获取数据进行拦截
    2. xxx.xxx.xxx...无论 undefined 出现在哪里都不能报错
    3. Proxy 的 get() 传入的参数必须是对象
  • 传统方式深层取值繁琐,利用Proxy可以简化不必要代码

  • 但是当 target[prop] 是 undefined 的时候,Proxy get()的入参变成了 undefined,但 Proxy 第一个入参必须为对象

  • 需要对 obj 为 undefined 的时候进行特殊处理,为了能够深层取值,所以使用一个空函数进行设置拦截,利用 apply trap 进行处理

  • 我们理想中的应该是,如果属性为 undefined 就返回 undefined,但仍要支持访问下级属性,而不是抛出错误

  • 顺着这个思路来的话,很明显当属性为 undefined 的时候也需要用 Proxy 进行特殊处理

    所以我们需要一个具有下面特性的 get() 方法

	getData(undefined)() === undefined; // true
getData(undefined).xxx.yyy.zzz(); // undefined
  • 这里完全不需要注意 get(undefined).xxx 是否为正确的值,因为想获取值必须要执行才能拿到
  • 那么只需要对所有 undefined 后面访问的属性都默认为 undefined 就好了,所以我们需要一个代理了 undefined 后的返回对象
  • 同时为了解决无限循环执行的问题,当第一次检测到出现 undefined 的时候,停止执行

六:日志上报

Vue 3.0 的 Proxy & Object.defineProperty

Proxy

  • 劫持方式:代理整个对象,只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性
  • 本质:Proxy 本质上属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念

Object.defineProperty

  • 劫持方式:只能劫持对象的属性,不能直接代理对象
  • 流程:get中进行依赖收集,set数据时通知订阅者更新
  • 存在的问题:虽然 Object.defineProperty 通过为属性设置 getter/setter 能够完成数据的响应式,但是它并不算是实现数据的响应式的完美方案,某些情况下需要对其进行修补或者hack,这也是它的缺陷,主要表现在两个方面:
    • 无法检测到对象属性的新增或删除
    • 不能监听数组的变化

1. Object.defineProperty 无法监听新增加的属性

  • 解决方式:提供方法重新手动Observe,需要监听的话使用 Vue.set() 重新设置添加属性的响应式

2. Object.defineProperty 无法一次性监听对象所有属性,如对象属性的子属性

  • 解决方式: 通过递归调用来实现子属性响应式

3. Object.defineProperty 无法响应数组操作

  • 解决方式:通过遍历和重写Array数组原型方法操作方法实现,但是也只限制在 push/pop/shift/unshift/splice/sort/reverse 这七个方法,其他数组方法及数组的使用则无法检测到,也无法监听数组索引的变化和长度的变更

4. Proxy 拦截方式更多, Object.defineProperty 只有 get 和 set

5. Proxy 性能问题

6. Proxy 兼容性差

  • Vue 3.0 中放弃了对于IE的支持(以为 Vue 3.0 中会对不兼容的浏览器进行向下兼容,但是经过查看资料和源码发现尤大压根没做兼容)
  • 目前并没有一个完整支持 Proxy 所有拦截方法的 Polyfill 方案,有一个 google 编写的 proxy-polyfill 也只支持了 get/set/apply/construct 四种拦截

多说一嘴 Decorator

  • ES7 中实现的 Decorator,相当于设计模式中的装饰器模式。
  • 如果简单地区分 Proxy 和 Decorator 的使用场景,可以概括为:Proxy 的核心作用是控制外界对被代理者内部的访问,Decorator 的核心作用是增强被装饰者的功能。

写在最后

  • 如果你觉得这篇文章对你有益,烦请点赞以及分享给更多需要的人!

快到碗里来!百度校招还有HC!甩简历来!

极速直接内推【字节跳动】&【百度】&【猿辅导】&【京东】

欢迎关注微信公众号【全栈道路】,获取更多科技相关知识及免费书籍。

更多好文

几行代码教你解决微信生成海报及二维码

冷门的HTML - tabindex 的作用

[万字长文]百度和好未来面试经含答案

[前端面试]前端缓存问题看这篇,让面试官爱上你

记一次惨痛的Vue-cli + VueX + SSR经历

[三分钟小文]前端性能优化-HTML、CSS、JS部分

[三分钟小文]前端性能优化-页面加载速度优化

[三分钟小文]前端性能优化-网络传输层优化

Vue3.0 响应式数据原理:ES6 Proxy的更多相关文章

  1. Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理

    1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...

  2. vue2与vue3实现响应式的原理区别和提升

    区别: vue2.x: 实现原理: 对象类型:Object.defineProperty()对属性的读取,修改进行拦截(数据劫持): 数组类型:通过重写更新数组的一系列方法来进行拦截(对数组的变更方法 ...

  3. Vue3.0响应式原理

    Vue3.0的响应式基于Proxy实现.具体代码如下: 1 let targetMap = new WeakMap() 2 let effectStack = [] //存储副作用 3 4 const ...

  4. Vue3.0响应式实现

    基于Proxy // 弱引用映射表 es6 防止对象不能被回收 let toProxy = new WeakMap(); // 原对象: 代理过得对象 let toRaw = new WeakMap( ...

  5. vue2.0与3.0响应式原理机制

    vue2.0响应式原理 - defineProperty 这个原理老生常谈了,就是拦截对象,给对象的属性增加set 和 get方法,因为核心是defineProperty所以还需要对数组的方法进行拦截 ...

  6. Vue 2.0 与 Vue 3.0 响应式原理比较

    Vue 2.0 的响应式是基于Object.defineProperty实现的 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 prop ...

  7. Vue实现双向绑定的原理以及响应式数据

    一.vue中的响应式属性 Vue中的数据实现响应式绑定 1.对象实现响应式: 是在初始化的时候利用definePrototype的定义set和get过滤器,在进行组件模板编译时实现water的监听搜集 ...

  8. 简单对比vue2.x与vue3.x响应式及新功能

    简单对比vue2.x与vue3.x响应式 对响应方式来讲:Vue3.x 将使用Proxy ,取代Vue2.x 版本的 Object.defineProperty. 为何要将Object.defineP ...

  9. vue源码之响应式数据

    分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...

随机推荐

  1. 如何将Nginx注册为系统服务,开机自启动。

    亲测有效! 一般程序员在实际工作中,除了敲代码,很少有机会实际接触操作其它东西,例如服务器环境搭建,项目部署等等,不是领导信任或项目组核心成员,应该是没有机会实际接触的,只能通过网上资料稍微了解一下. ...

  2. Luogu P6280 [USACO20OPEN]Exercise G

    题意 定义一个长度为 \(n\) 的置换的步数为将 \(P=(1,2,\cdots,n)\) 在该置换操作下变回原样的最小次数. 求所有 \(K\) 的和,使得存在一个长度为 \(n\) 的置换使得其 ...

  3. Boxing

    测试自动装箱和自动拆箱,意思是运行的时候编译器帮我们加了两个代码: public class AutoBoxingandUnBoxing { public static void main(Strin ...

  4. GPRS DTU工作的原理与应用场景

    GPRS DTU是属于物联网无线数据终端设备的中一种,它主要是利用公用运营商的GPRS网络(又称G网)来为用户提供无线长距离数据传输的功能.一般都是采用的高性能工业级8/16/32位通信处理器和工业级 ...

  5. Linux 系统编程 学习:06-基于socket的网络编程1:有关概念

    Linux 系统编程 学习:006-基于socket的网络编程1:有关概念 背景 上一讲 进程间通信:System V IPC(2)中,我们介绍了System IPC中关于信号量的概念,以及如何使用. ...

  6. python面向对象(类与对象)

    面向对象思想 关注公众号"轻松学编程"了解更多. 1.面向对象的设计思想 面向对象是基于万物皆对象这个哲学观点. 2.面向对象和面向过程的区别 面向过程 在生活中: 它是一种看待问 ...

  7. 关于appium

    一.特点 1.appium是开源的移动端自动化测试框架: 2.appium可以测试原生的.混合的.以及移动端的web项目: "移动原生应用"是指那些用iOS或者 Android S ...

  8. PHP 教程:Composer 最佳实践

    概述 Composer 是 PHP 应用程序的依赖管理器,最初发布于大约 8 年前,2012 年 3 月. 在 php 中使用 Composer 可以提高代码的可重用性,并使你的项目能够轻松地集成来自 ...

  9. 2020最常见的200+Java面试题汇总(含答案解析)

    前言 2020年快要结束了,很多朋友问题,有没有整理今年的一些面试题,最近抽时间整理了一份Java面试题.或许这份面试题还不足以囊括所有 Java 问题,但有了它,我相信足以应对目前市面上绝大部分的 ...

  10. 题解 P1541 【乌龟棋】

    题目描述 乌龟棋的棋盘是一行\(N\)个格子,每个格子上一个分数(非负整数).棋盘第\(1\)格是唯一的起点,第\(N\)格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点. 乌龟棋中\(M\) ...