ES6学习笔记: 代理和反射 下

简介: ES6学习笔记: 代理和反射 下

handler.defineProperty()

defineProperty(target, propKey, propDesc)用于拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDesc)Reflect.defineProperty(...),返回一个布尔值。

let target = {}
let handler = {
    defineProperty: function (target, propKey, propDescriptor) {
        console.log(`Called: ${propKey}`)
        return false
    }
}
let proxy = new Proxy(target, handler)
let propDescriptor = {
    configurable: true,
    enumerable: true,
    value: 10
}
Object.defineProperty(proxy, 'name', propDescriptor)

当调用 Object.defineProperty() 或者 Reflect.defineProperty(),传递给 definePropertydescriptor 有一个限制 - 只有以下属性才有用,非标准的属性将会被无视:

  • enumerable
  • configurable
  • writable
  • value
  • get
  • set

如果违背了以下的不变量,Proxy会抛出TypeError

  • 如果目标对象target不可扩展,将不能添加属性
  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话
  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的
  • 如果一个属性在目标对象中存在对应的属性,那么Object.defineProperty(target, propKey, propDescriptor)将不会抛出异常
  • 在严格模式下,false作为handler.defineProperty方法的返回值的话将会抛出TypeError异常

handler.getOwnPropertyDescriptor()

getOwnPropertyDescriptor(target, propKey)用于拦截Object.getOwnPropertyDescriptor(proxy, propKey)Reflect.getOwnPropertyDescriptor(...),返回属性的描述对象或者undefined

let handler = {
    getOwnPropertyDescriptor: function (target, key) {
        console.log(`Called: ${key}`)
        if (key[0] === '_') {
            return 
        }
        return Object.getOwnPropertyDescriptor(target, key)
    }
}
let target = {
    _prop: '大漠',
    prop: 'w3cplus'
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Object.getOwnPropertyDescriptor(proxy, '_prop'))
console.log(Object.getOwnPropertyDescriptor(proxy, 'prop'))

如果下列不变量被违反,代理将抛出一个 TypeError

  • getOwnPropertyDescriptor 必须返回一个 objectundefined
  • 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在
  • 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在
  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在
  • Object.getOwnPropertyDescriptor(target)的结果可以使用 Object.defineProperty 应用于目标对象,也不会抛出异常

以下是 Object.getOwnPropertyDescriptor() 的代码违反了不变量:

let target = {
    name: '大漠'
}
Object.preventExtensions(target)
let handler = {
    getOwnPropertyDescriptor: function (target, propKey) {
        console.log(`Called: ${propKey}`)
        return undefined
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))

handler.getPrototypeOf()

getPrototypeOf(target)用于拦截Object.getPrototypeOf(proxy)Reflect.preventExtensions(...)__proto__Object.isPrototypeOf()instanceof,返回一个对象。

let target = {}
let handler = {
    getPrototypeOf: function (target) {
        return Array.prototype
    }
}
let proxy = new Proxy(target, handler)
console.log(
`Object.getPrototypeOf(proxy):  ${Object.getPrototypeOf(proxy) === Array.prototype},
Reflect.getPrototypeOf(proxy): ${Reflect.getPrototypeOf(proxy) === Array.prototype},
proxy.__proto__: ${proxy.__proto__ === Array.prototype},
Array.prototype.isPrototypeOf(proxy): ${Array.prototype.isPrototypeOf(proxy)},
proxy instanceof Array: ${proxy instanceof Array}`)

如果遇到了下面两种情况,JavaScript 引擎会抛出 TypeError 异常:

  • getPrototypeOf() 方法返回的不是对象也不是 null
  • 目标对象是不可扩展的,且 getPrototypeOf() 方法返回的原型不是目标对象本身的原型

下面的示例展示的两种情况下就会报异常:

let proxy1 = new Proxy(
    {},
    {
        getPrototypeOf: function (target) {
            return '大漠'
        }
    }
)
console.log(Object.getPrototypeOf(proxy1))
let proxy2 = new Proxy(
    Object.preventExtensions({}),
    {
        getPrototypeOf: function (target) {
            return {}
        }
    }
)
console.log(Object.getPrototypeOf(proxy2))

handler.isExtensible()

isExtensible(target)拦截Object.isExtensible(proxy)Reflect.isExtensible(...),返回一个布尔值。

