ES6的代理模式 | Proxy

 🎬 岸边的风:个人主页

 🔥 个人专栏 :《 VUE 》 《 javaScript 》

⛺️ 生活的理想,就是为了理想的生活 !

在这里插入图片描述

 

目录

正文

语法

Handler 对象常用的方法

handler.get

可撤消的Proxy

Proxy的应用场景

校验器

私有属性

为什么要用Proxy重构

Vue中的defineProperty

对象新增属性为什么不更新

数组变异

对比


proxy修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)

  • 元编程(英语:Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作

一段代码来理解元编程

#!/bin/bash
# metaprogram
echo '#!/bin/bash' >program
for ((I=1; I<=1024; I++)) doecho "echo $I" >>program
done
chmod +x program

这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行echo,如果我们手动来写1024行代码,效率显然低效

元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译

proxy 译为代理,可以理解为在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,生活中也有许许多多的“proxy”, 如代购,中介,因为他们所有的行为都不会直接触达到目标对象

正文

本篇文章作为 Vue3 源码系列前置篇章之一,Proxy 的科普文,跟Vue3并没有绝对关系,但是当你静下心读完了前置篇章,再去读后续的源码系列,感受定会截然不同

前置篇章包含

  • 为什么要学习源码
  • 认识Typescript
  • 理解函数式编程
  • 搞明白Proxy
  • 摸清楚Set、Map、WeakSet、WeakMap

下来将介绍 Proxy 的基本使用

语法

  • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
  • handler 一个通常以函数作为属性的对象,用来定制拦截行为

const proxy = new Proxy(target, handle)

举个例子

const origin = {}
const obj = new Proxy(origin, {get: function (target, propKey, receiver) {return '10'}
});obj.a // 10
obj.b // 10
origin.a // undefined
origin.b // undefined

上方代码我们给一个空对象的get架设了一层代理,所有get操作都会直接返回我们定制的数字10,需要注意的是,代理只会对proxy对象生效,如上方的origin就没有任何效果

Handler 对象常用的方法

方法描述
handler.has()in 操作符的捕捉器。
handler.get()属性读取操作的捕捉器。
handler.set()属性设置操作的捕捉器。
handler.deleteProperty()delete 操作符的捕捉器。
handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply()函数调用操作的捕捉器。
handler.construct()new 操作符的捕捉器

下面挑handler.get重点讲一下,其它方法的使用也都大同小异,不同的是参数的区别

handler.get

get我们在上面例子已经体验过了,现在详细介绍一下,用于代理目标对象的属性读取操作

授受三个参数 get(target, propKey, ?receiver)

  • target 目标对象
  • propkey 属性名
  • receiver Proxy 实例本身

举个例子

const person = {like: "vuejs"
}const obj = new Proxy(person, {get: function(target, propKey) {if (propKey in target) {return target[propKey];} else {throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");}}
})obj.like // vuejs
obj.test // Uncaught ReferenceError: Prop name "test" does not exist.

上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误

注意:

  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同
  • 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined

如下面的例子

const obj = {};
Object.defineProperty(obj, "a", { configurable: false, enumerable: false, value: 10, writable: false 
})const p = new Proxy(obj, {get: function(target, prop) {return 20;}
})p.a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..

可撤消的Proxy

proxy有一个唯一的静态方法,Proxy.revocable(target, handler)

Proxy.revocable()方法可以用来创建一个可撤销的代理对象

该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}

  • proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

该方法常用于完全封闭对目标对象的访问, 如下示例

const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked

Proxy的应用场景

Proxy的应用范围很广,下方列举几个典型的应用场景

校验器

想要一个number,拿回来的却是string,惊不惊喜?意不意外?下面我们使用Proxy实现一个逻辑分离的数据格式验证器

嗯,真香!

const target = {_id: '1024',name:  'vuejs'
}const validators = {  name(val) {return typeof val === 'string';},_id(val) {return typeof val === 'number' && val > 1024;}
}const createValidator = (target, validator) => {return new Proxy(target, {_validator: validator,set(target, propkey, value, proxy){let validator = this._validator[propkey](value)if(validator){return Reflect.set(target, propkey, value, proxy)}else {throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`)}}})
}const proxy = createValidator(target, validators)proxy.name = 'vue-js.com' // vue-js.com
proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type.
proxy._id = 1025 // 1025
proxy._id = 22  // Uncaught Error: Cannot set _id to 22. Invalid type 

私有属性

在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下面使用Proxy轻松实现私有属性拦截

const target = {_id: '1024',name:  'vuejs'
}const proxy = new Proxy(target, {get(target, propkey, proxy){if(propkey[0] === '_'){throw Error(`${propkey} is restricted`)}return Reflect.get(target, propkey, proxy)},set(target, propkey, value, proxy){if(propkey[0] === '_'){throw Error(`${propkey} is restricted`)}return Reflect.set(target, propkey, value, proxy)}
})proxy.name // vuejs
proxy._id // Uncaught Error: _id is restricted
proxy._id = '1025' // Uncaught Error: _id is restricted

Proxy 使用场景还有很多很多,不再一一列举,如果你需要在某一个动作的生命周期内做一些特定的处理,那么Proxy 都是适合的

为什么要用Proxy重构

在 Proxy 之前,JavaScript 中就提供过 Object.defineProperty,允许对对象的 getter/setter 进行拦截

Vue3.0之前的双向绑定是由 defineProperty 实现, 在3.0重构为 Proxy,那么两者的区别究竟在哪里呢?

首先我们再来回顾一下它的定义

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

上面给两个词划了重点,对象上属性,我们可以理解为是针对对象上的某一个属性做处理的

语法

  • obj 要定义属性的对象
  • prop 要定义或修改的属性的名称或 Symbol
  • descriptor 要定义或修改的属性描述符
Object.defineProperty(obj, prop, descriptor)

1

举个例子

const obj = {}
Object.defineProperty(obj, "a", {value : 1,writable : false, // 是否可写 configurable : false, // 是否可配置enumerable : false // 是否可枚举
})// 上面给了三个false, 下面的相关操作就很容易理解了
obj.a = 2 // 无效
delete obj.a // 无效
for(key in obj){console.log(key) // 无效 
}

Vue中的defineProperty

Vue3之前的双向绑定都是通过 defineProperty 的 getter,setter 来实现的,我们先来体验一下 getter,setter

const obj = {};
Object.defineProperty(obj, 'a', {set(val) {console.log(`开始设置新值: ${val}`)},get() { console.log(`开始读取属性`)return 1; },writable : true
})obj.a = 2 // 开始设置新值: 2
obj.a // 开始获取属性 

看到这里,我相信有些同学已经想到了实现双向绑定背后的流程了,其实很简单嘛,只要我们观察到对象属性的变更,再去通知更新视图就好了

我们摘抄一段 Vue 源码中的核心实现验证一下,这一部分一笔代过,不是本文重点

  // 源码位置:https://github.com/vuejs/vue/blob/ef56410a2c/src/core/observer/index.js#L135// ...Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {// ...if (Dep.target) {// 收集依赖dep.depend()}return value},set: function reactiveSetter (newVal) {// ...// 通知视图更新dep.notify()}})

对象新增属性为什么不更新

这个问题用过Vue的同学应该有超过95%比例遇到过

data  () {return  {obj: {a: 1}}
}methods: {update () {this.obj.b = 2}
}

上面的伪代码,当我们执行 update 更新 obj 时,我们预期视图是要随之更新的,实际是并不会

这个其实很好理解,我们先要明白 vue 中 data init 的时机,data init 是在生命周期 created 之前的操作,会对 data 绑定一个观察者 Observer,之后 data 中的字段更新都会通知依赖收集器Dep触发视图更新

然后我们回到 defineProperty 本身,是对对象上的属性做操作,而非对象本身

一句话来说就是,在 Observer data 时,新增属性并不存在,自然就不会有 getter, setter,也就解释了为什么新增视图不更新,解决有很多种,Vue 提供的全局$set 本质也是给新增的属性手动 observer

// 源码位置 https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L201
function set (target: Array<any> | Object, key: any, val: any): any {// ....if (!ob) {target[key] = valreturn val}defineReactive(ob.value, key, val)ob.dep.notify()return val
}

数组变异

由于 JavaScript 的限制,Vue 不能检测以下数组的变动: 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

先来看一段代码

var vm = new Vue({data: {items: ['1', '2', '3']}
})
vm.items[1] = '4' // 视图并未更新

文档已经做出了解释,但并不是defineProperty的锅,而是尤大在设计上对性能的权衡,下面这段代码可以验证

function defineReactive(data, key, val) {Object.defineProperty(data, key, {enumerable: true,configurable: true,get: function defineGet() {console.log(`get key: ${key} val: ${val}`);return val;},set: function defineSet(newVal) {console.log(`set key: ${key} val: ${newVal}`);val = newVal;}})
}function observe(data) {Object.keys(data).forEach(function(key) {defineReactive(data, key, data[key]);})
}let test = [1, 2, 3];observe(test);test[0] = 4 // set key: 0 val: 4

虽然说索引变更不是 defineProperty 的锅,但新增索引的确是 defineProperty 做不到的,所以就有了数组的变异方法

能看到这里,大概也能猜到内部实现了,还是跟$set一样,手动 observer,下面我们验证一下

const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]methodsToPatch.forEach(function (method) {// 缓存原生数组const original = arrayProto[method]// def使用Object.defineProperty重新定义属性def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args) // 调用原生数组的方法const ob = this.__ob__  // ob就是observe实例observe才能响应式let insertedswitch (method) {// push和unshift方法会增加数组的索引,但是新增的索引位需要手动observe的case 'push':case 'unshift':inserted = argsbreak// 同理,splice的第三个参数,为新增的值,也需要手动observecase 'splice':inserted = args.slice(2)break}// 其余的方法都是在原有的索引上更新,初始化的时候已经observe过了if (inserted) ob.observeArray(inserted)// dep通知所有的订阅者触发回调ob.dep.notify()return result})
})

对比

一个优秀的开源框架本身就是一个不断打碎重朔的过程,上面做了些许铺垫,现在我们简要总结一下

  • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化

  • Proxy 能观察的类型比 defineProperty 更丰富

  • Proxy 不兼容IE,也没有 polyfilldefineProperty 能支持到IE9

  • Object.definedProperty 是劫持对象的属性,新增元素需要再次 definedProperty。而 Proxy 劫持的是整个对象,不需要做特殊处理

  • 使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/135229.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【eXtplorer】本地搭建免费在线文件管理器并实现在外远程登录

文章目录 1. 前言2. eXtplorer网站搭建2.1 eXtplorer下载和安装2.2 eXtplorer网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1. 前言 通过互联网传输文件&#xff0c;是互联网最重要的应用之一&#xff0c;无论是…

Java的XWPFTemplate工具类导出word.docx的使用

依赖 <!-- word导出 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.7.3</version></dependency><!-- 上面需要的依赖--><dependency><groupId>org.ap…

【MySQL】基础SQL语句——库的操作

文章目录 一. 创建数据库1.1 基础语句1.2 字符集和校验规则1.3 校验规则对读取数据的影响 二. 查看数据库三. 修改数据库四. 删除数据库及备份4.1 删除4.2 备份和还原 结束语 一. 创建数据库 1.1 基础语句 最简洁的创建数据库的SQL语句是&#xff1a; create database db_nam…

Linux设备驱动模型之platform设备

Linux设备驱动模型之platform设备 上一章节介绍了Linux字符设备驱动&#xff0c;它是比较基础的&#xff0c;让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中&#xff0c;个人认为完全手写一个字符设备驱动的机会比较少&#xff0c;更多的都是基于前人的代码修修…

深入理解Serverless架构:构建无服务器应用的完全指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 Serverless架构是一种现…

AOSP Android 系统源码编译出的framework.jar和android.jar之间的区别

简介 AOSP&#xff08;Android Open Source Project&#xff09;编译出的 android.jar 和 framework.jar 都是 Android 平台开发中的重要组件&#xff0c;但它们有不同的作用和用途&#xff1a; android.jar&#xff1a; 用途&#xff1a;android.jar 包含了 Android API 的定…

远程计算机控制软件

远程控制软件允许您连接和控制位于不同位置的设备&#xff0c;对于 IT 帮助台技术人员来说&#xff0c;这是一个很好的工具&#xff0c;可以通过与用户协作、与他们聊天以及安全地访问他们的文件来轻松排除故障和修复远程设备。使用远程控制软件&#xff0c;距离不再是提供技术…

LVS + Keepalived群集

文章目录 1. Keepalived工具概述1.1 什么是Keepalived1.2 工作原理1.3 Keepailved实现原理1.4 Keepalived体系主要模块及其作用1.5 keepalived的抢占与非抢占模式 2. 脑裂现象 &#xff08;拓展&#xff09;2.1 什么是脑裂2.2 脑裂的产生原因2.3 如何解决脑裂2.4 如何预防脑裂 …

VMware虚拟机如何设置网络

一直没弄明白怎么能让虚拟机正常上网和访问&#xff0c;最近总结一个小经验 要在宿主机访问虚拟机电脑服务器&#xff0c;要设置成nat格式&#xff0c;虚拟机可以上网&#xff0c;宿主机访问虚拟机上的ip即可访问虚拟机里的服务器&#xff0c;也就是这样设置就行。 这时候ip不…

ubunutu20/18/22 编译android 5相关的问题汇总-千里马framework开源代码平板编译过程

hi&#xff0c;粉丝朋友们&#xff1a; 闲鱼50块钱淘到了一个开源平板&#xff0c;注意这个平板是有源码的&#xff0c;可以进行相关的编译修改。哈哈哈&#xff0c;马哥这边就体验了一下50块钱平板是否可以拿来做framework呢&#xff1f; 哈哈&#xff0c;说好就开干了&#x…

Centos nginx配置文档

1、安装nginx: yum install nginx 2、Nginx常用命令 查看版本:nginx -v 启动:nginx -c /etc/nginx/nginx.conf 重新加载配置:nginx -s reload 停止:nginx -s stop 3、Nginx反向代理配置 nginx配置详解 1、Nginx配置图 详情可以查看:http://nginx.org/ru/docs/example…

华为云云服务器云耀L实例评测 | 智能不卡顿:如何实现流畅的业务运行

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Typora偏好设置中图床的配置文件点击打开没有反应

Typora偏好设置中图床的配置文件点击打开没有反应 突然发现Typora偏好设置中图床打开配置文件点击没有反应&#xff0c;如下按钮所示 可能是因为系统不知道用什么软件打开json&#xff0c;直接进入配置文件json目录&#xff0c;一般位置在C:\Users\<your_user_name>\.pi…

Qt 围炉札记

文章目录 一、Qt 调试二、vscode 与 Qt1、安装插件&#xff1a;2、设置中配置插件 一、Qt 调试 【Qt调试技巧】Profile配置使用及一些坑 QT运行时的Debug、Release、Profile选项区别 Qt Creator release版本进行调试 【Qt调试技巧】如何在Release下调试Qt程序&#xff1f; …

辅助驾驶功能开发-功能规范篇(21)-4-XP行泊一体方案功能规范

XPilot Parking 自动泊车系统 • 超级自动泊车辅助(Super AutoParking Assist)、语音控制泊车辅助(Autoparking with Speech) - 产品定义 超级自动泊车辅助是⼀个增强的自动泊车辅助系统。在超级自动泊车辅助系统中,识别车位将会变得实时可见, 并且不可泊入的⻋位也将…

GIS前端-地图事件编程

GIS前端-地图事件编程 图层操作事件地图状态事件交互事件弹出框事件导出PDF 在地图上的一切操作均要采用地图事件机制来实现&#xff0c;即通过鼠标、键盘等交互&#xff0c;触发地图相关事件&#xff0c;进而调用相关功能接口函数实现相应的GIS功能。在具体的实现过程中&#…

利用transform和border 创造简易图标,以适应uniapp中多字体大小情况下的符号问题

html: <text class"icon-check"></text> css: .icon-check {border: 2px solid black;border-left: 0;border-top: 0;height: 12px;width: 6px;transform-origin: center;transform: rotate(45deg);} 实际上就是声明一个带边框的div 将其中相邻的两边去…

驱动开发,IO多路复用(select,poll,epoll三种实现方式的比较)

1.IO多路复用介绍 在使用单进程或单线程情况下&#xff0c;同时处理多个输入输出请求&#xff0c;需要用到IO多路复用&#xff1b;IO多路复用有select/poll/epoll三种实现方式&#xff1b;由于不需要创建新的进程和线程&#xff0c;减少了系统资源的开销&#xff0c;减少了上下…

1. 快速体验 VSCode 和 CMake 创建 C/C++项目

1. 快速体验 VSCode 和 CMake 创建 C/C项目 本章的全部代码和markdown文件地址: CMake_Tutorial&#xff0c;欢迎互相交流. 此次介绍的内容都是针对于 Linux 操作系统上的开发过程. 1.1 安装开发工具 VSCode: 自行下载安装, 然后安装插件 Cmake:在 Ubuntu 系统上, 可以采用 ap…

Ae 效果:CC Hair

模拟/CC Hair Simulation/CC Hair CC Hair&#xff08;CC 毛发&#xff09;可以在源图像上模拟生成毛发、绒线等&#xff0c;并可调整它们的长度、方向、重量等属性&#xff0c;从而创建出非常独特的效果。 CC Hair 本质上是基于 Alpha 通道来生成毛发&#xff0c;无毛发处将变…