当我们请求一个对象中不存在的 key 时,我们可以从对象中提供一个默认值,不过要注意, in 操作符仍然能知道这个 key 是否在对象中存在。
function defaultProp(defaultObj){
const handler = {
get(obj, prop) {
return Reflect.get(obj, prop) || defaultObj[prop];
}
};
return new Proxy({}, handler);
}
function test(){
const exist = 'name' in o ? 'in' : 'not in'
console.log(`name = "${o.name}", name ${exist} o`)
}
const o = defaultProp({ name: 'default name'})
test() // name = "default name", name not in o
// 替换默认值
o.name = 'abcdef'
test() // name = "abcdef", name in o
// 删除name属性后 使用默认值
delete o.name
test() // name = "default name", name not in o
隐藏私有属性
Proxy 也有利于限制属性的访问,比如隐藏以下划线开头的的属性,让他们真正私有化。
先上代码,后面解释:
// filter参数作为一个过滤函数出入
function priavateProp(obj, filter){
const handler = {
get(obj, prop) {
if(!filter(prop)){
let val = Reflect.get(obj, prop)
if(typeof val === 'function'){
val = val.bind(obj)
}
return val
}
},
set(obj, prop, val) {
if(filter(prop)){
throw new Error(`cannot set property ${prop}`)
}
return Reflect.set(obj, prop, val)
},
has(obj, prop) {
return filter(prop) ? false : Reflect.has(obj, prop)
},
ownKeys(obj) {
return Reflect.ownKeys(obj).filter( prop => !filter(prop))
}
}
return new Proxy(obj, handler)
}
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)
}
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
监听数组的变化
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 可是实现操作符的重载,但也只能对 inofdeletenew 这几个实现重载
我们劫持 in 操作符来实现 Array.includes 检查值是否存在数组中
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 操作符,以便每次都返回单例实例
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 引用同一个实例对象
用实例代码理解 ES6 Proxy
基本用法
Proxy 是通过包装对象,用拦截的方式来修改某些操作的默认行为,比如获取属性值。我们可以为需要拦截的对象提供一个带有 traps 的函数对象,如果对象操作没有定义 trap 将会指向原始的对象操作上。
上面代码中当要获取
p.a
值时,handler.get
这个 trap 就会被调用,相当于我们劫持了p.a
中的 . 操作符,于是就不能再去访问原始对象,我们在劫持中就可以做例如验证、封装、访问控制等各种操作了。默认值
当我们请求一个对象中不存在的 key 时,我们可以从对象中提供一个默认值,不过要注意,
in
操作符仍然能知道这个 key 是否在对象中存在。隐藏私有属性
Proxy 也有利于限制属性的访问,比如隐藏以下划线开头的的属性,让他们真正私有化。
先上代码,后面解释:
上面代码使用了4个 trap 函数来实现,参数
filter
函数是用来过滤私有属性的过滤器。has
函数因为在 Proxy 中用来监听对象in
的使用,所以成了过滤私有属性的核心。ownKeys
过滤私有属性,让Object.keys(p)
返回其他所有属性。注意,因为在 Proxy 上调用方法,
this
默认指向的是 Proxy 代理 ,而不是原始对象,因此方法将无法访问私有属性,所以通过将方法绑定到get
函数中来解决。枚举 Enum
js 中可用对象操作或者
Object.freeze
的方式来实现枚举,但有时候会出现类型安全的问题,因此很不健壮。通过 Proxy ,我们创建一个键值对象,通过阻止修改其值来保证其健壮性,同时比
Object.freeze
更安全。(虽然Object.freeze
可以阻止内容被修改,但不会抛出错误,所以会隐藏潜在的 bug)我们先实现一个
createEnum
,然后在和其他枚举的方式做下对比下面对比三种方式的枚举操作
Object.freeze
的对象枚举用 Proxy 包装后去处理枚举,代码更健壮,各种操作异常也能抛出。
枚举另一个常用的功能就是根据 value 获取 key 值,虽然我们可以通过原型继承的方式实现,但这里还是用 Proxy 做一层包装,添加一个
key
函数追踪对象和数组
这也是观察者模式的一部分 在 Vue.js 中无法监听数组的
length
导致arr.length = 1
这种数据的改变无法监听,因此 Vue.js 在今年(2017)也会实现一个基于 Proxy 的 Observation当对象或数组发生变化时,我们通过订阅的事件就可以观察到,同理,我们还可以添加验证的拦截,在数据更改之前先做验证处理。
因此我们把对象和数组也加一层 Proxy 来处理,我们把所有的改变都转发到原对象上,在修改或删除之后添加一个函数当做监听器 :
在数组中使用
in
使用 Proxy 可是实现操作符的重载,但也只能对
in
of
delete
new
这几个实现重载我们劫持
in
操作符来实现Array.includes
检查值是否存在数组中实现单例模式
这里我们通过
construct
这个 trap 劫持new
操作符,以便每次都返回单例实例数据验证
revocable
Proxy.revocable(obj, prop)
方法可以用来创建一个可撤销的代理对象,返回一个包含了所生成的代理对象本身以及该代理对象的撤销方法的对象。该方法的返回值结构为 {"proxy": proxy, "revoke": revoke}, 其中:new Proxy(obj, prop)
没什么区别,只是它可以被撤销掉。一旦某个代理对象被撤销,它将变的几乎完全不可用,在它身上执行任何的可代理操作都会抛出 TypeError 异常。
Proxy.revocable
的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。所以,利用Proxy.revocable
实现一个验证和可撤销访问的例子某个代理对象一旦被撤销,这个代理对象永远不可能恢复到原来的状态,同时和它关联的目标对象以及处理器对象将有可能被垃圾回收掉。
最后
Proxy 是在目标对象之前加一层劫持,外界对该对象的访问,都必须先通过这层劫持,因此提供了一种机制,可以对外界的访问进行过滤和改写,等同于在语言层面做出修改,所以属于一种 『元编程』