11
2- ### Proxy
2+ #ES6的代理模式 | Proxy
33
44> 定义: 用于定义基本操作的自定义行为
55
6- 由于 ` proxy ` 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(` meta ` ` programming ` )
6+ ` proxy ` 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(` meta ` ` programming ` )
77
8- ** 元编程(英语:Metaprogramming** ,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
8+ - ** 元编程(英语:Metaprogramming** ,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
99
1010一段代码来理解元编程
1111``` bash
@@ -17,18 +17,32 @@ for ((I=1; I<=1024; I++)) do
1717done
1818chmod +x program
1919```
20- 这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行` echo ` ,如果我们手动来写1024行代码,显然就很低效
20+ 这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行` echo ` ,如果我们手动来写1024行代码,效率显然低效
2121
22- 元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译
22+ ** 元编程优点** :与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译
2323
24- ` proxy ` 音译为代理,在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,通俗点可以理解为生活的代购,中介服务,所有的行为都不会直接触达目标对象
24+ ` proxy ` 译为代理,可以理解为在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,生活中也有许许多多的"proxy", 如代购,中介,因为他们所有的行为都不会直接触达到目标对象
25+ 26+ ## 正文
27+ 28+ 本篇文章作为 ` Vue3 ` 源码系列前置篇章之一,` Proxy ` 的科普文,跟` Vue3 ` 并没有绝对关系,但是当你静下心读完了前置篇章,再去读后续的源码系列,感受定会截然不同
29+ 30+ 前置篇章包含
31+ - 为什么要学习源码
32+ - 认识Typescript
33+ - 理解函数式编程
34+ - 搞明白Proxy
35+ - 摸清楚Set、Map、WeakSet、WeakMap
36+ 37+ 下来将介绍 ` Proxy ` 的基本使用
38+ 39+ ## 语法
40+ 41+ - target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
42+ - handler 一个通常以函数作为属性的对象,用来定制拦截行为
2543
26- ### 语法
2744``` js
28- /
29- * target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
30- * handler 一个通常以函数作为属性的对象,用来定制拦截行为
31- */
45+ 3246const proxy = new Proxy (target, handle)
3347```
3448
@@ -48,7 +62,7 @@ origin.b // undefined
4862```
4963上方代码我们给一个空对象的get架设了一层代理,所有` get ` 操作都会直接返回我们定制的数字10,需要注意的是,代理只会对` proxy ` 对象生效,如上方的` origin ` 就没有任何效果
5064
51- ### Handler 对象常用的方法
65+ ## Handler 对象常用的方法
5266
5367| 方法 | 描述 |
5468| ---- | ---- |
@@ -63,14 +77,14 @@ origin.b // undefined
6377
6478下面挑` handler.get ` 重点讲一下,其它方法的使用也都大同小异,不同的是参数的区别
6579
66- #### handler.get
80+ ### handler.get
6781
68- get我们在上面例子已经体验过了,这里再详细介绍一下,方法用于代理目标对象的属性读取操作
82+ ` get ` 我们在上面例子已经体验过了,现在详细介绍一下,用于代理目标对象的属性读取操作
6983
7084授受三个参数 ` get(target, propKey, ?receiver) `
7185- target 目标对象
7286- propkey 属性名
73- - receiver Proxy实例本身
87+ - receiver Proxy 实例本身
7488
7589** 举个例子**
7690``` js
@@ -116,7 +130,7 @@ const p = new Proxy(obj, {
116130p .a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..
117131```
118132
119- ### 可撤消的Proxy
133+ ## 可撤消的Proxy
120134
121135` proxy ` 有一个唯一的静态方法,` Proxy.revocable(target, handler) `
122136
@@ -138,12 +152,15 @@ revoke() // 取值完成对proxy进行封闭,撤消代理
138152proxy .name // TypeError: Revoked
139153```
140154
141- ### Proxy的应用场景
155+ ## Proxy的应用场景
142156
143157` Proxy ` 的应用范围很广,下方列举几个典型的应用场景
144- - ** 校验器**
145158
146- 想要一个` number ` ,拿回来的却是` string ` ,头大不大?下面我们使用` Proxy ` 实现一个逻辑分离的数据格式验证器,嗯,真香!
159+ ### ** 校验器**
160+ 161+ 想要一个` number ` ,拿回来的却是` string ` ,惊不惊喜?意不意外?下面我们使用` Proxy ` 实现一个逻辑分离的数据格式验证器
162+ 163+ 嗯,真香!
147164
148165``` js
149166const target = {
@@ -183,11 +200,9 @@ proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type
183200
184201```
185202
203+ ### 私有属性
186204
187- 188- - ** 防止对象的内部属性(私有属性)被外部读写**
189- 190- 在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下方示例使用Proxy轻松实现私有属性拦截
205+ 在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下面使用Proxy轻松实现私有属性拦截
191206
192207``` js
193208const target = {
@@ -215,24 +230,238 @@ proxy._id // Uncaught Error: _id is restricted
215230proxy ._id = ' 1025' // Uncaught Error: _id is restricted
216231```
217232
218- ` Proxy ` 的使用场景还有很多很多,这里也不再一一列举,只要你需要在某一个动作的生命周期内做一些特定的处理,那么` Proxy ` 都是适合你的
233+ ` Proxy ` 使用场景还有很多很多,不再一一列举,如果你需要在某一个动作的生命周期内做一些特定的处理,那么` Proxy ` 都是适合的
234+ 235+ ## 为什么要用Proxy重构
236+ 237+ 在 ` Proxy ` 之前,` JavaScript ` 中就提供过 ` Object.defineProperty ` ,允许对对象的 ` getter/setter ` 进行拦截
238+ 239+ Vue3.0之前的双向绑定是由 ` defineProperty ` 实现, 在3.0重构为 ` Proxy ` ,那么两者的区别究竟在哪里呢?
240+ 241+ 首先我们再来回顾一下它的定义
242+ 243+ > Object.defineProperty() 方法会直接在一个** 对象上** 定义一个** 新属性** ,或者修改一个对象的现有属性,并返回此对象
244+
245+ 上面给两个词划了重点,** 对象上** ,** 属性** ,我们可以理解为是针对对象上的某一个属性做处理的
246+ 247+ ** 语法**
248+ 249+ - obj 要定义属性的对象
250+ - prop 要定义或修改的属性的名称或 Symbol
251+ - descriptor 要定义或修改的属性描述符
252+ 253+ ``` js
254+ Object .defineProperty (obj, prop, descriptor)
255+ ```
256+ 257+ 举个例子
258+ ``` js
259+ const obj = {}
260+ Object .defineProperty (obj, " a" , {
261+ value : 1 ,
262+ writable : false , // 是否可写
263+ configurable : false , // 是否可配置
264+ enumerable : false // 是否可枚举
265+ })
266+ 267+ // 上面给了三个false, 下面的相关操作就很容易理解了
268+ obj .a = 2 // 无效
269+ delete obj .a // 无效
270+ for (key in obj){
271+ console .log (key) // 无效
272+ }
273+ ```
274+ 275+ ### ** Vue中的defineProperty**
276+ 277+ Vue3之前的双向绑定都是通过 ` defineProperty ` 的 ` getter,setter ` 来实现的,我们先来体验一下 ` getter,setter `
278+ ``` js
279+ const obj = {};
280+ Object .defineProperty (obj, ' a' , {
281+ set (val ) {
282+ console .log (` 开始设置新值: ${ val} ` )
283+ },
284+ get () {
285+ console .log (` 开始读取属性` )
286+ return 1 ;
287+ },
288+ writable : true
289+ })
290+ 291+ obj .a = 2 // 开始设置新值: 2
292+ obj .a // 开始获取属性
293+ ```
294+ 295+ 看到这里,我相信有些同学已经想到了实现双向绑定背后的流程了,其实很简单嘛,只要我们观察到对象属性的变更,再去通知更新视图就好了
296+ 297+ 我们摘抄一段 Vue 源码中的核心实现验证一下,这一部分一笔代过,不是本文重点
298+ ``` js
299+ // 源码位置:https://github.com/vuejs/vue/blob/ef56410a2c/src/core/observer/index.js#L135
300+ // ...
301+ Object .defineProperty (obj, key, {
302+ enumerable: true ,
303+ configurable: true ,
304+ get : function reactiveGetter () {
305+ // ...
306+ if (Dep .target ) {
307+ // 收集依赖
308+ dep .depend ()
309+ }
310+ return value
311+ },
312+ set : function reactiveSetter (newVal ) {
313+ // ...
314+ // 通知视图更新
315+ dep .notify ()
316+ }
317+ })
318+ ```
319+ 320+ ### ** 对象新增属性为什么不更新**
321+ 322+ 这个问题用过Vue的同学应该有超过95%比例遇到过
323+ ``` js
324+ data () {
325+ return {
326+ obj: {
327+ a: 1
328+ }
329+ }
330+ }
331+ 332+ methods: {
333+ update () {
334+ this .obj .b = 2
335+ }
336+ }
337+ ```
338+ 339+ 上面的伪代码,当我们执行 ` update ` 更新 ` obj ` 时,我们预期视图是要随之更新的,实际是并不会
340+ 341+ 这个其实很好理解,我们先要明白 ` vue ` 中 ` data init ` 的时机,` data init ` 是在生命周期 ` created ` 之前的操作,会对 ` data ` 绑定一个观察者 ` Observer ` ,之后 ` data ` 中的字段更新都会通知依赖收集器` Dep ` 触发视图更新
342+ 343+ 然后我们回到 ` defineProperty ` 本身,是对** 对象上的属性** 做操作,而非对象本身
344+ 345+ 一句话来说就是,在 ` Observer data ` 时,新增属性并不存在,自然就不会有 ` getter, setter ` ,也就解释了为什么新增视图不更新,解决有很多种,` Vue ` 提供的全局` $set ` 本质也是给新增的属性手动 ` observer `
346+ 347+ ``` js
348+ // 源码位置 https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L201
349+ function set (target : Array < any > | Object , key : any , val : any ): any {
350+ // ....
351+ if (! ob) {
352+ target[key] = val
353+ return val
354+ }
355+ defineReactive (ob .value , key, val)
356+ ob .dep .notify ()
357+ return val
358+ }
359+ ```
360+ 361+ ### 数组变异
362+ 363+ > 由于 JavaScript 的限制,Vue 不能检测以下数组的变动: 当你利用索引直接设置一个数组项时,例如:vm.items[ indexOfItem] = newValue
364+
365+ 先来看一段代码
366+ 367+ ``` js
368+ var vm = new Vue ({
369+ data: {
370+ items: [' 1' , ' 2' , ' 3' ]
371+ }
372+ })
373+ vm .items [1 ] = ' 4' // 视图并未更新
374+ ```
375+ 文档已经做出了解释,但并不是` defineProperty ` 的锅,而是尤大在设计上对性能的权衡,下面这段代码可以验证
376+ ``` js
377+ function defineReactive (data , key , val ) {
378+ Object .defineProperty (data, key, {
379+ enumerable: true ,
380+ configurable: true ,
381+ get : function defineGet () {
382+ console .log (` get key: ${ key} val: ${ val} ` );
383+ return val;
384+ },
385+ set : function defineSet (newVal ) {
386+ console .log (` set key: ${ key} val: ${ newVal} ` );
387+ val = newVal;
388+ }
389+ })
390+ }
391+ 392+ function observe (data ) {
393+ Object .keys (data).forEach (function (key ) {
394+ defineReactive (data, key, data[key]);
395+ })
396+ }
397+ 398+ let test = [1 , 2 , 3 ];
399+ 400+ observe (test);
401+ 402+ test[0 ] = 4 // set key: 0 val: 4
403+ ```
404+ 405+ 虽然说索引变更不是 ` defineProperty ` 的锅,但新增索引的确是 ` defineProperty ` 做不到的,所以就有了数组的变异方法
406+ 407+ 能看到这里,大概也能猜到内部实现了,还是跟` $set ` 一样,手动 ` observer ` ,下面我们验证一下
408+ 409+ ``` js
410+ const methodsToPatch = [
411+ ' push' ,
412+ ' pop' ,
413+ ' shift' ,
414+ ' unshift' ,
415+ ' splice' ,
416+ ' sort' ,
417+ ' reverse'
418+ ]
419+ 420+ methodsToPatch .forEach (function (method ) {
421+ // 缓存原生数组
422+ const original = arrayProto[method]
423+ // def使用Object.defineProperty重新定义属性
424+ def (arrayMethods, method, function mutator (... args ) {
425+ const result = original .apply (this , args) // 调用原生数组的方法
426+ 427+ const ob = this .__ob__ // ob就是observe实例observe才能响应式
428+ let inserted
429+ switch (method) {
430+ // push和unshift方法会增加数组的索引,但是新增的索引位需要手动observe的
431+ case ' push' :
432+ case ' unshift' :
433+ inserted = args
434+ break
435+ // 同理,splice的第三个参数,为新增的值,也需要手动observe
436+ case ' splice' :
437+ inserted = args .slice (2 )
438+ break
439+ }
440+ // 其余的方法都是在原有的索引上更新,初始化的时候已经observe过了
441+ if (inserted) ob .observeArray (inserted)
442+ // dep通知所有的订阅者触发回调
443+ ob .dep .notify ()
444+ return result
445+ })
446+ })
447+ ```
448+ ### 对比
449+ 一个优秀的开源框架本身就是一个不断打碎重朔的过程,上面做了些许铺垫,现在我们简要总结一下
450+ 451+ - ` Proxy ` 作为新标准将受到浏览器厂商重点持续的性能优化
219452
220- ### Proxy 与 Object.defineProperty
221- 在 ` Proxy ` 出现之前,` JavaScript ` 中就提供过 ` Object.defineProperty ` ,允许对对象的 ` getter/setter ` 进行拦截
453+ - ` Proxy ` 能观察的类型比 ` defineProperty ` 更丰富
222454
223- 大家熟悉的Vue框架双向绑定的实现也由 ` defineProperty ` 重构为 ` Proxy ` ,那么两者的区别在究竟在哪里呢?
455+ - ` Proxy ` 不兼容IE,也没有 ` polyfill ` , ` defineProperty ` 能支持到IE9
224456
225- 请看下面这张图,图中所示的都是 ` defineProperty ` 现存的问题,而 ` Proxy ` 均已完美支持
457+ - ` Object.definedProperty ` 是劫持对象的属性,新增元素需要再次 ` definedProperty ` 。而 ` Proxy ` 劫持的是整个对象,不需要做特殊处理
226458
227- ![ image.png ] ( https://static.vue-js.com/5289d760-c675-11ea-ae44-f5d67be454e7.png )
459+ - 使用 ` defineProperty ` 时,我们修改原来的 ` obj ` 对象就可以触发拦截,而使用 ` proxy ` ,就必须修改代理对象,即 ` Proxy ` 的实例才可以触发拦截
228460
229- ### 兼容
230- 除了IE,其它浏览器最新版本均已支持
231461
232- ![ image.png] ( https://static.vue-js.com/9a145b00-c675-11ea-ae44-f5d67be454e7.png )
233462
234- 参考:
463+ ## 参考文献
235464- [ https://zh.wikipedia.org/wiki/ ] ( https://zh.wikipedia.org/wiki/ )
236465- [ https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy ] ( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy )
237466- [ https://es6.ruanyifeng.com/#docs/proxy#Proxy-revocable ] ( https://es6.ruanyifeng.com/#docs/proxy#Proxy-revocable )
238- - [ https://juejin.im/post/5e78d908f265da57340267f7#heading-2 ] ( https://juejin.im/post/5e78d908f265da57340267f7#heading-2 )
467+ - [ https://youngzhang08.github.io/ ] ( https://youngzhang08.github.io/ )
0 commit comments