理解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 validator 插件增加日期比较方法
Feb 21 Javascript
JS控制图片等比例缩放的示例代码
Dec 24 Javascript
node.js中的favicon.ico请求问题处理
Dec 15 Javascript
更高效的使用JQuery 这里总结了8个小技巧
Apr 13 Javascript
JavaScript实现精美个性导航栏筋斗云效果
Oct 29 Javascript
解决layer弹层遮罩挡住窗体的问题
Aug 17 Javascript
JavaScript学习笔记之基于定时器实现图片无缝滚动功能详解
Jan 09 Javascript
如何写好一个vue组件,老夫的一年经验全在这了(推荐)
May 18 Javascript
JS实现求字符串中出现最多次数的字符和次数示例
Jul 05 Javascript
vuejs移动端实现div拖拽移动
Jul 25 Javascript
antd日期选择器禁止选择当天之前的时间操作
Oct 29 Javascript
vant组件中 dialog的确认按钮的回调事件操作
Nov 04 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+DBM的同学录程序(4)
2006/10/09 PHP
php中如何判断一个网页请求是ajax请求还是普通请求
2013/08/10 PHP
PHP生成自定义长度随机字符串的函数分享
2014/05/04 PHP
Laravel实现用户注册和登录
2015/01/23 PHP
php反射类ReflectionClass用法分析
2016/05/12 PHP
如何使用Javascript正则表达式来格式化XML内容
2013/07/04 Javascript
javascript中style.left和offsetLeft的用法说明
2014/03/07 Javascript
用nodejs实现PHP的print_r函数代码
2014/03/14 NodeJs
Javascript访问器属性实例分析
2014/12/30 Javascript
分享一个插件实现水珠自动下落效果
2016/06/01 Javascript
基于chosen插件实现人员选择树搜索自动筛选功能
2016/09/24 Javascript
JS设计模式之单例模式(一)
2017/09/29 Javascript
基于vue实现网站前台的权限管理(前后端分离实践)
2018/01/13 Javascript
Vue前端开发规范整理(推荐)
2018/04/23 Javascript
vue.js input框之间赋值方法
2018/08/24 Javascript
前端防止用户重复提交js实现代码示例
2018/09/07 Javascript
浅谈js中的bind
2019/03/18 Javascript
VUE 实现element upload上传图片到阿里云
2020/08/12 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
2020/09/10 Javascript
Python中的localtime()方法使用详解
2015/05/22 Python
python使用urllib2提交http post请求的方法
2015/05/26 Python
Python生成器以及应用实例解析
2018/02/08 Python
python广度优先搜索得到两点间最短路径
2019/01/17 Python
快速排序的四种python实现(推荐)
2019/04/03 Python
Python facenet进行人脸识别测试过程解析
2019/08/16 Python
django queryset相加和筛选教程
2020/05/18 Python
Python同时处理多个异常的方法
2020/07/28 Python
python开发一款翻译工具
2020/10/10 Python
Trip.com澳大利亚:在线旅行社
2019/12/01 全球购物
继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
2015/11/18 面试题
法警的竞聘演讲稿
2014/01/02 职场文书
临床护士自荐信
2014/01/31 职场文书
优秀公益广告词大全
2014/03/19 职场文书
教师作风整改措施思想汇报
2014/10/12 职场文书
详解python字符串驻留技术
2021/05/21 Python
Win11任务栏太宽了怎么办?一招解决Win11任务栏太宽问题
2021/11/21 数码科技