理解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 相关文章推荐
JS backgroundImage控制
May 19 Javascript
jQuery基本过滤选择器使用介绍
Apr 18 Javascript
jquery实现点击消失的代码
Mar 03 Javascript
jquery解析JSON数据示例代码
Mar 17 Javascript
Javascript writable特性介绍
Feb 27 Javascript
JS实现密码框根据焦点的获取与失去控制文字的消失与显示效果
Nov 26 Javascript
js实时获取窗口大小变化的实例代码
Nov 18 Javascript
使用Bootstrap美化按钮实例代码(demo)
Feb 03 Javascript
Vue.js如何实现路由懒加载浅析
Aug 14 Javascript
详解angularjs 学习之 scope作用域
Jan 15 Javascript
Element-UI踩坑之Pagination组件的使用
Oct 29 Javascript
Vue仿百度搜索功能
Dec 28 Vue.js
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 strstr查找字符串中是否包含某些字符的查找函数
2010/06/03 PHP
laravel5.0在linux下解决.htaccess无效和去除index.php的问题
2019/10/16 PHP
PHP实现简单登录界面
2019/10/23 PHP
动态加载js文件 document.createElement
2006/10/14 Javascript
js loading加载效果实现代码
2009/11/24 Javascript
封装的原生javascript弹出层代码
2010/09/24 Javascript
Jquery Uploadify多文件上传带进度条且传递自己的参数
2013/08/28 Javascript
基于JQuery实现的图片自动进行缩放和裁剪处理
2014/01/31 Javascript
Vue中保存用户登录状态实例代码
2017/06/07 Javascript
vue Render中slots的使用的实例代码
2017/07/19 Javascript
vue中锚点的三种方法
2018/07/06 Javascript
Vue使用axios出现options请求方法
2019/05/30 Javascript
JavaScrip数组去重操作实例小结
2019/06/20 Javascript
VueJS 取得 URL 参数值的方法
2019/07/19 Javascript
vue 动态生成拓扑图的示例
2021/01/03 Vue.js
python根据给定文件返回文件名和扩展名的方法
2015/03/27 Python
Python批量重命名同一文件夹下文件的方法
2015/05/25 Python
Python+Pika+RabbitMQ环境部署及实现工作队列的实例教程
2016/06/29 Python
Django项目中model的数据处理以及页面交互方法
2018/05/30 Python
基于python指定包的安装路径方法
2018/10/27 Python
python pcm音频添加头转成Wav格式文件的方法
2019/01/09 Python
详解python:time模块用法
2019/03/25 Python
python实现从本地摄像头和网络摄像头截取图片功能
2019/07/11 Python
postman模拟访问具有Session的post请求方法
2019/07/15 Python
python获取栅格点和面值的实现
2020/03/10 Python
利用Python制作动态排名图的实现代码
2020/04/09 Python
python 元组的使用方法
2020/06/09 Python
波兰灯具、照明和LED购物网站:Lampy.pl
2019/03/11 全球购物
AJax面试题
2014/11/25 面试题
员工薪酬福利制度
2014/01/17 职场文书
新学期校长寄语
2014/01/18 职场文书
联欢晚会主持词
2014/03/25 职场文书
2015年绩效考核工作总结
2015/05/23 职场文书
村官2015年度工作总结
2015/10/14 职场文书
使用Djongo模块在Django中使用MongoDB数据库
2021/06/20 Python
Python+Selenium实现抖音、快手、B站、小红书、微视、百度好看视频、西瓜视频、微信视频号、搜狐视频、一点号、大风号、趣头条等短视频自动发布
2022/04/13 Python