Skip to content

Commit

Permalink
update proxy blog
Browse files Browse the repository at this point in the history
  • Loading branch information
ccforward committed Mar 15, 2017
1 parent 0291218 commit 7db4b43
Showing 1 changed file with 257 additions and 8 deletions.
265 changes: 257 additions & 8 deletions Blog/58.ES6 Proxy 实用代码示例.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
Proxy 是通过包装对象,用拦截的方式来修改某些操作的默认行为,比如获取属性值。我们可以为需要拦截的对象提供一个带有 [traps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) 的函数对象,如果对象操作没有定义 trap 将会指向原始的对象操作上。

```js
const hanlder = {
const handler = {
get(target, prop) {
const val = target[prop]
console.log(`property ${prop} = ${val}`)
return val
}
}

const p = new Proxy({a: 1}, hanlder)
const p = new Proxy({a: 1}, handler)

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


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

```js
function priavateProp(obj, filter){
const hanlder = {
const handler = {
get(obj, prop) {
if(!filter(prop)){
let val = Reflect.get(obj, prop)
Expand All @@ -92,7 +92,7 @@ function priavateProp(obj, filter){
}
}

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

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

我们先实现一个 Enum ,然后在和其他枚举的方式做下对比
我们先实现一个 `createEnum` ,然后在和其他枚举的方式做下对比

```js
function createEnum(object){
const handler = {
get(obj, prop) {
if(!(prop in obj)){
throw new ReferenceError(`unknow ${prop} in this Enum.`)
}
return Reflect.get(obj, prop)
},
set() {
throw new TypeError('Enum is readonly.')
},
deleteProperty() {
throw new TypeError('Enum is readonly.')
}
}

return new Proxy(object, handler)
}
```
下面对比三种方式的枚举操作

1. 把一个普通对象当做枚举来处理

```js
const anotherValue = 'another'

const objOne = { a: 'a1', b: 'b1' }

objOne.a // "a1"
objOne.c // undefined 并没有报错

objOne.a = anotherValue
objOne.a // "a111" 改变了枚举 --- 其实这是正常的对象操作

delete objOne.a // 正常删除对象属性 同时也删除了枚举值
```

2. 使用 `Object.freeze` 的对象枚举

```js
const anotherValue = 'another'
const objTwo = Object.freeze({ a: 'a2', b: 'b2' })

objTwo.a // "a2"
objTwo.c // undefined

if(objTwo.a = anotherValue){ // 能够赋值
console.log(objTwo.a) // 但是依然返回的是 "a2"
}

delete objTwo.a // 不能删除 但也没有抛出错误
```

3. 使用 Proxy 包装过的枚举

```js
const objEnum = createEnum({ a: 'a3', b: 'b3' })

objEnum.a // "a3"

try {
objEnum.c
}catch(e){
console.log(e) // ReferenceError: unknow c in this Enum.
}

try {
if(objEnum.a = "another") {
console.log(objEnum.a) // 这一行永远不会执行
}
}catch(e){
console.log(e) // TypeError: Enum is readonly.
}

try {
delete objEnum.a
}catch(e){
console.log(e) // TypeError: Enum is readonly.
}

```

用 Proxy 包装后去处理枚举,代码更健壮,各种操作异常也能抛出。

枚举另一个常用的功能就是根据 value 获取 key 值,虽然我们可以通过原型继承的方式实现,但这里还是用 Proxy 做一层包装,添加一个 `key` 函数

```js
function createEnum(name, val){
function key(v){
const keys = Object.keys(this)
for(let i=0,l=keys.length;i<l;i++){
let k = keys[i]
if(this[k] === v) {
return `${name}.${k}`
}
}
}
const handler = {
get(obj, prop) {
if(prop == 'key'){
return key.bind(obj)
}
if(!(prop in obj)){
throw new ReferenceError(`unknow ${prop} in this Enum.`)
}
return Reflect.get(obj, prop)
},
set() {
throw new TypeError('Enum is readonly.')
},
deleteProperty() {
throw new TypeError('Enum is readonly.')
}
}

return new Proxy(val, handler)
}

const obj = createEnum('obj', { a: 'a', b: 'b', c: 1 })

obj.key('a') // "obj.a"
obj.key(1) // "obj.c"
obj.key('x') // undefined
```

## 追踪对象和数组

这也是观察者模式的一部分,同时 Vue.js 在今年(2017)也会实现一个基于 Proxy 的 Observation

当对象或数组发生变化时,我们通过订阅的事件就可以观察到,同理,我们还可以添加验证的拦截,在数据更改之前先做验证处理。

在 Vue.js 中无法监听数组的 `length` 导致 `arr.length = 1` 这种数据的改变无法监听

因此我们把对象和数组也加一层 Proxy 来处理,我们把所有的改变都转发到原对象上,在修改或删除之后添加一个函数当做监听器 :

```js
function Enum(){

function track(obj, fn){
const handler = {
set(obj, prop, val) {
const oldVal = obj[prop]
Reflect.set(obj, prop, val)
fn(obj, prop, oldVal, val)
},
deleteProperty(obj, prop) {
const oldVal = obj[prop]
Reflect.deleteProperty(obj, prop)
fn(obj, prop, oldVal, undefined)
}
}

return new Proxy(obj, handler)
}
```

1. 监听对象的变化

```js
const obj = track({a: 'a1', b: 'b1'}, (obj, prop, oldVal, newVal) => {
console.log(`obj.${prop} changed from ${oldVal} to ${newVal}`)
})
obj.a = 'a2222' // obj.a changed from a1 to a2222
obj.a = 'xxxxx' // obj.a changed from a2222 to xxxxx
delete obj.b // obj.b changed from undefined to undefined
obj.c = 'c1' // obj.c changed from undefined to c1
```

2. 监听数组的变化

```js
const arr = track([1, 2, 3, 4, 5], (obj, prop, oldVal, newVal) => {
let val = isNaN(parseInt(prop)) ? `.${prop}` : `[${prop}]`
const sum = arr.reduce( (p,n) => p + n)

console.log(`arr${val} changed from ${oldVal} to ${newVal}`)
console.log(`sum [${arr}] is ${sum}`)
})

arr[4] = 0
// arr[4] changed from 5 to 0
// sum [1,2,3,4,0] is 10

delete arr[3]
// arr[3] changed from 4 to undefined
// sum [1,2,3,,0] is 6

arr.length = 2
// arr.length changed from 5 to 2
// sum [1,2] is 3
```

## 在数组中使用 `in`

使用 Proxy 可是实现操作符的重载,但也只能对 `in` `of` `delete` `new` 这几个实现重载

我们劫持 `in` 操作符来实现 `Array.includes` 检查值是否存在数组中

```js
function arrIn(arr){
const handler = {
has(arr, val) {
return arr.includes(val)
}
}

return new Proxy(arr, handler)
}

const arr = arrIn(['a', 'b', 'c'])

'a' in arr // true

1 in arr // false

```

## 实现单例模式

这里我们通过 `construct` 这个 trap 劫持 `new` 操作符,以便每次都返回单例实例

```js
function Sigleton(fn){
let instance
const handler = {
construct() {
if(!instance){
instance = new fn()
}
return instance
}
}

return new Proxy(fn, handler)
}

function Func() {
this.value = 'value'
}

// 1.普通的实例化
const f1 = new Func()
const f2 = new Func()

f1.value = 'new value'
f2.value // "value" f1 f2 是两个不同的实例


// 2. 用Proxy实现的单例
const p1 = Sigleton(Func)
const p2 = Sigleton(Func)

p1.value = "proxy value"

p2.value // "proxy value" p1 p2 引用同一个实例对象

```



Expand Down

0 comments on commit 7db4b43

Please sign in to comment.