理解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 相关文章推荐
对google个性主页的拖拽效果的js的完整注释[转]
Apr 10 Javascript
jquery.cookie.js 操作cookie实现记住密码功能的实现代码
Apr 27 Javascript
Mac地址验证的javascript代码
Nov 09 Javascript
jquery的each方法使用示例分享
Mar 25 Javascript
JQuery记住用户名和密码的具体实现
Apr 04 Javascript
Jquery图片延迟加载插件jquery.lazyload.js的使用方法
May 21 Javascript
vue计算属性和监听器实例解析
May 10 Javascript
vue中轮训器的使用
Jan 27 Javascript
Vue项目服务器部署之子目录部署方法
May 12 Javascript
Vue.js路由实现选项卡简单实例
Jul 24 Javascript
JS中的算法与数据结构之集合(Set)实例详解
Aug 20 Javascript
JS实现可以用键盘方向键控制的动画
Dec 11 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 cookis创建实现代码
2009/03/16 PHP
php-perl哈希算法实现(times33哈希算法)
2013/12/30 PHP
yii2中的rules 自定义验证规则详解
2016/04/19 PHP
php 判断字符串编码是utf-8 或gb2312实例
2016/11/01 PHP
JCalendar 日历控件 v1.0 beta[兼容IE&amp;Firefox] 有文档和例子
2007/05/30 Javascript
jquery.cvtooltip.js 基于jquery的气泡提示插件
2010/11/19 Javascript
禁用页面部分JavaScript方法的具体实现
2013/07/31 Javascript
Js 去掉字符串中的空格(实现代码)
2013/11/19 Javascript
详解JavaScript语法对{}处理的坑爹之处
2014/06/05 Javascript
javascript变量声明实例分析
2015/04/25 Javascript
Canvas实现放射线动画效果
2017/02/15 Javascript
Vue下滚动到页面底部无限加载数据的示例代码
2018/04/22 Javascript
nodejs高大上的部署方式(PM2)
2018/09/11 NodeJs
vue 全局封装loading加载教程(全局监听)
2020/11/05 Javascript
python共享引用(多个变量引用)示例代码
2013/12/04 Python
python虚拟环境virtualenv的使用教程
2017/10/20 Python
Scrapy抓取京东商品、豆瓣电影及代码分享
2017/11/23 Python
python实现图片文件批量重命名
2020/03/23 Python
Python实现FTP文件传输的实例
2019/07/07 Python
keras中模型训练class_weight,sample_weight区别说明
2020/05/23 Python
Html5画布_动力节点Java学院整理
2017/07/13 HTML / CSS
html5 跨文档消息传输示例探讨
2013/04/01 HTML / CSS
为什么说Ruby是一种真正的面向对象程序设计语言
2012/10/30 面试题
历史系毕业生自荐信
2013/10/28 职场文书
商业融资计划书
2014/04/29 职场文书
社区春季防火方案
2014/06/02 职场文书
毕业生求职信
2014/06/10 职场文书
立项申请报告范本
2015/05/15 职场文书
2015年高中生国庆节演讲稿
2015/07/30 职场文书
CSS3实现模糊背景的三种效果示例
2021/03/30 HTML / CSS
MySQL infobright的安装步骤
2021/04/07 MySQL
单身狗福利?Python爬取某婚恋网征婚数据
2021/06/03 Python
JavaScript 定时器详情
2021/11/11 Javascript
golang连接MySQl使用sqlx库
2022/04/14 Golang
MySQL三种方式实现递归查询
2022/04/18 MySQL
关于对TypeScript泛型参数的默认值理解
2022/07/15 Javascript