理解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 相关文章推荐
在 IE 中调用 javascript 打开 Excel 表
Dec 21 Javascript
学习ExtJS Panel常用方法
Oct 07 Javascript
JavaScript 设计模式之组合模式解析
Apr 09 Javascript
AJAX异步从优酷专辑中采集所有视频及信息(JavaScript代码)
Nov 20 Javascript
js全屏显示显示代码的三种方法
Nov 11 Javascript
防止登录页面出现在frame中js代码
Jul 22 Javascript
jquery ajaxfileupload异步上传插件使用详解
Feb 08 Javascript
剖析Angular Component的源码示例
Mar 23 Javascript
vue-rx的初步使用教程
Sep 21 Javascript
vue实现商品列表的添加删除实例讲解
May 14 Javascript
用vue 实现手机触屏滑动功能
May 28 Javascript
vue 将多个过滤器封装到一个文件中的代码详解
Sep 05 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/08/06 PHP
PHP生成指定长度随机数最简洁的方法
2014/07/14 PHP
php保存二进制原始数据为图片的程序代码
2014/10/14 PHP
PHP查询大量数据内存耗尽问题的解决方法
2016/10/28 PHP
基于javascipt-dom编程 table对象的使用
2013/04/22 Javascript
jquery实现div阴影效果示例代码
2013/09/16 Javascript
javascript生成大小写字母
2015/07/03 Javascript
轻松实现js弹框显示选项
2016/09/13 Javascript
scroll事件实现监控滚动条并分页显示(zepto.js)
2016/12/18 Javascript
基于NodeJS+MongoDB+AngularJS+Bootstrap开发书店案例分析
2017/01/12 NodeJs
基于LayUI分页和LayUI laypage分页的使用示例
2017/08/02 Javascript
NodeJs form-data格式传输文件的方法
2017/12/13 NodeJs
vue-router history模式下的微信分享小结
2018/07/05 Javascript
详解vue axios二次封装
2018/07/22 Javascript
微信小程序实现写入读取缓存详解
2019/08/30 Javascript
layer.open弹层查看缩略图的原图,自适应大小的实例
2019/09/05 Javascript
解决layer弹出层的内容页点击按钮跳转到新的页面问题
2019/09/14 Javascript
vue-router之实现导航切换过渡动画效果
2019/10/31 Javascript
vue绑定class的三种方法
2020/12/24 Vue.js
[03:07]【DOTA2亚洲邀请赛】我们,梦开始的地方
2017/03/07 DOTA
[01:06:59]完美世界DOTA2联赛PWL S2 Magma vs FTD 第一场 11.29
2020/12/02 DOTA
Python实现FTP上传文件或文件夹实例(递归)
2017/01/16 Python
python对DICOM图像的读取方法详解
2017/07/17 Python
深入理解Python中的super()方法
2017/11/20 Python
Python设计模式之代理模式简单示例
2018/01/09 Python
Python全排列操作实例分析
2018/07/24 Python
python隐藏类中属性的3种实现方法
2019/12/19 Python
关于Python turtle库使用时坐标的确定方法
2020/03/19 Python
加拿大户外探险购物网站:SAIL
2020/06/27 全球购物
计算机应用专业毕业生求职信
2014/06/03 职场文书
镇党政领导班子民主生活会思想汇报
2014/10/11 职场文书
个人年底工作总结
2015/03/10 职场文书
黄河绝恋观后感
2015/06/08 职场文书
领导干部学习三严三实心得体会
2016/01/05 职场文书
React Fragment介绍与使用详解
2021/11/11 Javascript
vue实现省市区联动 element-china-area-data插件
2022/04/22 Vue.js