7
7
Proxy 是通过包装对象,用拦截的方式来修改某些操作的默认行为,比如获取属性值。我们可以为需要拦截的对象提供一个带有 [ traps] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology ) 的函数对象,如果对象操作没有定义 trap 将会指向原始的对象操作上。
8
8
9
9
``` js
10
- const hanlder = {
10
+ const handler = {
11
11
get (target , prop ) {
12
12
const val = target[prop]
13
13
console .log (` property ${ prop} = ${ val} ` )
14
14
return val
15
15
}
16
16
}
17
17
18
- const p = new Proxy ({a: 1 }, hanlder )
18
+ const p = new Proxy ({a: 1 }, handler )
19
19
20
20
console .log (p .a )
21
21
// property a = 1
@@ -24,7 +24,7 @@ console.log(p.b)
24
24
// property b = undefined
25
25
// 1
26
26
```
27
- 上面代码中当要获取 ` p.a ` 值时,` hanlder .get` 这个 trap 就会被调用,相当于我们劫持了 ` p.a ` 中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。
27
+ 上面代码中当要获取 ` p.a ` 值时,` handler .get` 这个 trap 就会被调用,相当于我们劫持了 ` p.a ` 中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。
28
28
29
29
30
30
## 默认值
@@ -68,7 +68,7 @@ Proxy 也有利于限制属性的访问,比如隐藏以下划线开头的的
68
68
69
69
``` js
70
70
function priavateProp (obj , filter ){
71
- const hanlder = {
71
+ const handler = {
72
72
get (obj , prop ) {
73
73
if (! filter (prop)){
74
74
let val = Reflect .get (obj, prop)
@@ -92,7 +92,7 @@ function priavateProp(obj, filter){
92
92
}
93
93
}
94
94
95
- return new Proxy (obj, hanlder )
95
+ return new Proxy (obj, handler )
96
96
}
97
97
// 私有属性过滤器
98
98
function filter (prop ){
@@ -137,15 +137,264 @@ js 中可用对象操作或者 `Object.freeze` 的方式来实现枚举,但有
137
137
138
138
通过 Proxy ,我们创建一个键值对象,通过阻止修改其值来保证其健壮性,同时比 ` Object.freeze ` 更安全。(虽然 ` Object.freeze ` 可以阻止内容被修改,但不会抛出错误,所以会隐藏潜在的 bug)
139
139
140
- 我们先实现一个 Enum ,然后在和其他枚举的方式做下对比
140
+ 我们先实现一个 ` createEnum ` ,然后在和其他枚举的方式做下对比
141
+
142
+ ``` js
143
+ function createEnum (object ){
144
+ const handler = {
145
+ get (obj , prop ) {
146
+ if (! (prop in obj)){
147
+ throw new ReferenceError (` unknow ${ prop} in this Enum.` )
148
+ }
149
+ return Reflect .get (obj, prop)
150
+ },
151
+ set () {
152
+ throw new TypeError (' Enum is readonly.' )
153
+ },
154
+ deleteProperty () {
155
+ throw new TypeError (' Enum is readonly.' )
156
+ }
157
+ }
158
+
159
+ return new Proxy (object, handler)
160
+ }
161
+ ```
162
+ 下面对比三种方式的枚举操作
163
+
164
+ 1 . 把一个普通对象当做枚举来处理
165
+
166
+ ``` js
167
+ const anotherValue = ' another'
168
+
169
+ const objOne = { a: ' a1' , b: ' b1' }
170
+
171
+ objOne .a // "a1"
172
+ objOne .c // undefined 并没有报错
173
+
174
+ objOne .a = anotherValue
175
+ objOne .a // "a111" 改变了枚举 --- 其实这是正常的对象操作
176
+
177
+ delete objOne .a // 正常删除对象属性 同时也删除了枚举值
178
+ ```
179
+
180
+ 2 . 使用 ` Object.freeze ` 的对象枚举
181
+
182
+ ``` js
183
+ const anotherValue = ' another'
184
+ const objTwo = Object .freeze ({ a: ' a2' , b: ' b2' })
185
+
186
+ objTwo .a // "a2"
187
+ objTwo .c // undefined
188
+
189
+ if (objTwo .a = anotherValue){ // 能够赋值
190
+ console .log (objTwo .a ) // 但是依然返回的是 "a2"
191
+ }
192
+
193
+ delete objTwo .a // 不能删除 但也没有抛出错误
194
+ ```
195
+
196
+ 3 . 使用 Proxy 包装过的枚举
197
+
198
+ ``` js
199
+ const objEnum = createEnum ({ a: ' a3' , b: ' b3' })
200
+
201
+ objEnum .a // "a3"
202
+
203
+ try {
204
+ objEnum .c
205
+ }catch (e){
206
+ console .log (e) // ReferenceError: unknow c in this Enum.
207
+ }
208
+
209
+ try {
210
+ if (objEnum .a = " another" ) {
211
+ console .log (objEnum .a ) // 这一行永远不会执行
212
+ }
213
+ }catch (e){
214
+ console .log (e) // TypeError: Enum is readonly.
215
+ }
216
+
217
+ try {
218
+ delete objEnum .a
219
+ }catch (e){
220
+ console .log (e) // TypeError: Enum is readonly.
221
+ }
222
+
223
+ ```
224
+
225
+ 用 Proxy 包装后去处理枚举,代码更健壮,各种操作异常也能抛出。
226
+
227
+ 枚举另一个常用的功能就是根据 value 获取 key 值,虽然我们可以通过原型继承的方式实现,但这里还是用 Proxy 做一层包装,添加一个 ` key ` 函数
228
+
229
+ ``` js
230
+ function createEnum (name , val ){
231
+ function key (v ){
232
+ const keys = Object .keys (this )
233
+ for (let i= 0 ,l= keys .length ;i< l;i++ ){
234
+ let k = keys[i]
235
+ if (this [k] === v) {
236
+ return ` ${ name} .${ k} `
237
+ }
238
+ }
239
+ }
240
+ const handler = {
241
+ get (obj , prop ) {
242
+ if (prop == ' key' ){
243
+ return key .bind (obj)
244
+ }
245
+ if (! (prop in obj)){
246
+ throw new ReferenceError (` unknow ${ prop} in this Enum.` )
247
+ }
248
+ return Reflect .get (obj, prop)
249
+ },
250
+ set () {
251
+ throw new TypeError (' Enum is readonly.' )
252
+ },
253
+ deleteProperty () {
254
+ throw new TypeError (' Enum is readonly.' )
255
+ }
256
+ }
257
+
258
+ return new Proxy (val, handler)
259
+ }
260
+
261
+ const obj = createEnum (' obj' , { a: ' a' , b: ' b' , c: 1 })
262
+
263
+ obj .key (' a' ) // "obj.a"
264
+ obj .key (1 ) // "obj.c"
265
+ obj .key (' x' ) // undefined
266
+ ```
267
+
268
+ ## 追踪对象和数组
269
+
270
+ 这也是观察者模式的一部分,同时 Vue.js 在今年(2017)也会实现一个基于 Proxy 的 Observation
271
+
272
+ 当对象或数组发生变化时,我们通过订阅的事件就可以观察到,同理,我们还可以添加验证的拦截,在数据更改之前先做验证处理。
273
+
274
+ 在 Vue.js 中无法监听数组的 ` length ` 导致 ` arr.length = 1 ` 这种数据的改变无法监听
275
+
276
+ 因此我们把对象和数组也加一层 Proxy 来处理,我们把所有的改变都转发到原对象上,在修改或删除之后添加一个函数当做监听器 :
141
277
142
278
``` js
143
- function Enum (){
144
-
279
+ function track (obj , fn ){
280
+ const handler = {
281
+ set (obj , prop , val ) {
282
+ const oldVal = obj[prop]
283
+ Reflect .set (obj, prop, val)
284
+ fn (obj, prop, oldVal, val)
285
+ },
286
+ deleteProperty (obj , prop ) {
287
+ const oldVal = obj[prop]
288
+ Reflect .deleteProperty (obj, prop)
289
+ fn (obj, prop, oldVal, undefined )
290
+ }
291
+ }
292
+
293
+ return new Proxy (obj, handler)
145
294
}
146
295
```
147
296
297
+ 1 . 监听对象的变化
298
+
299
+ ``` js
300
+ const obj = track ({a: ' a1' , b: ' b1' }, (obj , prop , oldVal , newVal ) => {
301
+ console .log (` obj.${ prop} changed from ${ oldVal} to ${ newVal} ` )
302
+ })
303
+ obj .a = ' a2222' // obj.a changed from a1 to a2222
304
+ obj .a = ' xxxxx' // obj.a changed from a2222 to xxxxx
305
+ delete obj .b // obj.b changed from undefined to undefined
306
+ obj .c = ' c1' // obj.c changed from undefined to c1
307
+ ```
308
+
309
+ 2 . 监听数组的变化
310
+
311
+ ``` js
312
+ const arr = track ([1 , 2 , 3 , 4 , 5 ], (obj , prop , oldVal , newVal ) => {
313
+ let val = isNaN (parseInt (prop)) ? ` .${ prop} ` : ` [${ prop} ]`
314
+ const sum = arr .reduce ( (p ,n ) => p + n)
315
+
316
+ console .log (` arr${ val} changed from ${ oldVal} to ${ newVal} ` )
317
+ console .log (` sum [${ arr} ] is ${ sum} ` )
318
+ })
319
+
320
+ arr[4 ] = 0
321
+ // arr[4] changed from 5 to 0
322
+ // sum [1,2,3,4,0] is 10
323
+
324
+ delete arr[3 ]
325
+ // arr[3] changed from 4 to undefined
326
+ // sum [1,2,3,,0] is 6
148
327
328
+ arr .length = 2
329
+ // arr.length changed from 5 to 2
330
+ // sum [1,2] is 3
331
+ ```
332
+
333
+ ## 在数组中使用 ` in `
334
+
335
+ 使用 Proxy 可是实现操作符的重载,但也只能对 ` in ` ` of ` ` delete ` ` new ` 这几个实现重载
336
+
337
+ 我们劫持 ` in ` 操作符来实现 ` Array.includes ` 检查值是否存在数组中
338
+
339
+ ``` js
340
+ function arrIn (arr ){
341
+ const handler = {
342
+ has (arr , val ) {
343
+ return arr .includes (val)
344
+ }
345
+ }
346
+
347
+ return new Proxy (arr, handler)
348
+ }
349
+
350
+ const arr = arrIn ([' a' , ' b' , ' c' ])
351
+
352
+ ' a' in arr // true
353
+
354
+ 1 in arr // false
355
+
356
+ ```
357
+
358
+ ## 实现单例模式
359
+
360
+ 这里我们通过 ` construct ` 这个 trap 劫持 ` new ` 操作符,以便每次都返回单例实例
361
+
362
+ ``` js
363
+ function Sigleton (fn ){
364
+ let instance
365
+ const handler = {
366
+ construct () {
367
+ if (! instance){
368
+ instance = new fn ()
369
+ }
370
+ return instance
371
+ }
372
+ }
373
+
374
+ return new Proxy (fn, handler)
375
+ }
376
+
377
+ function Func () {
378
+ this .value = ' value'
379
+ }
380
+
381
+ // 1.普通的实例化
382
+ const f1 = new Func ()
383
+ const f2 = new Func ()
384
+
385
+ f1 .value = ' new value'
386
+ f2 .value // "value" f1 f2 是两个不同的实例
387
+
388
+
389
+ // 2. 用Proxy实现的单例
390
+ const p1 = Sigleton (Func)
391
+ const p2 = Sigleton (Func)
392
+
393
+ p1 .value = " proxy value"
394
+
395
+ p2 .value // "proxy value" p1 p2 引用同一个实例对象
396
+
397
+ ```
149
398
150
399
151
400
0 commit comments