资讯专栏INFORMATION COLUMN

深入理解ES6之《代理和反射》

curried / 565人阅读

摘要:使用陷阱验证属性用于接收属性代理的目标的对象要写入的属性键被写入的属性的值操作发生的对象通常是代理属性必须是数字抛错用陷阱验证对象结构属性不存在抛出错误使用陷阱隐藏已有属性可以用操作符来检测给定对象中是否包含有某个属性,如果自有属性或原型属

使用set陷阱验证属性

</>复制代码

  1. let target = {
  2. name: "target"
  3. }
  4. let proxy = new Proxy(target, {
  5. /**
  6. *
  7. *
  8. * @param {any} trapTarget 用于接收属性(代理的目标)的对象
  9. * @param {any} key 要写入的属性键
  10. * @param {any} value 被写入的属性的值
  11. * @param {any} receiver 操作发生的对象(通常是代理)
  12. */
  13. set(trapTarget, key, value, receiver) {
  14. if (!trapTarget.hasOwnProperty(key)) {
  15. if (isNaN(value)) {
  16. throw new TypeError("属性必须是数字")
  17. }
  18. }
  19. return Reflect.set(trapTarget, key, value, receiver)
  20. }
  21. })
  22. proxy.count = 1;
  23. console.log(proxy.count)//1
  24. console.log(target.count)//1
  25. proxy.name = "proxy"
  26. console.log(proxy.name)//proxy
  27. console.log(target.name)//proxy
  28. proxy.anthorName = "test"// 抛错
用get陷阱验证对象结构

</>复制代码

  1. let proxy = new Proxy({}, {
  2. get(trapTarget, key, receiver) {
  3. if (!(key in receiver)) {
  4. throw new TypeError("属性" + key + "不存在")
  5. }
  6. return Reflect.get(trapTarget,key,receiver)
  7. }
  8. })
  9. proxy.name="proxy"
  10. console.log(proxy.name)//proxy
  11. console.log(proxy.nme)//抛出错误
使用has陷阱隐藏已有属性

可以用in操作符来检测给定对象中是否包含有某个属性,如果自有属性或原型属性匹配这个名称或Symbol就返回true

</>复制代码

  1. let target = {
  2. name: "target",
  3. value: 42
  4. }
  5. let proxy = new Proxy(target, {
  6. has(trapTarget, key) {
  7. if (key === "value") {
  8. return false
  9. }
  10. return Reflect.has(trapTarget, key)
  11. }
  12. })
  13. console.log("value" in proxy)//false
  14. console.log("name" in proxy)//true
  15. console.log("toString" in proxy)//true
用deleteProperty陷阱防止删除属性

不可配置属性name用delete操作返回的是false,如果在严格模式下还会抛出错误
可以通过deleteProperty陷阱来改变这个行为

</>复制代码

  1. let target = {
  2. name: "target",
  3. value: 42
  4. }
  5. let proxy = new Proxy(target, {
  6. deleteProperty(trapTarget, key) {
  7. if (key === "value") {
  8. return false
  9. }
  10. return Reflect.deleteProperty(trapTarget, key)
  11. }
  12. })
  13. console.log("value" in proxy)//true
  14. let result1 = delete proxy.value
  15. console.log(result1)//false
  16. console.log("value" in proxy)//true
  17. //尝试删除不可配置属性name 如果没有使用代理则会返回false并且删除不成功
  18. console.log("name" in proxy)//true
  19. let result2 = delete proxy.name;
  20. console.log(result2)//true
  21. console.log("name" in proxy)//false
原型代理陷阱

