Skip to content

Commit 7db4b43

Browse files
committed
update proxy blog
1 parent 0291218 commit 7db4b43

File tree

1 file changed

+257
-8
lines changed

1 file changed

+257
-8
lines changed

Blog/58.ES6 Proxy 实用代码示例.md

+257-8
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
Proxy 是通过包装对象,用拦截的方式来修改某些操作的默认行为,比如获取属性值。我们可以为需要拦截的对象提供一个带有 [traps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) 的函数对象,如果对象操作没有定义 trap 将会指向原始的对象操作上。
88

99
```js
10-
const hanlder = {
10+
const handler = {
1111
get(target, prop) {
1212
const val = target[prop]
1313
console.log(`property ${prop} = ${val}`)
1414
return val
1515
}
1616
}
1717

18-
const p = new Proxy({a: 1}, hanlder)
18+
const p = new Proxy({a: 1}, handler)
1919

2020
console.log(p.a)
2121
// property a = 1
@@ -24,7 +24,7 @@ console.log(p.b)
2424
// property b = undefined
2525
// 1
2626
```
27-
上面代码中当要获取 `p.a` 值时,`hanlder.get` 这个 trap 就会被调用,相当于我们劫持了 `p.a` 中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。
27+
上面代码中当要获取 `p.a` 值时,`handler.get` 这个 trap 就会被调用,相当于我们劫持了 `p.a` 中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。
2828

2929

3030
## 默认值
@@ -68,7 +68,7 @@ Proxy 也有利于限制属性的访问,比如隐藏以下划线开头的的
6868

6969
```js
7070
function priavateProp(obj, filter){
71-
const hanlder = {
71+
const handler = {
7272
get(obj, prop) {
7373
if(!filter(prop)){
7474
let val = Reflect.get(obj, prop)
@@ -92,7 +92,7 @@ function priavateProp(obj, filter){
9292
}
9393
}
9494

95-
return new Proxy(obj, hanlder)
95+
return new Proxy(obj, handler)
9696
}
9797
// 私有属性过滤器
9898
function filter(prop){
@@ -137,15 +137,264 @@ js 中可用对象操作或者 `Object.freeze` 的方式来实现枚举,但有
137137

138138
通过 Proxy ,我们创建一个键值对象,通过阻止修改其值来保证其健壮性,同时比 `Object.freeze` 更安全。(虽然 `Object.freeze` 可以阻止内容被修改,但不会抛出错误,所以会隐藏潜在的 bug)
139139

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 来处理,我们把所有的改变都转发到原对象上,在修改或删除之后添加一个函数当做监听器 :
141277

142278
```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)
145294
}
146295
```
147296

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
148327

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+
```
149398

150399

151400

0 commit comments

Comments
 (0)