You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
当我们请求一个对象中不存在的 key 时,我们可以从对象中提供一个默认值,不过要注意, in 操作符仍然能知道这个 key 是否在对象中存在。
functiondefaultProp(defaultObj){consthandler={get(obj,prop){returnReflect.get(obj,prop)||defaultObj[prop];}};returnnewProxy({},handler);}functiontest(){constexist='name'ino ? 'in' : 'not in'console.log(`name = "${o.name}", name ${exist} o`)}consto=defaultProp({name: 'default name'})test()// name = "default name", name not in o// 替换默认值o.name='abcdef'test()// name = "abcdef", name in o// 删除name属性后 使用默认值deleteo.nametest()// name = "default name", name not in o
隐藏私有属性
Proxy 也有利于限制属性的访问,比如隐藏以下划线开头的的属性,让他们真正私有化。
先上代码,后面解释:
// filter参数作为一个过滤函数出入functionpriavateProp(obj,filter){consthandler={get(obj,prop){if(!filter(prop)){letval=Reflect.get(obj,prop)if(typeofval==='function'){val=val.bind(obj)}returnval}},set(obj,prop,val){if(filter(prop)){thrownewError(`cannot set property ${prop}`)}returnReflect.set(obj,prop,val)},has(obj,prop){returnfilter(prop) ? false : Reflect.has(obj,prop)},ownKeys(obj){returnReflect.ownKeys(obj).filter(prop=>!filter(prop))}}returnnewProxy(obj,handler)}
functioncreateEnum(object){consthandler={get(obj,prop){if(!(propinobj)){thrownewReferenceError(`unknow ${prop} in this Enum.`)}returnReflect.get(obj,prop)},set(){thrownewTypeError('Enum is readonly.')},deleteProperty(){thrownewTypeError('Enum is readonly.')}}returnnewProxy(object,handler)}
constobjEnum=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{deleteobjEnum.a}catch(e){console.log(e)// TypeError: Enum is readonly.}
用 Proxy 包装后去处理枚举,代码更健壮,各种操作异常也能抛出。
枚举另一个常用的功能就是根据 value 获取 key 值,虽然我们可以通过原型继承的方式实现,但这里还是用 Proxy 做一层包装,添加一个 key 函数
functioncreateEnum(name,val){functionkey(v){constkeys=Object.keys(this)for(leti=0,l=keys.length;i<l;i++){letk=keys[i]if(this[k]===v){return`${name}.${k}`}}}consthandler={get(obj,prop){if(prop=='key'){returnkey.bind(obj)}if(!(propinobj)){thrownewReferenceError(`unknow ${prop} in this Enum.`)}returnReflect.get(obj,prop)},set(){thrownewTypeError('Enum is readonly.')},deleteProperty(){thrownewTypeError('Enum is readonly.')}}returnnewProxy(val,handler)}constobj=createEnum('obj',{a: 'a',b: 'b',c: 1})obj.key('a')// "obj.a"obj.key(1)// "obj.c"obj.key('x')// undefined
constobj=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 a2222obj.a='xxxxx'// obj.a changed from a2222 to xxxxxdeleteobj.b// obj.b changed from undefined to undefinedobj.c='c1'// obj.c changed from undefined to c1
监听数组的变化
constarr=track([1,2,3,4,5],(obj,prop,oldVal,newVal)=>{letval=isNaN(parseInt(prop)) ? `.${prop}` : `[${prop}]`constsum=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 10deletearr[3]// arr[3] changed from 4 to undefined// sum [1,2,3,,0] is 6arr.length=2// arr.length changed from 5 to 2// sum [1,2] is 3
constrevocableInfo=Proxy.revocable({},{set(obj,prop,val){if(prop==='name'){if(typeofval!=='string'||val===''){thrownewTypeError(' "name" is not valid')}}elseif(prop==='age'){if(typeofval!=='number'||val>150||val<=0){thrownewTypeError(' "age" is not valid')}}else{thrownewTypeError(' must be "name" or "age"')}Reflect.set(obj,prop,val)}})// 用户填写信息letuserInfo={}functionsetUser(info){info.name="Tom"info.age=10// 非法agetry{info.age=-10}catch(e){console.log(e)}// 非法属性try{info.gender='male'}catch(e){console.log(e)}// 存储 infouserInfo=info}// 测试已经被 revoked 的代理functionsetUserAgain(){try{userInfo.name='Jerry'}catch(e){console.log(e)}}// 执行写入用户信息setUser(revocableInfo.proxy)// 数据验证出错// TypeError: "age" is not valid// TypeError: must be "name" or "age"// 代理对象执行撤销方法 收回代理权revocableInfo.revoke()setUserAgain()// TypeError: Cannot perform 'set' on a proxy that has been revoked
用实例代码理解 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 是在目标对象之前加一层劫持,外界对该对象的访问,都必须先通过这层劫持,因此提供了一种机制,可以对外界的访问进行过滤和改写,等同于在语言层面做出修改,所以属于一种 『元编程』
The text was updated successfully, but these errors were encountered: