理解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 相关文章推荐
javascript 函数式编程
Aug 16 Javascript
JavaScript格式化数字的函数代码
Nov 30 Javascript
js正则表达式中test,exec,match方法的区别说明
Jan 29 Javascript
jquery中ajax使用error调试错误的方法
Feb 08 Javascript
node.js中fs.stat与fs.fstat的区别详解
Jun 01 Javascript
微信小程序实现图片预览功能
Jan 31 Javascript
element-ui 表格实现单元格可编辑的示例
Feb 26 Javascript
详解如何在Node.js的httpServer中接收前端发送的arraybuffer数据
Nov 11 Javascript
Vue 3.x+axios跨域方案的踩坑指南
Jul 04 Javascript
Node.js API详解之 dgram模块用法实例分析
Jun 05 Javascript
解决vue props传Array/Object类型值,子组件报错的情况
Nov 07 Javascript
原生JS实现京东查看商品点击放大
Dec 21 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
在模板页面的js使用办法
2010/04/01 Javascript
10个基于Jquery的幻灯片插件教程
2010/10/29 Javascript
jquery 选择器引擎sizzle浅析
2013/02/06 Javascript
Javascript事件实例详解
2013/11/06 Javascript
jquery实现用户打分评分特效
2015/05/28 Javascript
对于jQuery性能的一些优化建议
2015/08/13 Javascript
详解JavaScript正则表达式之分组匹配及反向引用
2016/03/09 Javascript
浅谈JS的基础类型与引用类型
2016/09/13 Javascript
JavaScript中绑定事件的三种方式及去除绑定
2016/11/05 Javascript
jQuery验证表单格式的使用方法
2017/01/10 Javascript
element-ui循环显示radio控件信息的方法
2018/08/24 Javascript
微信小程序保存多张图片的实现方法
2019/03/05 Javascript
了解前端理论:rscss和rsjs
2019/05/23 Javascript
vue.js iview打包上线后字体图标不显示解决办法
2020/01/20 Javascript
Python 操作MySQL详解及实例
2017/04/30 Python
Python模拟三级菜单效果
2017/09/11 Python
python shell命令行中import多层目录下的模块操作
2020/03/09 Python
python右对齐的实例方法
2020/07/05 Python
python如何建立全零数组
2020/07/19 Python
基于Python的一个自动录入表格的小程序
2020/08/05 Python
HTML5中的网络存储实现方式
2020/04/28 HTML / CSS
美国著名手表网站:Timepiece
2017/11/15 全球购物
《小儿垂钓》教学反思
2014/02/23 职场文书
秋季运动会演讲稿
2014/09/16 职场文书
法定代表人授权委托书范文
2014/09/22 职场文书
家庭困难证明
2014/10/12 职场文书
2014年保洁员工作总结
2014/11/19 职场文书
2014年教研工作总结
2014/12/06 职场文书
贷款担保书
2015/01/20 职场文书
材料员岗位职责范本
2015/04/11 职场文书
捐款仪式主持词
2015/07/04 职场文书
个人业务学习心得体会
2016/01/25 职场文书
vue组件的路由高亮问题解决方法
2021/05/11 Vue.js
MySQL的Query Cache图文详解
2021/07/01 MySQL
MySQL插入数据与查询数据
2022/03/25 MySQL
mysql数据库如何转移到oracle
2022/12/24 MySQL