语法:

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

例:

const origon={}const obj=new Proxy(origon,{get:function(target,propkey,receiver){return "10"}
})
console.log(obj.b);//10
console.log(obj.a);//10
console.log(origon.a);//undefined
console.log(origon.a);//undefined

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

Handler 对象常用的方法

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

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

proxy对象生效,如上方的origin就没有任何效果

handler.get

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

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

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

例:

const preson = {like: 'vue.js'
}const obj = new Proxy(preson, {get: function (target, propkey) {if (propkey in target) {return target[propkey]} else {throw new ReferenceError('错误')}}
})
console.log(obj.like);// vuejs
console.log(obj.text);//错误

注意:

  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同
  • 如果要访问的目标属性没有配置访问方法,即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: '123',name: '张三'
}const proxy = new Proxy(target, {get(target, key, proxy) {if (key[0] === "_") {throw Error('我是私有属性')}return Reflect.get(target, key, proxy)},set(target, key, val, proxy) {if (key[0] === "_") {throw Error('我是私有属性')}return Reflect.set(target, key, val, proxy)}
})console.log(proxy.name);//张三
console.log(proxy._id);//Error: 我是私有属性
console.log(proxy.name = '李四');//李四
console.log(proxy._id = 456);//Error: 我是私有属性

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)

举个例子

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

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 的实例才可以触发拦截

Vue3源码-Proxy相关推荐

  1. 推荐 7 个 Vue2、Vue3 源码解密分析的开源项目

    大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 1. 为什么要学习源码 ? 阅读优秀的代码的目的是让我们能够写出优秀的代码. 不给自己设限,不要让你周围人的技术上限成为你的上限.其 ...

  2. 学习尤雨溪写的 Vue3 源码中的简单工具函数

    大家好,我是若川.最近组织了源码共读活动.每周读 200 行左右的源码.很多第一次读源码的小伙伴都感觉很有收获,感兴趣可以加我微信ruochuan12,拉你进群学习. 初学者也能看懂的 Vue3 源码 ...

  3. 初学者也能看懂的 Vue3 源码中那些实用的基础工具函数

    1. 前言 大家好,我是若川.最近组织了源码共读活动.每周读 200 行左右的源码.很多第一次读源码的小伙伴都感觉很有收获,感兴趣可以加我微信ruochuan12,拉你进群学习. 写相对很难的源码,耗 ...

  4. Vue3源码解析01--Vue3初探

    Vue3 源码解析 01 - Vue3 浅谈 前言 最近几个月一直忙于公司搬砖,导致都没有时间学习了.正好这段时间慢慢恢复了正常工作的作息,赶紧学习一下最新发布的 Vue3 框架. 为什么有 Vue3 ...

  5. vue3源码分析--真的有必要掌握框架的细枝末节吗?

    古人云:工欲善其事必先利其器,磨刀不误砍柴工.但是砍柴的人需要知道怎么制作刀吗? 注意:本文先分析要不要学源码,然后分析要不要掌握源码的每一个细枝末节(深究技术)!!! 为什么要学源码 为了面试被迫学 ...

  6. Vue3源码分析之打包原理

    Vue3源码分析之打包原理 如果之前你已经看过我的<Vue3源码分析之入门>,那么你可以直接阅读此篇文章 Vue3源码分析之入门 一.配置环境 1. 全局安装yarn Monorepo 管 ...

  7. Vue3源码解析之入门

    Vue3源码分析之入门 本文主要是针对想自学Vue3之类的框架源码的,却不知道如何上手的小伙伴们~ Vue3源码GitHub地址 Vue3源码克隆路径 :git@github.com:vuejs/co ...

  8. vue3源码分析——看看complier是怎么来解析的

    引言 <<往期回顾>> vue3源码分析--手写diff算法 vue3源码分析--实现组件更新 vue3源码分析--解密nextTick的实现 想知道vue3-complier ...

  9. vue3源码分析——实现slots

    引言 <<往期回顾>> vue3源码分析--rollup打包monorepo vue3源码分析--实现组件的挂载流程 vue3源码分析--实现props,emit,事件处理等 ...

  10. 【Vue3源码学习】响应式源码解析:reactive、effect、ref

    源码版本 Vue3.2.24 废话不多说,直接开始!!! reactive响应式 源码地址:packages/reactivity/reactive.ts 先看一下在 Vue3 中定义的几个用来标记目 ...

最新文章

  1. 我对于js注入的理解
  2. 科技公司最常用的50款开源工具,提升你的逼格~
  3. 数据包是如何在网络中传输的
  4. git clone 几种可选参数的使用与区别
  5. Python的嵌套函数使用和闭包
  6. centos7 php安装
  7. opengl开启垂直同步_东风悦达起亚ALL NEW K5正式定名凯酷,预售同步开启
  8. 收缩Mysql的ibdata1文件大小方法
  9. java编写WordCound的Spark程序,Scala编写wordCound程序
  10. 小师妹学JVM之:JDK14中JVM的性能优化
  11. 风口更需冷静 智能家居如何跨越鸿沟?
  12. 移动Web开发之流式布局笔记
  13. php解析html类库simple_html_dom(2)
  14. lisp型材库_STMX 1.3.2 发布,高性能的 Common Lisp 库
  15. YOLOV5目标检测-后处理NMS(非极大值抑制)
  16. 微信小程序用什么工具开发(微信小程序开发工具介绍)
  17. matlab绘制折线图
  18. 几个关于矩阵的定义 奇异值分解 谱分解
  19. 采集全国疫情数据(Python)
  20. 视频云:云巨头们的“新格斗场”

热门文章

  1. DAY16-T1342面试题 05.08 -2022-01-31-非自己作答
  2. win10装sql2000卡在选择配置_如何在WIN10/SERVER2016上安装MSSQL2000数据库和新中大老版本软件...
  3. java jui_急求用带jui界面写的java聊天程序!!!
  4. Java多线程学习(吐血超详细总结)转自博主林炳文Evankaka
  5. Java多线程学习(吐血超详细总结)
  6. HTML从入门到入土 - CSS基础
  7. Java输出各种乘法口诀表
  8. linux——20线程池
  9. html 控件enabled,用 Enabled 和 disabled 属性禁用 HTML 控件后,取值结果
  10. 用IF公式实现向上取整的结果