前言

4 月 17 日,尤大在微博上宣布 Vue 3.0 beta 版本正式发布。

在尤大发布的《 Vue3 设计过程》文章中提到之所以重构 Vue 一个考量就是JavaScript新的语言特性在主流浏览器中的支持程度,其中最值得一提的就是Proxy,它为框架提供了拦截对于object的操作的能力。Vue 的一项核心能力就是监听用户定义的状态变化并响应式刷新DOM。Vue 2是通过替换状态对象属性的getter和setter来实现这一特性的。改为Proxy后,可以突破Vue当前的限制,比如无法监听新增属性,还能提供更好的性能表现。

Two key considerations led us to the new major version (and rewrite) of Vue: First, the general availability of new JavaScript language features in mainstream browsers. Second, design and architectural issues in the current codebase that had been exposed over time.

作为一名高级前端猿,我们要知其然,更要知其所以然,那就让我们来看一下到底什么是 Proxy?

什么是 Proxy?

Proxy 这个词翻译过来就是“代理”,用在这里表示由它来“代理”某些操作。 Proxy 会在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

先来看下 proxy 的基本语法

const proxy = new Proxy(target, handler)

  • target :您要代理的原始对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler :一个对象,定义将拦截哪些操作以及如何重新定义拦截的操作

我们看一个简单的例子:

const person = {name: 'muyao',age: 27
};const proxyPerson = new Proxy({}, {get: function(target, propKey) {return 35;}
});proxy.name // 35
proxy.age // 35
proxy.sex // 35 不存在的属性同样起作用person.name // muyao 原对象未改变

上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35

注意,Proxy 并没有改变原有对象 而是生成一个新的对象,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxyPerson)进行操作,而不是针对目标对象(上例是 person)进行操作

Proxy 支持的拦截操作一共 13 种:

  • get(target, propKey, receiver):拦截对象属性的读取,比如 proxy.fooproxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如 proxy.foo = vproxy['foo'] = v, 返回一个布尔值。
  • has(target, propKey):拦截 propKey in proxy 的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作,返回一个布尔值。
  • ownKeys(target):拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截 Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

为什么要用 Proxy?

vue2 变更检测

Vue2 中是递归遍历 data 中的所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter,在getter 中做数据依赖收集处理,在 setter 中 监听数据的变化,并通知订阅当前数据的地方。

