Vue3源码-Proxy
语法:
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.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 要定义或修改的属性描述符
|
举个例子
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,也没有polyfill
,defineProperty
能支持到IE9Object.definedProperty
是劫持对象的属性,新增元素需要再次definedProperty
。而Proxy
劫持的是整个对象,不需要做特殊处理- 使用
defineProperty
时,我们修改原来的obj
对象就可以触发拦截,而使用proxy
,就必须修改代理对象,即Proxy
的实例才可以触发拦截
Vue3源码-Proxy相关推荐
- 推荐 7 个 Vue2、Vue3 源码解密分析的开源项目
大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 1. 为什么要学习源码 ? 阅读优秀的代码的目的是让我们能够写出优秀的代码. 不给自己设限,不要让你周围人的技术上限成为你的上限.其 ...
- 学习尤雨溪写的 Vue3 源码中的简单工具函数
大家好,我是若川.最近组织了源码共读活动.每周读 200 行左右的源码.很多第一次读源码的小伙伴都感觉很有收获,感兴趣可以加我微信ruochuan12,拉你进群学习. 初学者也能看懂的 Vue3 源码 ...
- 初学者也能看懂的 Vue3 源码中那些实用的基础工具函数
1. 前言 大家好,我是若川.最近组织了源码共读活动.每周读 200 行左右的源码.很多第一次读源码的小伙伴都感觉很有收获,感兴趣可以加我微信ruochuan12,拉你进群学习. 写相对很难的源码,耗 ...
- Vue3源码解析01--Vue3初探
Vue3 源码解析 01 - Vue3 浅谈 前言 最近几个月一直忙于公司搬砖,导致都没有时间学习了.正好这段时间慢慢恢复了正常工作的作息,赶紧学习一下最新发布的 Vue3 框架. 为什么有 Vue3 ...
- vue3源码分析--真的有必要掌握框架的细枝末节吗?
古人云:工欲善其事必先利其器,磨刀不误砍柴工.但是砍柴的人需要知道怎么制作刀吗? 注意:本文先分析要不要学源码,然后分析要不要掌握源码的每一个细枝末节(深究技术)!!! 为什么要学源码 为了面试被迫学 ...
- Vue3源码分析之打包原理
Vue3源码分析之打包原理 如果之前你已经看过我的<Vue3源码分析之入门>,那么你可以直接阅读此篇文章 Vue3源码分析之入门 一.配置环境 1. 全局安装yarn Monorepo 管 ...
- Vue3源码解析之入门
Vue3源码分析之入门 本文主要是针对想自学Vue3之类的框架源码的,却不知道如何上手的小伙伴们~ Vue3源码GitHub地址 Vue3源码克隆路径 :git@github.com:vuejs/co ...
- vue3源码分析——看看complier是怎么来解析的
引言 <<往期回顾>> vue3源码分析--手写diff算法 vue3源码分析--实现组件更新 vue3源码分析--解密nextTick的实现 想知道vue3-complier ...
- vue3源码分析——实现slots
引言 <<往期回顾>> vue3源码分析--rollup打包monorepo vue3源码分析--实现组件的挂载流程 vue3源码分析--实现props,emit,事件处理等 ...
- 【Vue3源码学习】响应式源码解析:reactive、effect、ref
源码版本 Vue3.2.24 废话不多说,直接开始!!! reactive响应式 源码地址:packages/reactivity/reactive.ts 先看一下在 Vue3 中定义的几个用来标记目 ...
最新文章
- 我对于js注入的理解
- 科技公司最常用的50款开源工具,提升你的逼格~
- 数据包是如何在网络中传输的
- git clone 几种可选参数的使用与区别
- Python的嵌套函数使用和闭包
- centos7 php安装
- opengl开启垂直同步_东风悦达起亚ALL NEW K5正式定名凯酷,预售同步开启
- 收缩Mysql的ibdata1文件大小方法
- java编写WordCound的Spark程序,Scala编写wordCound程序
- 小师妹学JVM之:JDK14中JVM的性能优化
- 风口更需冷静 智能家居如何跨越鸿沟?
- 移动Web开发之流式布局笔记
- php解析html类库simple_html_dom(2)
- lisp型材库_STMX 1.3.2 发布,高性能的 Common Lisp 库
- YOLOV5目标检测-后处理NMS(非极大值抑制)
- 微信小程序用什么工具开发(微信小程序开发工具介绍)
- matlab绘制折线图
- 几个关于矩阵的定义 奇异值分解 谱分解
- 采集全国疫情数据(Python)
- 视频云:云巨头们的“新格斗场”
热门文章
- DAY16-T1342面试题 05.08 -2022-01-31-非自己作答
- win10装sql2000卡在选择配置_如何在WIN10/SERVER2016上安装MSSQL2000数据库和新中大老版本软件...
- java jui_急求用带jui界面写的java聊天程序!!!
- Java多线程学习(吐血超详细总结)转自博主林炳文Evankaka
- Java多线程学习(吐血超详细总结)
- HTML从入门到入土 - CSS基础
- Java输出各种乘法口诀表
- linux——20线程池
- html 控件enabled,用 Enabled 和 disabled 属性禁用 HTML 控件后,取值结果
- 用IF公式实现向上取整的结果