</>复制代码

  1. let target = {}
  2. let proxy = new Proxy(target, {
  3. getPrototypeOf(trapTarget) {
  4. //必须返回对象或null,只要返回的是值类型必将导致运行时错误
  5. return null;
  6. },
  7. setPrototypeOf(trapTarget, proto) {
  8. // 如果操作失败则返回false 如果setPrototypeOf返回了任何不是false的值,那么Object.setPrototypeOf便设置成功
  9. return false
  10. }
  11. })
  12. let targetProto = Object.getPrototypeOf(target);
  13. let proxyProto = Object.getPrototypeOf(proxy)
  14. console.log(targetProto === Object.prototype)//true
  15. console.log(proxyProto === Object.prototype)//false
  16. Object.setPrototypeOf(target, {})//成功
  17. Object.setPrototypeOf(proxy, {})//抛出错误

再看一下下面的代码

</>复制代码

  1. let target = {}
  2. let proxy = new Proxy(target, {
  3. getPrototypeOf(trapTarget) {
  4. return Reflect.getPrototypeOf(trapTarget);
  5. },
  6. setPrototypeOf(trapTarget, proto) {
  7. return Reflect.setPrototypeOf(trapTarget,proto)
  8. }
  9. })
  10. let targetProto = Object.getPrototypeOf(target);
  11. let proxyProto = Object.getPrototypeOf(proxy)
  12. console.log(targetProto === Object.prototype)//true
  13. console.log(proxyProto === Object.prototype)//true
  14. Object.setPrototypeOf(target, {})//成功
  15. Object.setPrototypeOf(proxy, {})//成功

再来说说Object.getPrototypeOf和Reflect.getPrototypeOf的异同点吧
1如果传入的参数不是对象,则Reflect.getPrototypeOf方法会抛出错误,而Object.getPrototypeOf方法则会在操作执行前先将参数强制转换为一个对象(对于Object.getPrototypeOf也是同样)

</>复制代码

  1. let result = Object.getPrototypeOf(1)
  2. console.log(result === Number.prototype)//true
  3. Reflect.getPrototypeOf(1)//抛错
对象可扩展性陷阱

</>复制代码

  1. let target = {}
  2. let proxy = new Proxy(target, {
  3. isExtensible(trapTarget) {
  4. return Reflect.isExtensible(trapTarget)
  5. },
  6. preventExtensions(trapTarget) {
  7. return Reflect.preventExtensions(trapTarget)
  8. }
  9. })
  10. console.log(Object.isExtensible(target))//true
  11. console.log(Object.isExtensible(proxy))//true
  12. Object.preventExtensions(proxy)
  13. console.log(Object.isExtensible(target))//false
  14. console.log(Object.isExtensible(proxy))//false

比方说你想让Object.preventExtensions失败,可返回false,看下面的例子

</>复制代码

  1. let target = {}
  2. let proxy = new Proxy(target, {
  3. isExtensible(trapTarget) {
  4. return Reflect.isExtensible(trapTarget)
  5. },
  6. preventExtensions(trapTarget) {
  7. return false
  8. }
  9. })
  10. console.log(Object.isExtensible(target))//true
  11. console.log(Object.isExtensible(proxy))//true
  12. Object.preventExtensions(proxy)
  13. console.log(Object.isExtensible(target))//true //书上说这里会返回true,可是我自己运行的时候就已经抛出错误了
  14. console.log(Object.isExtensible(proxy))//true

Object.isExtensible和Reflect.isExtensible方法非常相似,只有当传入非对象值时,Object.isExtensible返回false而Reflect.isExtensible则抛出错误

</>复制代码

  1. let result1 = Object.isExtensible(2)
  2. console.log(result1)//false
  3. let result2 = Reflect.isExtensible(2)

Object.preventExtensions和Reflect.preventExtensions非常类似,无论传入Object.preventExtensions方法的参数是否为一个对象,它总是返回该参数,而如果Reflect.preventExtensions方法的参数不是一个对象则会抛出错误,如果参数是一个对象,操作成功时Reflect.preventExtensions会返回true否则返回false

</>复制代码

  1. let result1 = Object.preventExtensions(2)
  2. console.log(result1)//2
  3. let target = {}
  4. let result2 = Reflect.preventExtensions(target)
  5. console.log(result2)//true
  6. let result3 = Reflect.preventExtensions(3)///抛出错误
