理解JavaScript中的Proxy 与 Reflection API


Posted in Javascript onSeptember 21, 2020

一、创建 Proxy

let target = {}
let proxy = new Proxy(target, {})

proxy.name = "proxy"
console.log(proxy.name)  // proxy
console.log(target.name) // proxy

target.name = "target"
console.log(proxy.name)  // target
console.log(target.name) // target

在上面的例子中,由 Proxy 构造器创建的 proxy 对象会将自身的所有操作直接转发给 target
proxy.name 被赋值为 "proxy" 时,target 对象也会创建 name 属性并获得同样的值。实际上 proxy 对象本身并不创建和存储 name 属性,它只是转发对应的操作给 target

类似的,proxy.name target.name 的值始终保持一致,因为它们实际上都指向了 target.name。这也意味着给 target.name 赋予一个新的值时,该变化也会反映到 proxy.name 上。

使用 set Trap 验证属性

Proxy 允许开发者主动拦截本该转发给 target 对象的底层操作,这些拦截行为通过 trap 实现。每个 trap 都可以覆盖 JavaScript 对象的某些内置行为,即 proxy 允许通过 trap 拦截并修改指向 target 对象的操作。

假设需要创建一个新添加的属性值只能是数字类型的对象,就可以借助 set trap 覆盖默认的赋值行为。代码如下:

let target = {
 name: "target"
}

let proxy = new Proxy(target, {
 set(trapTarget, key, value, receiver) {
  if (!trapTarget.hasOwnProperty(key)) {
   if (isNaN(value)) {
    throw new TypeError("New property must be a number.")
   }
  }
  return Reflect.set(trapTarget, key, value, receiver)
 }
})

proxy.count = 1
console.log(proxy.count)  // 1
console.log(target.count) // 1

proxy.name = "proxy"
console.log(proxy.name)  // proxy
console.log(target.name)  // proxy

proxy.anotherName = "proxy"
// TypeError: New property must be a number.

set trap 中的四个参数含义如下:

  • trapTarget:接收新属性的对象(即 proxy 指向的 target)
  • key:新属性对应的 key
  • value:新属性对应的 value
  • receiver:通常为 proxy 自身

Reflect.set() 是与 set trap 相对应的原始方法,表示被覆盖前的默认的赋值行为。

使用 get Trap 令程序读取不存在属性时报错

JavaScript 在读取不存在的属性时并不会报错,而是返回 undefined

let target = {}
console.log(target.name) // undefined

可以借助 get trap 修改读取对象属性时的默认行为:

let proxy = new Proxy({}, {
 get(trapTarget, key, receiver) {
  if (!(key in receiver)) {
   throw new TypeError("Property " + key + " doesn't exist.")
  }
  return Reflect.get(trapTarget, key, receiver)
 }
})

proxy.name = "proxy"
console.log(proxy.name) // proxy

console.log(proxy.nme)
// TypeError: Property nme doesn't exist.

通过 deleteProperty Trap 防止删除属性

JavaScript 中使用 delete 操作符删除对象的属性:

let target = {
 name: "target",
 value: 42
}

Object.defineProperty(target, "name", { configurable: false })
console.log("value" in target)  // true

let result1 = delete target.value
console.log(result1)       // true
console.log("value" in target)  // false

let result2 = delete target.name
console.log(result2)       // false
console.log("name" in target)   // true

使用 deleteProxy Trap 防止属性被意外删除:

let target = {
 name: "target",
 value: 42
}

let proxy = new Proxy(target, {
 deleteProperty(trapTarget, key) {
  if (key === "value") {
   return false
  } else {
   return Reflect.deleteProperty(trapTarget, key)
  }
 }
})

console.log("value" in proxy)  // true

let result1 = delete proxy.value
console.log(result1)       // false
console.log("value" in proxy)  // true

let result2 = delete proxy.name
console.log(result2)       // true
console.log("name" in proxy)   // false

二、Proxy 的现实应用

logging

function makeLoggable(target) {
 return new Proxy(target, {
  get: (target, property) => {
   console.log("Reading " + property)
   return target[property]
  },

  set: (target, property, value) => {
   console.log("Writing value " + value + " to " + property)
   target[property] = value
  }
 })
}

let ninja = { name: "Yoshi" }
ninja = makeLoggable(ninja)

console.log(ninja.name)
ninja.weapon = "sword"
// Reading name
// Yoshi
// Writing value sword to weapon

性能测试

function isPrime(number) {
 if (number < 2) { return false }

 for (let i = 2; i < number; i++) {
  if (number % i === 0) { return false }
 }
 return true
}

isPrime = new Proxy(isPrime, {
 apply: (target, thisArg, args) => {
  console.time("isPrime")
  const result = target.apply(thisArg, args)
  console.timeEnd("isPrime")
  return result
 }
})

console.log(isPrime(1358765377))
// isPrime: 6815.107ms
// true

自动添加属性

