理解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 ajax cache缓存问题
Jul 01 Javascript
js setTimeout 参数传递使用介绍
Aug 13 Javascript
js使用eval解析json(js中使用json)
Jan 17 Javascript
点击弹出层效果&amp;弹出窗口后网页背景变暗效果的实现代码
Feb 10 Javascript
jquery delay()介绍及使用指南
Sep 02 Javascript
可以浮动某个物体的jquery控件用法实例
Jul 24 Javascript
AngularJS 入门教程之HTML DOM实例详解
Jul 28 Javascript
ionic cordova一次上传多张图片(类似input file提交表单)的实现方法
Dec 16 Javascript
AngularJS 验证码60秒倒计时功能的实现
Jun 05 Javascript
vue.js template模板的使用(仿饿了么布局)
Aug 13 Javascript
利用Angular2的Observables实现交互控制的方法
Dec 27 Javascript
JS中队列和双端队列实现及应用详解
Sep 29 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+MySQL5.0中文乱码解决方法
2006/11/20 PHP
ThinkPHP在新浪SAE平台的部署实例
2014/10/31 PHP
ASP Json Parser修正版
2009/12/06 Javascript
JQuery获取浏览器窗口内容部分高度的代码
2012/02/24 Javascript
你需要知道的10个最佳javascript开发实践小结
2012/04/15 Javascript
Javascript 遮罩层和加载效果代码
2013/08/01 Javascript
JS动态增加删除UL节点LI及相关内容示例
2014/05/21 Javascript
iframe如何动态创建及释放其所占内存
2014/09/03 Javascript
JavaScript判断textarea值是否为空并给出相应提示
2014/09/04 Javascript
基于javascript实现判断移动终端浏览器版本信息
2014/12/09 Javascript
什么是JavaScript中的结果值?
2016/10/08 Javascript
原生js实现无缝轮播图效果
2017/01/11 Javascript
JavaScript实现简易的天数计算器实例【附demo源码下载】
2017/01/18 Javascript
浅谈JavaScript异步编程
2017/01/20 Javascript
Node.js+ES6+dropload.js实现移动端下拉加载实例
2017/06/01 Javascript
js实现随机点名小功能
2017/08/17 Javascript
使用vue-cli(vue脚手架)快速搭建项目的方法
2018/05/21 Javascript
vue导入.md文件的步骤(markdown转HTML)
2020/12/31 Vue.js
Python ORM框架SQLAlchemy学习笔记之映射类使用实例和Session会话介绍
2014/06/10 Python
Python写的贪吃蛇游戏例子
2014/06/16 Python
深入浅析python 中的匿名函数
2018/05/21 Python
python 中如何获取列表的索引
2019/07/02 Python
详解用python生成随机数的几种方法
2019/08/04 Python
opencv python图像梯度实例详解
2020/02/04 Python
python实现俄罗斯方块小游戏
2020/04/24 Python
美国领先的在线旅游网站:Orbitz
2018/11/05 全球购物
Dr. Martens马汀博士德国官网:马丁靴鼻祖
2019/12/26 全球购物
客户代表实习人员自我鉴定
2013/09/27 职场文书
医学检验专业个人求职信范文
2013/12/04 职场文书
《春到梅花山》教学反思
2014/04/16 职场文书
篮球比赛策划方案
2014/06/05 职场文书
委托培训协议书
2014/11/17 职场文书
复兴之路纪录片观后感
2015/06/02 职场文书
2016年乡镇综治宣传月活动总结
2016/03/16 职场文书
解决Navicat for Mysql连接报错1251的问题(连接失败)
2021/05/27 MySQL
Java实现HTML转为Word的示例代码
2022/06/28 Java/Android