属性描述符陷阱

</>复制代码

  1. let proxy = new Proxy({}, {
  2. defineProperty(trapTarget, key, descriptor) {
  3. if (typeof key === "symbol") {
  4. return false
  5. }
  6. return Reflect.defineProperty(trapTarget, key, descriptor)
  7. }
  8. })
  9. Object.defineProperty(proxy, "name", {
  10. value: "proxy"
  11. })
  12. console.log(proxy.name)//proxy
  13. let nameSymbol = Symbol("name")
  14. //抛错
  15. Object.defineProperty(proxy, nameSymbol, {
  16. value: "proxy"
  17. })

如果让陷阱返回true并且不调用Reflect.defineProperty方法,则可以让Object.defineProperty方法静默失效,这既消除了错误又不会真正定义属性
无论将什么参数作为第三个参数传递给Object.defineProperty方法都只有属性enumerable、configurable、value、writable、get和set将出现在传递给defineProperty陷阱的描述符对象中

</>复制代码

  1. let proxy = new Proxy({}, {
  2. defineProperty(trapTarget, key, descriptor) {
  3. console.log(descriptor)
  4. console.log(descriptor.value)
  5. console.log(descriptor.name)
  6. return Reflect.defineProperty(trapTarget, key, descriptor)
  7. }
  8. })
  9. Object.defineProperty(proxy, "name", {
  10. value: "proxy",
  11. name: "custom"
  12. })

getOwnPropertyDescriptor它的返回值必须是null、undefined或是一个对象,如果返回对象,则对象自己的属性只能是enumerable、configurable、value、writable、get和set,在返回的对象中使用不被允许的属性则会抛出一个错误

</>复制代码

  1. let proxy = new Proxy({}, {
  2. getOwnPropertyDescriptor(trapTarget, key) {
  3. //在返回的对象中使用不被允许的属性则会抛出一个错误
  4. return {
  5. name: "proxy"
  6. }
  7. }
  8. })
  9. let descriptor = Object.getOwnPropertyDescriptor(proxy, "name")

Object.defineProperty和Reflect.defineProperty只有返回值不同
Object.defineProperty返回第一个参数
Reflect.defineProperty的返回值与操作有关,成功则返回true,失败则返回false

</>复制代码

  1. let target = {}
  2. let result1 = Object.defineProperty(target, "name", { value: "target" })
  3. console.log(target === result1)//true
  4. let result2 = Reflect.defineProperty(target, "name", { value: "refelct" })
  5. console.log(result2)//false

Object.getOwnPropertyDescriptor如果传入原始值作为第一个参数,内部会将这个值强制转换成一个对象,若调用Reflect.getOwnPropertyDescriptor传入原始值作为第一个参数,则会抛出错误

ownKeys陷阱

</>复制代码

  1. let proxy = new Proxy({}, {
  2. ownKeys(trapTarget) {
  3. return Reflect.ownKeys(trapTarget).filter(key => {
  4. return typeof key !== "string" || key[0] !== "_"
  5. })
  6. }
  7. })
  8. let nameSymbol = Symbol("name")
  9. proxy.name = "proxy"
  10. proxy._name = "private"
  11. proxy[nameSymbol] = "symbol"
  12. let names = Object.getOwnPropertyNames(proxy),
  13. keys = Object.keys(proxy),
  14. symbols = Object.getOwnPropertySymbols(proxy)
  15. console.log(names)//["name"]
  16. console.log(keys)//["name"]
  17. console.log(symbols)//[Symbol(name)]

尽管ownKeys代理陷阱可以修改一小部分操作返回的键,但不影响更常用的操作,例如for of循环,这些不能使用代理为更改,ownKeys陷阱也会影响for in循环,当确定循环内部使用的键时会调用陷阱

函数代理中的apply和construct陷阱