let target = {}
let handler = {
    isExtensible: function (target) {
        console.log(`Called`)
        return true
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.isExtensible(proxy))

这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。

Object.isExtensible(proxy) === Object.isExtensible(target)

下面是一个示例:

let target = {}
let handler = {
    isExtensible: function (target) {
        console.log('Call')
        return false
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.isExtensible(proxy))

handler.ownKeys()

ownKeys(target)用于拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)Reflect.get()Reflect.ownKeys(...),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。

let target = {}
let handler = {
    ownKeys: function (target) {
        console.log('Called')
        return ['a', 'b', 'c']
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyNames(proxy))

如果违反了下面的约定,Proxy将抛出错误 TypeError:

  • ownKeys 的结果必须试一个数组
  • 数组的元素类型要么是一个 String ,要么是一个 Symbol
  • 结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(own)属性的key
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的key,不能有其它值

下面的代码违反了约定,将会抛出TypeError

let target = {}
Object.defineProperty(
    target,
    'name',
    {
        configurable: false,
        enumerable: true,
        value: 10
    }
)
let handler = {
    ownKeys: function (target) {
        return [123, 12.5, true, false, undefined, null, {}, []]
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyName(proxy))

handler.preventExtensions()

preventExtensions(target)用于拦截Object.preventExtensions(proxy)Reflect.preventExtensions(...),将返回一个布尔值。这个方法有一个限制,只有当Object.isExtensible(proxy)false(即不可扩展)时,proxy.preventExtensions才能返回true,否则会报错。

let target = {}
let handler = {
    preventExtensions: function(target) {
        return true
    }
}
let proxy = new Proxy(target, handler)
console.log(Object.preventExtensions(proxy))

上面的代码,proxy.preventExtensions方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。为了防止出现这个问题,通常要在proxy.preventExtensions方法里面,调用一次Object.preventExtensions

let target = {}
let handler = {
    preventExtensions: function (target) {
        console.log('Called')
        Object.preventExtensions(target)
        return true
    }
}
let proxy = new Proxy (target, handler)
console.log(Object.preventExtensions(proxy))

handler.setPrototypeOf()

setPrototypeOf(target, proto)用于拦截Object.setPrototypeOf(proxy, proto)Reflect.setPrototypeOf(...)。将返回一个布尔值。

let target = function () {}
let handler = {
    setPrototypeOf: function(target, propKey) {
        throw new Error('Changing the prototype is forbidden')
    }
}
let propKey = {}
let proxy = new Proxy(target, handler)
console.log(proxy.setPrototypeOf(proxy, propKey))

handler.apply()

apply(target, object, args)用于拦截Proxy实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)Reflect.apply(...)

let target = function () {}
let handler = {
    apply: function (target, thisArg, argumentsList) {
        console.log(`Called: ${argumentsList.join(',')}`)
        return argumentsList[0] + argumentsList[1] + argumentsList[2] 
    }
}
let proxy = new Proxy (target, handler)
console.log(proxy(1, 2, 3))

proxy.revocable()

Proxyrevocable的意思是,取消访问proxy 的权限. 这个有什么用呢? 没有什么卵用… 开玩笑的. 他的映射范围应该算是比较少的。但是往往却是精华. 现在,我们有这样一个场景,假如,你现在有一个敏感数据, 但如果突然发生错误,你想立即取消掉对该数据的访问,so? 一般,要不就是直接删除该数据。 但这样方式,太简单粗暴了。 而,proxy提供了一个完美的trick来帮助我们实现这一方式。使用revocable()方法. 这时, 定义一个proxy就需要使用 Proxy.revocable(target, handler)

let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Reflect

Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。
  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

一个对象的键们可以这样被访问:

  • Reflect.ownKeys(..):返回对象自己的键(不是通过“继承”的),同样也返回Object.getOwnPropertyNames()Object.getOwnPropertySymbols()
  • Reflect.enumerate(..):返回对象上可枚举的属性(包括“继承”过来的)。
  • Reflect.has(..):和in操作符差不多。

函数的调用和构造调用可以手动通过下面 API 执行:

  • Reflect.apply(..):举个例子,Reflect.apply(foo,thisObj,[42,"bar"])调用了foo()函数,上下文是thisObj,参数为42bar
  • Reflect.construct(..):举个例子,Reflect.construct(foo,[42,"bar"])等于new foo(42,"bar")

对象属性访问,设置,删除可以手动通过下面 API 执行:

  • Reflect.get()Reflect.get(o,"foo")等于o.foo
  • Reflect.set()Reflect.set(o,"foo",42)等于o.foo = 42
  • Reflect.deleteProperty()Reflect.deleteProperty(o,"foo")等于delete o.foo

Reflect的元编程能力可以让你等效的模拟以前隐藏的各种语法特性。这样你就可以使用这些功能为特定语言(DSL)拓展新功能和 API。

总结

这篇文章主要整理了有关于JavaScript中的ProxyReflect相关的知识点。整理的有点混乱,如果你有更好的经验或者说文章中有不对之处,还请在下面的评论中与我一起分享。

目录
相关文章
|
2天前
|
Java
反射&代理
反射&代理
37 0
|
6月前
|
Java
反射--JavaJEE基础
反射--JavaJEE基础
46 0
|
8月前
|
设计模式 Java
反射和代理
反射和代理
49 0
|
8月前
|
JavaScript 前端开发 API
ES6 拾遗:理解 Reflect 反射对象
理解 Reflect 反射对象
69 0
ES6 拾遗:理解 Reflect 反射对象
|
10月前
|
API
不同场景下如何使用易路代理?
不同场景下如何使用易路代理?
es6 代理(Reflect)和反射(Proxy)的学习总结
es6 代理(Reflect)和反射(Proxy)的学习总结
|
监控
代理与反射(二)
代理与反射(二)
79 0
代理与反射(一)
代理与反射(一)
76 0
|
JavaScript 前端开发 API
ES6学习笔记: 代理和反射 上
ES6学习笔记: 代理和反射 上
73 0
|
JavaScript 前端开发 物联网
ES6躬行记(24)——代理和反射
代理和反射是ES6新增的两个特性,两者之间是协调合作的关系,它们的具体功能将在接下来的章节中分别讲解。