function Folder() {
 return new Proxy({}, {
  get: (target, property) => {
   console.log("Reading " + property)

   if(!(property in target)) {
    target[property] = new Folder()
   }
   return target[property]
  }
 })
}

const rootFolder = new Folder()
rootFolder.ninjasDir.firstNinjaDir.ninjaFile = "yoshi.txt"
// Reading ninjasDir
// Reading firstNinjaDir
console.log(rootFolder.ninjasDir.firstNinjaDir.ninjaFile)
// Reading ninjasDir
// Reading firstNinjaDir
// Reading ninjaFile
// yoshi.txt

参考资料

https://leanpub.com/understandinges6

https://www.manning.com/books/secrets-of-the-javascript-ninja-second-edition

以上就是理解JavaScript中的Proxy 与 Reflection API的详细内容,更多关于JavaScript中的Proxy 与 Reflection API的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
javascritp实现input输入框相关限制用法
Jun 29 Javascript
javascript控制图片播放的实现代码
Jul 29 Javascript
轻松掌握jQuery中wrap()与unwrap()函数的用法
May 24 Javascript
jQuery+CSS3文字跑马灯特效的简单实现
Jun 25 Javascript
如何使用headjs来管理和异步加载js
Nov 29 Javascript
JavaScript动态绑定详解
Sep 14 Javascript
微信小程序实现鼠标拖动效果示例
Dec 01 Javascript
浅谈Postman解决token传参的问题
Mar 31 Javascript
webpack4 css打包压缩问题的解决
May 18 Javascript
jQuery实现简单的Ajax调用功能示例
Feb 15 jQuery
vue 通过 Prop 向子组件传递数据的实现方法
Oct 30 Javascript
vue 获取url参数、get参数返回数组的操作
Nov 12 Javascript
vue实践---根据不同环境,自动转换请求的url地址操作
Sep 21 #Javascript
vue实践---vue不依赖外部资源实现简单多语操作
Sep 21 #Javascript
js节流防抖应用场景,以及在vue中节流防抖的具体实现操作
Sep 21 #Javascript
微信小程序实现翻牌抽奖动画
Sep 21 #Javascript
不依任何赖第三方,单纯用vue实现Tree 树形控件的案例
Sep 21 #Javascript
微信小程序实现转盘抽奖
Sep 21 #Javascript
结合axios对项目中的api请求进行封装操作
Sep 21 #Javascript
You might like
基于php权限分配的实现代码
2013/04/28 PHP
深入解析PHP的引用计数机制
2013/06/14 PHP
php导出word格式数据的代码实例
2013/11/25 PHP
php管理nginx虚拟主机shell脚本实例
2014/11/19 PHP
PHP+MySQL实现模糊查询员工信息功能示例
2018/06/01 PHP
laravel 根据不同组织加载不同视图的实现
2019/10/14 PHP
Javascript与flash交互通信基础教程
2008/08/07 Javascript
jquery网页回到顶部效果(图标渐隐,自写)
2014/06/16 Javascript
JavaScript检查弹出窗口是否被阻拦的方法技巧
2015/03/13 Javascript
jQuery同步提交示例代码
2015/12/12 Javascript
关于input全选反选恶心的异常情况
2016/07/24 Javascript
Node.js下自定义错误类型详解
2016/10/17 Javascript
Layui table 组件的使用之初始化加载数据、数据刷新表格、传参数
2017/09/11 Javascript
如何在Angular应用中创建包含组件方法示例
2019/03/23 Javascript
node.js中npm包管理工具用法分析
2020/02/14 Javascript
token 机制和实现方式
2020/12/15 Javascript
[42:35]2018DOTA2亚洲邀请赛3月30日 小组赛A组 VG VS OpTic
2018/03/31 DOTA
修改默认的pip版本为对应python2.7的方法
2018/11/06 Python
python_opencv用线段画封闭矩形的实例
2018/12/05 Python
详解如何减少python内存的消耗
2019/08/09 Python
Python 读取 YUV(NV12) 视频文件实例
2019/12/09 Python
python实现梯度下降和逻辑回归
2020/03/24 Python
详解Python IO编程
2020/07/24 Python
外语专业毕业生个人的自荐信
2013/11/19 职场文书
教师的实习自我鉴定
2013/12/17 职场文书
小学生开学第一课活动方案
2014/03/27 职场文书
《少年王勃》教学反思
2014/04/27 职场文书
公司员工辞职信范文
2015/05/12 职场文书
单位同意报考证明
2015/06/17 职场文书
2015年三好一满意工作总结
2015/07/24 职场文书
2016年学校“3.12”植树节活动总结
2016/03/16 职场文书
2020年元旦祝福语录,总有适合你的
2019/12/31 职场文书
python机器学习Github已达8.9Kstars模型解释器LIME
2021/11/23 Python
canvas实现贪食蛇的实践
2022/02/15 Javascript
一行Python命令实现批量加水印
2022/04/07 Python
《艾尔登法环》Boss腐烂树灵很有可能是《黑暗之魂3》的一个废案
2022/04/11 其他游戏