</>复制代码

  1. let target = function () { return 42; },
  2. proxy = new Proxy(target, {
  3. apply: function (trapTarget, thisArg, argumentList) {
  4. return Reflect.apply(trapTarget, thisArg, argumentList)
  5. },
  6. construct: function (trapTarget, argumentList) {
  7. return Reflect.construct(trapTarget, argumentList)
  8. }
  9. });
  10. //一个目标是函数的代理看起来也像是一个函数
  11. console.log(typeof proxy)//function
  12. console.log(proxy())//42
  13. let instance=new proxy();
  14. //new创建一个instance对象,它同时是代理和目标的实例,因为instanceof通过原型链来确定此信息,而原型链查找不受代理影响,这也就是代理和目标好像有相同原型的原因
  15. console.log(instance instanceof proxy)//true
  16. console.log(instance instanceof target)//true

可以在apply陷阱中检查参数,在construct陷阱中来确认函数不会被new调用

</>复制代码

  1. function sum(...values) {
  2. return values.reduce((pre, cur) => pre + cur, 0)
  3. }
  4. let sumProxy = new Proxy(sum, {
  5. apply: function (trapTarget, thisArg, argumentList) {
  6. argumentList.forEach(arg => {
  7. if (typeof arg !== "number") {
  8. throw new TypeError("所有参数必须是数字。")
  9. }
  10. });
  11. return Reflect.apply(trapTarget, thisArg, argumentList)
  12. },
  13. construct: function (trapTarget, argumentList) {
  14. throw new TypeError("该函数不可通过new来调用")
  15. }
  16. })
  17. console.log(sumProxy(1, 2, 3, 4, 5))//15
  18. console.log(sumProxy(1, 2, "3", 4, 5))//抛出错误
  19. let result = new sumProxy()//抛出错误

以下例子是确保用new来调用函数并验证其参数为数字

</>复制代码

  1. function Numbers(...values) {
  2. this.values = values
  3. }
  4. let NumberProxy = new Proxy(Numbers, {
  5. apply: function (trapTarget, thisArg, argumentList) {
  6. throw new TypeError("该函数必须通过new来调用")
  7. },
  8. construct: function (trapTarget, argumentList) {
  9. argumentList.forEach(arg => {
  10. if (typeof arg !== "number") {
  11. throw new TypeError("所有参数必须是数字")
  12. }
  13. })
  14. return Reflect.construct(trapTarget, argumentList)
  15. }
  16. })
  17. let instance = new NumberProxy(12, 3, 4, 8)
  18. console.log(instance.values)// [12, 3, 4, 8]
  19. NumberProxy(1, 2, 3, 4)//报错

看一个不用new调用构造函数的例子:

</>复制代码

  1. function Numbers(...values) {
  2. if (typeof new.target === "undefined") {
  3. throw new TypeError("该函数必须通过new来调用")
  4. }
  5. this.values = values
  6. }
  7. let NumberProxy = new Proxy(Numbers, {
  8. apply: function (trapTarget, thisArg, argumentList) {
  9. return Reflect.construct(trapTarget, argumentList)
  10. }
  11. })
  12. let instance = NumberProxy(1, 2, 3, 4)
  13. console.log(instance.values)//[1,2,3,4]

覆写抽象基类构造函数

</>复制代码

  1. class AbstractNumbers {
  2. constructor(...values) {
  3. if (new.target === AbstractNumbers) {
  4. throw new TypeError("此函数必须被继承")
  5. }
  6. this.values = values
  7. }
  8. }
  9. class Numbers extends AbstractNumbers{}
  10. let instance = new Numbers(1,2,3,4,5)
  11. console.log(instance.values)//[1, 2, 3, 4, 5]
  12. new AbstractNumbers(1,2,3,4,5)//报错 此函数必须被继承

手动用代理给new.target赋值来绕过构造函数限制

