diff --git "a/Blog/58.ES6 Proxy \345\256\236\347\224\250\344\273\243\347\240\201\347\244\272\344\276\213.md" "b/Blog/58.ES6 Proxy \345\256\236\347\224\250\344\273\243\347\240\201\347\244\272\344\276\213.md" index 421660e..81dcb46 100644 --- "a/Blog/58.ES6 Proxy \345\256\236\347\224\250\344\273\243\347\240\201\347\244\272\344\276\213.md" +++ "b/Blog/58.ES6 Proxy \345\256\236\347\224\250\344\273\243\347\240\201\347\244\272\344\276\213.md" @@ -7,7 +7,7 @@ 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}`) @@ -15,7 +15,7 @@ const hanlder = { } } -const p = new Proxy({a: 1}, hanlder) +const p = new Proxy({a: 1}, handler) console.log(p.a) // property a = 1 @@ -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` 中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。 ## 默认值 @@ -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) @@ -92,7 +92,7 @@ function priavateProp(obj, filter){ } } - return new Proxy(obj, hanlder) + return new Proxy(obj, handler) } // 私有属性过滤器 function filter(prop){ @@ -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 { + 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 引用同一个实例对象 + +```