// 对 data中的数据进行深度遍历,给对象的每个属性添加响应式
Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {// 进行依赖收集dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {// 是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。dependArray(value)}}}return value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}if (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}// 新的值需要重新进行observe,保证数据响应式childOb = !shallow && observe(newVal)// 将数据变化通知所有的观察者dep.notify()}})

但由于 JavaScript 的限制,这种实现有几个问题:

  • 无法检测对象属性的添加或移除,为此我们需要使用 Vue.set 和 Vue.delete 来保证响应系统的运行符合预期
  • 无法监控到数组下标及数组长度的变化,当直接通过数组的下标给数组设置值或者改变数组长度时,不能实时响应
  • 性能问题,当data中数据比较多且层级很深的时候,因为要遍历data中所有的数据并给其设置成响应式的,会导致性能下降

Vue3 改进

Vue3 进行了全新改进,使用 Proxy 代理的作为全新的变更检测,不再使用 Object.defineProperty

在 Vue3 中,可以使用 reactive() 创建一个响应状态

import { reactive } from 'vue'// reactive state
const state = reactive({desc: 'Hello Vue 3!',count: 0
});

我们在源码 vue-next/packages/reactivity/src/reactive.ts 文件中看到了如下的实现:

//reactive f => createReactiveObject()
function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {...// 设置拦截器const handlers = collectionTypes.has(target.constructor)? collectionHandlers: baseHandlers;observed = new Proxy(target, handlers);...return observed;
}

下面我们看下 state 经过处理后的情况

可以看到被代理的目标对象 state 设置了 get()、set()、deleteProperty()、has()、ownKeys()这 5 个 handler,一起来看下它们都做了什么

get()

get() 会自动读取响应数据,并进行 track 调用

function createGetter(isReadonly = false, shallow = false) {return function get(target, key, receiver) {...// 恢复默认行为const res = Reflect.get(target, key, receiver)...// 调用 track!isReadonly && track(target, TrackOpTypes.GET, key)return isObject(res)? isReadonly? // need to lazy access readonly and reactive here to avoid// circular dependencyreadonly(res): reactive(res): res
}

set()

目标对象上不存在的属性设置值时,进行 “添加” 操作,并且会触发 trigger() 来通知响应系统的更新。解决了 Vue 2.x 中无法检测到对象属性的添加的问题

function createSetter(shallow = false) {return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {...const hadKey = hasOwn(target, key)// 恢复默认行为const result = Reflect.set(target, key, value, receiver)// 如果目标对象在原型链上,不要 triggerif (target === toRaw(receiver)) {// 如果设置的属性不在目标对象上 就进行 Add 这就解决了 Vue 2.x 中无法检测到对象属性的添加或删除的问题if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}
}

deleteProperty()

关联 delete 操作,当目标对象上的属性被删除时,会触发 trigger() 来通知响应系统的更新。这也解决了 Vue 2.x 中无法检测到对象属性的删除的问题

function deleteProperty(target, key) {const hadKey = hasOwn(target, key)const oldValue = (target as any)[key]const result = Reflect.deleteProperty(target, key)// 存在属性删除时触发 triggerif (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result
}

has() 和 ownKeys()

这两个 handler 并没有修改默认行为,但是它们都调用 track() 函数,回顾上文可以知道has() 影响 in 操作的,ownKeys() 影响 for...in 及循环

function has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key)track(target, TrackOpTypes.HAS, key)return result
}function ownKeys(target: object): (string | number | symbol)[] {track(target, TrackOpTypes.ITERATE, ITERATE_KEY)return Reflect.ownKeys(target)
}

通过上面的分析,我们可以看到,Vue3 借助 Proxy 的几个 Handler 拦截操作,收集依赖,实现了响应系统核心。

Proxy 还可以做什么?

我们已经看到了 Proxy 在 Vue3 中的应用场景,其实在使用了Proxy后,对象的行为基本上都是可控的,所以我们能拿来做一些之前实现起来比较复杂的事情。

实现访问日志

let api = {getUser: function(userId) {/* ... */},setUser: function(userId, config) {/* ... */}
};
// 打日志
function log(timestamp, method) {console.log(`${timestamp} - Logging ${method} request.`);
}
api = new Proxy(api, {get: function(target, key, proxy) {var value = target[key];return function(...arguments) {log(new Date(), key); // 打日志return Reflect.apply(value, target, arguments);};}
});
api.getUsers();

校验模块

let numObj = { count: 0, amount: 1234, total: 14 };
numObj = new Proxy(numObj, {set(target, key, value, proxy) {if (typeof value !== 'number') {throw Error('Properties in numObj can only be numbers');}return Reflect.set(target, key, value, proxy);}
});// 抛出错误,因为 "foo" 不是数值
numObj.count = 'foo';
// 赋值成功
numObj.count = 333;

可以看到 Proxy 可以有很多有趣的应用,大家快快去探索吧!


欢迎关注公众号:前端琐话(qianduansuohua)

proxy connect abort处理方法_Vue 3.0 初探 - Proxy相关推荐

  1. proxy connect abort处理方法_Java代理设计模式(Proxy)的几种具体实现

    Proxy是一种结构设计模型,主要解决对象直接访问带来的问题,代理又分为静态代理和动态代理(JDK代理.CGLIB代理. 静态代理:又程序创建的代理类,或者特定的工具类,在平时开发中经常用到这种代理模 ...

  2. es6 --- Proxy实例的get方法

    写一个拦截函数,访问目标对象不存在属性时,会抛出不存在该属性的错误 如果存在该属性时,就返回其值. var person = {name: "张三" };var proxy = n ...

  3. Proxy CONNECT aborted

    当使用GIT clone下载外网项目时,报了该错误. 前情: 为了下载外网项目,提前为终端配置了代理: export http_proxy=http://127.0.0.1:1087 export h ...

  4. DATA abort定位方法

     这是我一直收藏的一篇文章,出处已经无法知道. 根据自己实践增加了部分说明,现在分享出来. 该方法只能定位 显性 泄漏,定位到的C语句一定产生泄漏了,但可能这个位置是 " 理论上 &qu ...

  5. MariaDB: ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111 Connection refused)

    MariaDB : ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111 "Connection ref ...

  6. mysqldump: Got error: 2003: Can't connect to MySQL server on '127.0.0.1' (10060)

    今天在用批处理进行MySQL自动备份的过程中遇到一个问题,错误提示:mysqldump: Got error: 2003: Can't connect to mysql server on '127. ...

  7. Servlet和HTTP请求协议-学习笔记01【Servlet_快速入门-生命周期方法、Servlet_3.0注解配置、IDEA与tomcat相关配置】

    Java后端 学习路线 笔记汇总表[黑马程序员] Servlet和HTTP请求协议-学习笔记01[Servlet_快速入门-生命周期方法.Servlet_3.0注解配置.IDEA与tomcat相关配置 ...

  8. 默认构造函数的作用(“A”方法没有采用“0”个参数的重载

    构造函数主要用来初始化对象.它又分为静态(static)和实例(instance)构造函数两种类别.大家应该都了解如何来写类的构造函数,这里只说下默认构造函数的作用,以及在类中保留默认构造函数的重要性 ...

  9. MySql error 2003 Can't connect to MySQL server on 'localhost' (0)

    事情是这样的,今天群里一个小伙伴使用MySql的时候出现了error 2003 Can't connect to MySQL server on 'localhost' (0).见下图. 我们来分析, ...

最新文章

  1. 2022-2028年中国电动牙刷行业深度调研及投资前景预测报告(全卷)
  2. 关于ES6中Promise的应用-顺序合并Promise,并将返回结果以数组的形式输出
  3. 数据结构——图:极大小连通子图、图的存储结构、图的遍历
  4. webpack加载postcss,以及autoprefixer的loader
  5. 开发遇到的问题---【spring-security权限控制框架】
  6. 领域驱动设计学习之路—DDD的原则与实践
  7. html标签属性%= %,HTML标签属性集合
  8. 吞吐量-Corda的故事
  9. MicroProfile OpenAPI上的Swagger UI
  10. LaTeX的表格标题位置
  11. Eclipse 答疑:代码版权?如何更改 Eclipse 中注释块的 @author 版权信息?
  12. windows录屏_电脑自带录屏软件怎么打开?详细操作教程
  13. iptables二之防火墙SNAT源地址转换,MASQUERADE地址伪装之DNAT目标地址转换讲解和实验演示
  14. VUE优秀的组件库总结
  15. PS人像修图技巧——高低频磨皮
  16. 多线程输出奇数和偶数
  17. android 农历工具类,公历农历互相转换的Java日历工具类
  18. 安全模式下的自动启动
  19. scala中case class与一般的class的区别
  20. 「博文视点」专访黄哲铿/ Mr.K:未来三年,如何努力?如何赚钱?如何发展?...

热门文章

  1. GridView 梆定一个实体类
  2. MUI框架之输入框Input
  3. maven 之dependencyManagement 和 pluginManagement
  4. PHP经典算法 (转载)
  5. MySQL加索引避免锁表:避开事务 lock_wait_timeout 副本
  6. JQUERY设置或返回属性值attr
  7. 主流mes厂商_MES为什么可以成为企业核心
  8. 写给后端程序员的HTTP缓存原理介绍
  9. Java 改变cmd颜色_9 个小技巧让你的 if else 看起来更优雅!
  10. 7nfs客户端没权限_Ant design pro v4-服务器菜单和路由权限控制