</>复制代码

  1. class AbstractNumbers {
  2. constructor(...values) {
  3. if (new.target === AbstractNumbers) {
  4. throw new TypeError("此函数必须被继承")
  5. }
  6. this.values = values
  7. }
  8. }
  9. let AbstractNumbersProxy = new Proxy(AbstractNumbers, {
  10. construct: function (trapTarget, argumentList) {
  11. return Reflect.construct(trapTarget, argumentList, function () { })
  12. }
  13. })
  14. let instance = new AbstractNumbersProxy(1, 2, 3, 4)
  15. console.log(instance.values)//[1, 2, 3, 4]

可调用的类构造函数

</>复制代码

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. }
  6. let PersonProxy = new Proxy(Person, {
  7. apply: function (trapTarget, thisArg, argumentList) {
  8. return new trapTarget(...argumentList)
  9. }
  10. })
  11. let me = PersonProxy("angela")
  12. console.log(me.name)//angela
  13. console.log(me instanceof Person)//true
  14. console.log(me instanceof PersonProxy)//true

可撤销代理

</>复制代码

  1. let target = {
  2. name: "target"
  3. }
  4. let { proxy, revoke } = Proxy.revocable(target, {})
  5. console.log(proxy.name)//traget
  6. revoke()
  7. console.log(proxy.name)//报错
解决数组问题

</>复制代码

  1. function toUint32(value) {
  2. return Math.floor(Math.abs(Number(value))) % Math.pow(2, 32)
  3. }
  4. function isArrayIndex(key) {
  5. let numericKey = toUint32(key)
  6. return String(numericKey) == key && numericKey < (Math.pow(2, 32) - 1)
  7. }
  8. function createMyArray(length = 0) {
  9. return new Proxy({ length }, {
  10. set(trapTarget, key, value) {
  11. let currentLength = Reflect.get(trapTarget, "length")
  12. if (isArrayIndex(key)) {
  13. let numericKey = Number(key)
  14. if (numericKey >= currentLength) {
  15. Reflect.set(trapTarget, "length", numericKey + 1)
  16. }
  17. } else if (key === "length") {
  18. if (value < currentLength) {
  19. for (let index = currentLength - 1; index >= value; index--) {
  20. Reflect.deleteProperty(trapTarget, index)
  21. }
  22. }
  23. }
  24. Reflect.set(trapTarget, key, value)
  25. }
  26. })
  27. }
  28. let colors = createMyArray(3)
  29. colors[0] = "red"
  30. colors[1] = "green"
  31. colors[2] = "blue"
  32. console.log(colors.length)//3
  33. colors[3] = "black"
  34. console.log(colors[3])//black
  35. console.log(colors.length)//4
  36. colors.length = 1
  37. console.log(colors)//{0: "red", length: 1}
将代理用作原型

如果代理是原型,仅当默认操作继续执行到原型上时才调用代理陷阱,这会限制代理作为原型的能力
在原型上使用get陷阱

</>复制代码

  1. let target={}
  2. let thing=Object.create(new Proxy(target,{
  3. /**
  4. *
  5. *
  6. * @param {any} trapTarget 原型对象
  7. * @param {any} key
  8. * @param {any} receiver 实例对象
  9. */
  10. get(trapTarget,key,receiver){
  11. throw new ReferenceError(`${key} doesn"t exist`)
  12. }
  13. }))
  14. thing.name="thing"
  15. console.log(thing.name)//thing
  16. let unknown=thing.unknown//抛出错误

在原型上使用set陷阱

</>复制代码

  1. let target={}
  2. let thing=Object.create(new Proxy(target,{
  3. set(trapTarget,key,value,receiver){
  4. return Reflect.set(trapTarget,key,value,receiver)
  5. }
  6. }))
  7. console.log(thing.hasOwnProperty("name"))
  8. //触发set代理陷阱
  9. thing.name="thing"
  10. console.log(thing.name)
  11. console.log(thing.hasOwnProperty("name"))
  12. //不触发set代理陷阱
  13. thing.name="boo"
  14. console.log(thing.name)//boo

