理解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性能优化28条建议你值得借鉴
Feb 16 Javascript
解析Javascript中中括号“[]”的多义性
Dec 03 Javascript
jQuery web 组件 后台日历价格、库存设置的代码
Oct 14 Javascript
Vue中添加手机验证码组件功能操作方法
Dec 07 Javascript
用vue写一个仿简书的轮播图的示例代码
Mar 13 Javascript
angularJs中跳转到指定的锚点实例($anchorScroll)
Aug 31 Javascript
vue自定v-model实现表单数据双向绑定问题
Sep 03 Javascript
vue中typescript装饰器的使用方法超实用教程
Jun 17 Javascript
关于JSON解析的实现过程解析
Oct 08 Javascript
vue中axios防止多次触发终止多次请求的示例代码(防抖)
Feb 16 Javascript
Node.js API详解之 querystring用法实例分析
Apr 29 Javascript
手写Vue2.0 数据劫持的示例
Mar 04 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
codeigniter使用技巧批量插入数据实例方法分享
2013/12/31 PHP
ThinkPHP框架获取最后一次执行SQL语句及变量调试简单操作示例
2018/06/13 PHP
jquery插件之easing 动态菜单
2010/08/21 Javascript
基于jquery的滑动样例代码
2010/11/20 Javascript
Javascript类定义语法,私有成员、受保护成员、静态成员等介绍
2011/12/08 Javascript
js中escape对应的C#解码函数 UrlDecode
2012/12/16 Javascript
js Select下拉列表框进行多选、移除、交换内容的具体实现方法
2013/08/13 Javascript
jQuery中filter()方法用法实例
2015/01/06 Javascript
jQuery模拟新浪微博首页滚动效果的方法
2015/03/11 Javascript
javascript实现链接单选效果的方法
2015/05/13 Javascript
jQuery实现可展开折叠的导航效果示例
2016/09/12 Javascript
微信小程序 Video API实例详解
2016/10/02 Javascript
Vue.Js中的$watch()方法总结
2017/03/23 Javascript
Nuxt配合Node在实际生产中的应用详解
2018/08/07 Javascript
Vue列表循环从指定下标开始的多种解决方案
2020/04/08 Javascript
JavaScript设计模式--简单工厂模式定义与应用案例详解
2020/05/23 Javascript
小程序实现简单语音聊天的示例代码
2020/07/24 Javascript
vue 获取元素额外生成的data-v-xxx操作
2020/09/09 Javascript
python根据出生年份简单计算生肖的方法
2015/03/27 Python
初步解析Python中的yield函数的用法
2015/04/03 Python
Python 用三行代码提取PDF表格数据
2019/10/13 Python
Python如何使用BeautifulSoup爬取网页信息
2019/11/26 Python
基于python读取.mat文件并取出信息
2019/12/16 Python
Python要如何实现列表排序的几种方法
2020/02/21 Python
基于python实现操作git过程代码解析
2020/07/27 Python
HTML5 canvas基本绘图之图形变换
2016/06/27 HTML / CSS
html5手机端页面可以向右滑动导致样式受影响的问题
2018/06/20 HTML / CSS
西班牙三叶草药房:Farmacias Trébol
2019/05/03 全球购物
UNIX文件名称有什么规定
2013/03/25 面试题
地理科学专业毕业生求职信
2013/10/15 职场文书
双十佳事迹材料
2014/01/29 职场文书
户外婚礼策划方案
2014/02/08 职场文书
体育运动口号
2014/06/09 职场文书
补充协议书
2015/01/28 职场文书
2015年秋季灭鼠工作总结
2015/07/27 职场文书
sql查询语句之平均分、最高最低分及排序语句
2022/05/30 MySQL