理解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 相关文章推荐
javascript提取URL的搜索字符串中的参数(自定义函数实现)
Jan 22 Javascript
Node.js node-schedule定时任务隔多少分钟执行一次的方法
Feb 10 Javascript
JQ技术实现注册页面带有校验密码强度
Jul 27 Javascript
AngularJS利用Controller完成URL跳转
Aug 09 Javascript
JavaScript获取URL中参数querystring的方法详解
Oct 11 Javascript
js学习总结_选项卡封装(实例讲解)
Jul 13 Javascript
实例教学如何写vue插件
Nov 30 Javascript
基于vue 实现token验证的实例代码
Dec 14 Javascript
JS实现统计字符串中字符出现个数及最大个数功能示例
Jun 04 Javascript
浅谈vue项目4rs vue-router上线后history模式遇到的坑
Sep 27 Javascript
详解 微信小程序开发框架(MINA)
May 17 Javascript
vscode 配置vue+vetur+eslint+prettier自动格式化功能
Mar 23 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 var_export与var_dump 输出的不同
2013/08/09 PHP
PHP小技巧之JS和CSS优化工具Minify的使用方法
2014/05/19 PHP
CodeIgniter模板引擎使用实例
2014/07/15 PHP
PHP中使用Session配合Javascript实现文件上传进度条功能
2014/10/15 PHP
浅谈php中include文件变量作用域
2015/06/18 PHP
thinkPHP数据查询常用方法总结【select,find,getField,query】
2017/03/15 PHP
php检查函数必传参数是否存在的实例详解
2017/08/28 PHP
PHP实现多图上传和单图上传功能
2018/05/17 PHP
浅谈laravel5.5 belongsToMany自身的正确用法
2019/10/17 PHP
Aster vs KG BO3 第二场2.19
2021/03/10 DOTA
jquery实现的超出屏幕时把固定层变为定位层的代码
2010/02/23 Javascript
jquery如何判断某元素是否具备指定的样式
2013/11/05 Javascript
jQuery网页选项卡插件rTabs用法实例分析
2015/08/26 Javascript
node.js实现爬虫教程
2020/08/25 Javascript
基于 Node.js 实现前后端分离
2016/04/23 Javascript
基于jquery实现智能表单验证操作
2016/05/09 Javascript
Node.js 日志处理模块log4js
2016/08/28 Javascript
学习JavaScript图片预加载模块
2016/11/07 Javascript
bootstrap table动态加载数据示例代码
2017/03/25 Javascript
纯js代码生成可搜索选择下拉列表的实例
2018/01/11 Javascript
Vue 中的compile操作方法
2018/02/26 Javascript
解决nodejs的npm命令无反应的问题
2018/05/17 NodeJs
Angularjs中的$apply及优化使用详解
2018/07/02 Javascript
微信小程序scroll-view实现字幕滚动
2018/07/14 Javascript
nodejs高大上的部署方式(PM2)
2018/09/11 NodeJs
Vue 组件封装 并使用 NPM 发布的教程
2018/09/30 Javascript
Node.js学习教程之Module模块
2019/09/03 Javascript
js中console在一行内打印字符串和对象的方法
2019/09/10 Javascript
python实现中文转换url编码的方法
2016/06/14 Python
什么是python的函数体
2020/06/19 Python
英国二手物品交易网站:Preloved
2017/10/06 全球购物
美国奢侈品在线团购网站:Gilt City
2017/11/16 全球购物
法人代表证明书
2014/09/18 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
2014年保安个人工作总结
2014/11/13 职场文书
导游词之秦始皇兵马俑博物馆
2019/09/29 职场文书