理解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 相关文章推荐
jQuery选中select控件 无法设置selected的解决方法
Sep 01 Javascript
JS cookie中文乱码解决方法
Jan 28 Javascript
js实现下拉框选择要显示图片的方法
Feb 16 Javascript
jQuery根据用户电脑是mac还是pc加载对应样式的方法
Jun 26 Javascript
jQuery Validate验证框架经典大全
Sep 23 Javascript
基于jQuery实现响应式圆形图片轮播特效
Nov 25 Javascript
Javascript技术栈中的四种依赖注入小结
Feb 27 Javascript
关于javascript的一些知识以及循环详解
Sep 12 Javascript
Vuex之理解Store的用法
Apr 19 Javascript
自定义事件解决重复请求BUG的问题
Jul 11 Javascript
解决vue scoped scss 无效的问题
Sep 04 Javascript
Vue如何实现验证码输入交互
Dec 07 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
在windows iis5下安装php4.0+mysql之我见
2006/10/09 PHP
echo(),print(),print_r()之间的区别?
2006/11/19 PHP
使用XDebug调试及单元测试覆盖率分析
2011/01/27 PHP
Smarty变量用法详解
2016/05/11 PHP
jquery图片上下tab切换效果
2011/03/18 Javascript
javascript向flash swf文件传递参数值注意细节
2012/12/11 Javascript
下拉菜单点击实现连接跳转功能的js代码
2013/05/19 Javascript
Js数组排序函数sort()介绍
2015/06/08 Javascript
用 js 的 selection range 操作选择区域内容和图片
2017/04/18 Javascript
自制简易打赏功能的实例
2017/09/02 Javascript
node.js学习笔记之koa框架和简单爬虫练习
2018/12/13 Javascript
基于JS实现table导出Excel并保留样式
2020/05/19 Javascript
详解JavaScript之Array.reduce源码解读
2020/11/01 Javascript
Vue如何实现验证码输入交互
2020/12/07 Vue.js
Python写的Socks5协议代理服务器
2014/08/06 Python
解决python文件字符串转列表时遇到空行的问题
2017/07/09 Python
python实现对求解最长回文子串的动态规划算法
2018/06/02 Python
Python 异步协程函数原理及实例详解
2019/11/13 Python
html5的localstorage详解
2017/05/09 HTML / CSS
免费获得微软MCSD证书赶快行动吧!
2012/11/13 HTML / CSS
Carmen Sol官网:购买果冻鞋、手袋和配件
2021/01/01 全球购物
薇姿法国官网:Vichy法国
2021/01/28 全球购物
Linux内核的同步机制是什么?主要有哪几种内核锁
2016/07/11 面试题
室内设计实习自我鉴定
2013/09/25 职场文书
人事主管岗位职责范本
2013/12/04 职场文书
实践单位评语
2014/04/26 职场文书
推荐信格式要求
2014/05/09 职场文书
普通党员个人整改措施
2014/10/27 职场文书
2016年会开场白台词
2015/06/01 职场文书
西安事变观后感
2015/06/12 职场文书
2015年中学总务处工作总结
2015/07/22 职场文书
MySQL中distinct和count(*)的使用方法比较
2021/05/26 MySQL
JavaScript实例 ODO List分析
2022/01/22 Javascript
Mysql排查分析慢sql之explain实战案例
2022/04/19 MySQL
vue-cli3.x配置全局的scss的时候报错问题及解决
2022/04/30 Vue.js
MySQL存储过程及语法详解
2022/08/05 MySQL