在原型上使用has陷阱

</>复制代码

  1. let target = {}
  2. let thing = Object.create(new Proxy(target, {
  3. has(trapTarget, key) {
  4. return Reflect.has(trapTarget, key)
  5. }
  6. }))
  7. //触发has代理陷阱
  8. console.log("name" in thing)//false
  9. thing.name = "thing"
  10. //不触发has代理陷阱
  11. console.log("name" in thing)//true

将代理用作类的原型

</>复制代码

  1. function NoSuchProperty(){}
  2. NoSuchProperty.prototype=new Proxy({},{
  3. get(trapTarget,key,receiver){
  4. throw new ReferenceError(`${key} doesn"t exist`)
  5. }
  6. })
  7. let thing=new NoSuchProperty()
  8. //在get代理陷阱中抛出错误
  9. let result=thing.name

</>复制代码

  1. function NoSuchProperty() { }
  2. NoSuchProperty.prototype = new Proxy({}, {
  3. get(trapTarget, key, receiver) {
  4. throw new ReferenceError(`${key} doesn"t exist`)
  5. }
  6. })
  7. class Square extends NoSuchProperty {
  8. constructor(length, width) {
  9. super()
  10. this.length = length;
  11. this.width = width
  12. }
  13. }
  14. let shape = new Square(2, 6)
  15. let area1 = shape.length * shape.width
  16. console.log(area1)//12
  17. let area2 = shape.length * shape.wdth//抛出错误

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/112595.html

相关文章

  • 深入理解ES6笔记(十一)代理(Proxy)反射(Reflection)API(12)

    摘要:是陷阱函数对应的反射方法,同时也是操作的默认行为。对象外形指的是对象已有的属性与方法的集合,由于该属性验证只须在读取属性时被触发,因此只要使用陷阱函数。无论该属性是对象自身的属性还是其原型的属性。 主要知识点:代理和反射的定义、常用的陷阱函数、可被撤销的代理、将代理对象作为原型使用、将代理作为类的原型showImg(https://segmentfault.com/img/bVbfWr...

    explorer_ddf 评论0 收藏0
  • 深入理解ES6》笔记——代理(Proxy)反射(Reflection)API(12)

    摘要:方法与代理处理程序的方法相同。使用给目标函数传入指定的参数。当然,不用反射也可以读取的值。的例子我们可以理解成是拦截了方法,然后传入参数,将返回值赋值给,这样我们就能在需要读取这个返回值的时候调用。这种代理模式和的代理有异曲同工之妙。 反射 Reflect 当你见到一个新的API,不明白的时候,就在浏览器打印出来看看它的样子。 showImg(https://segmentfault....

    ZHAO_ 评论0 收藏0
  • 深入理解ES6》笔记——代理(Proxy)反射(Reflection)API(12)

    摘要:方法与代理处理程序的方法相同。使用给目标函数传入指定的参数。当然,不用反射也可以读取的值。的例子我们可以理解成是拦截了方法,然后传入参数,将返回值赋值给,这样我们就能在需要读取这个返回值的时候调用。这种代理模式和的代理有异曲同工之妙。 反射 Reflect 当你见到一个新的API,不明白的时候,就在浏览器打印出来看看它的样子。 showImg(https://segmentfault....

    shiina 评论0 收藏0
  • 深入理解ES6代理反射

    摘要:使用陷阱验证属性用于接收属性代理的目标的对象要写入的属性键被写入的属性的值操作发生的对象通常是代理属性必须是数字抛错用陷阱验证对象结构属性不存在抛出错误使用陷阱隐藏已有属性可以用操作符来检测给定对象中是否包含有某个属性,如果自有属性或原型属 使用set陷阱验证属性 let target = { name: target } let proxy = new Proxy(targe...

    Stardustsky 评论0 收藏0

发表评论

0条评论

curried

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<