Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit de56667

Browse files
author
febobo
committed
update proxy
1 parent bea9d9d commit de56667

File tree

1 file changed

+263
-34
lines changed

1 file changed

+263
-34
lines changed

‎docs/es6/index.md‎

Lines changed: 263 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
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
1717
done
1818
chmod +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+
3246
const 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, {
116130
p.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进行封闭,撤消代理
138152
proxy.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
149166
const 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
193208
const target = {
@@ -215,24 +230,238 @@ proxy._id // Uncaught Error: _id is restricted
215230
proxy._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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /