理解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 相关文章推荐
经典的带阴影的可拖动的浮动层
Jun 26 Javascript
Javascript中valueOf与toString区别浅析
Mar 19 Javascript
Js实现自定义右键行为
Mar 26 Javascript
详解vue2.0组件通信各种情况总结与实例分析
Mar 22 Javascript
Vue学习笔记进阶篇之vue-cli安装及介绍
Jul 18 Javascript
vue中的scope使用详解
Oct 29 Javascript
JS中offset和匀速动画详解
Feb 06 Javascript
Vue实现点击时间获取时间段查询功能
Aug 21 Javascript
详解Vue-cli3 项目在安卓低版本系统和IE上白屏问题解决
Apr 14 Javascript
Vue拖拽组件列表实现动态页面配置功能
Jun 17 Javascript
JavaScript进阶(四)原型与原型链用法实例分析
May 09 Javascript
JSONP解决JS跨域问题的实现
May 25 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
php4的session功能评述(二)
2006/10/09 PHP
php 验证码实例代码
2010/06/01 PHP
深入PHP获取随机数字和字母的方法详解
2013/06/06 PHP
PHP设计模式之单例模式定义与用法分析
2019/03/26 PHP
DEFER怎么用?
2006/07/01 Javascript
关于jQuery新的事件绑定机制on()的使用技巧
2013/04/26 Javascript
非常好用的JsonToString 方法 简单实例
2013/07/18 Javascript
调整小数的格式保留小数点后两位
2014/05/14 Javascript
从JQuery源码分析JavaScript函数的apply方法与call方法
2014/09/25 Javascript
Javascript学习笔记之函数篇(四):arguments 对象
2014/11/23 Javascript
JavaScript删除数组元素的方法
2015/03/20 Javascript
js获得当前系统日期时间的方法
2015/05/06 Javascript
Javascript编写俄罗斯方块思路及实例
2015/07/07 Javascript
Bootstrap每天必学之标签与徽章
2015/11/27 Javascript
利用angularjs1.4制作的简易滑动门效果
2017/02/28 Javascript
webpack学习教程之publicPath路径问题详解
2017/06/17 Javascript
深入理解node.js http模块
2018/01/24 Javascript
vue配置接口域名方法总结
2019/05/12 Javascript
js实现蒙版效果
2020/01/11 Javascript
[06:24]DOTA2亚洲邀请赛小组赛第三日 TOP10精彩集锦
2015/02/01 DOTA
[51:53]DOTA2-DPC中国联赛 正赛 RNG vs Dragon BO3 第二场 1月24日
2021/03/11 DOTA
python中WSGI是什么,Python应用WSGI详解
2017/11/24 Python
Python中super函数用法实例分析
2019/03/18 Python
pytorch 图像预处理之减去均值,除以方差的实例
2020/01/02 Python
python下载卫星云图合成gif的方法示例
2020/02/18 Python
Python动态导入模块和反射机制详解
2020/02/18 Python
python实现信号时域统计特征提取代码
2020/02/26 Python
在服务器上安装python3.8.2环境的教程详解
2020/04/26 Python
解决echarts中饼图标签重叠的问题
2020/05/16 Python
Django --Xadmin 判断登录者身份实例
2020/07/03 Python
python 字符串格式化的示例
2020/09/21 Python
意大利制造的西装、衬衫和针对男士量身定制的服装:Lanieri
2018/04/08 全球购物
公务员群众路线专题民主生活会发言材料
2014/09/17 职场文书
党的群众路线教育实践活动教师自我剖析材料
2014/10/09 职场文书
圆明园观后感
2015/06/03 职场文书
2015年高中语文教学总结
2015/08/18 职场文书