理解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 相关文章推荐
Input 特殊事件onpopertychange和oninput
Jun 17 Javascript
JavaScript类和继承 constructor属性
Mar 04 Javascript
基于Jquery的淡入淡出的特效基础练习
Dec 13 Javascript
JavaScript数组对象实现增加一个返回随机元素的方法
Jul 27 Javascript
Bootstrap自动适应PC、平板、手机的Bootstrap栅格系统
May 27 Javascript
bootstrap table表格使用方法详解
Apr 26 Javascript
通俗解释JavaScript正则表达式快速记忆
Aug 23 Javascript
vue.js仿hover效果的实现方法示例
Jan 28 Javascript
jquery实现选项卡切换代码实例
May 14 jQuery
LayUI数据接口返回实体封装的例子
Sep 12 Javascript
Vue实现图片轮播组件思路及实例解析
May 11 Javascript
关于AngularJS中几种Providers的区别总结
May 17 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
WINDOWS下php5.2.4+mysql6.0+apache2.2.4+ZendOptimizer-3.3.0配置
2008/03/28 PHP
php数组函数序列之array_key_exists() - 查找数组键名是否存在
2011/10/29 PHP
LotusPhp笔记之:Cookie组件的使用详解
2013/05/06 PHP
php对数组排序的简单实例
2013/12/25 PHP
PHP获取本周第一天和最后一天示例代码
2014/02/24 PHP
Laravel中使用阿里云OSS Composer包分享
2015/02/10 PHP
PHP实现登陆表单提交CSRF及验证码
2017/01/24 PHP
雄兵连第三季海报曝光,艾妮熙德成主角,蔷薇新造型
2021/03/09 国漫
json-lib出现There is a cycle in the hierarchy解决办法
2010/02/24 Javascript
Javascript中this关键字的一些小知识
2015/03/15 Javascript
javascript发送短信验证码实现代码
2015/11/12 Javascript
Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定)
2016/11/22 Javascript
简单实现js选项卡切换效果
2017/02/09 Javascript
详解nodejs微信公众号开发——4.自动回复各种消息
2017/04/11 NodeJs
vue中的event bus非父子组件通信解析
2017/10/27 Javascript
使用微信SDK自定义分享的方法
2019/07/03 Javascript
解决layui弹出层layer的area过大被遮挡的问题
2019/09/21 Javascript
jQuery 淡入/淡出效果函数用法分析
2020/05/19 jQuery
Python实现的Kmeans++算法实例
2014/04/26 Python
详细讲解Python中的文件I/O操作
2015/05/24 Python
使用Python制作自动推送微信消息提醒的备忘录功能
2018/09/06 Python
在双python下设置python3为默认的方法
2018/10/31 Python
我用Python抓取了7000 多本电子书案例详解
2019/03/25 Python
利用Python+阿里云实现DDNS动态域名解析的方法
2019/04/01 Python
Softmax函数原理及Python实现过程解析
2020/05/22 Python
python+selenium 简易地疫情信息自动打卡签到功能的实现代码
2020/08/22 Python
python Selenium 库的使用技巧
2020/10/16 Python
萨克斯第五大道精品百货店: Saks Fifth Avenue
2017/04/28 全球购物
美国最大的户外装备和服装购物网站:Backcountry
2019/10/15 全球购物
2014道德模范事迹材料
2014/02/16 职场文书
《有趣的发现》教学反思
2014/04/15 职场文书
社区春季防火方案
2014/06/02 职场文书
企业工会工作总结2015
2015/05/13 职场文书
结婚十年感言
2015/07/31 职场文书
2015年高三毕业班班主任工作总结
2015/10/22 职场文书
MybatisPlus代码生成器的使用方法详解
2021/06/13 Java/Android