拿Proxy可以做哪些有意思的事儿
Proxy是什么
首先,我们要清楚,Proxy是什么意思,这个单词翻译过来,就是 代理。
可以理解为,有一个很火的明星,开通了一个微博账号,这个账号非常活跃,回复粉丝、到处点赞之类的,但可能并不是真的由本人在维护的。
而是在背后有一个其他人 or 团队来运营,我们就可以称他们为代理人,因为他们发表的微博就代表了明星本人的意思。
P.S. 强行举例子,因为本人不追星,只是猜测可能会有这样的运营团队
这个代入到JavaScript当中来,就可以理解为对对象或者函数的代理操作。
JavaScript中的Proxy
Proxy是ES6中提供的新的API,可以用来定义对象各种基本操作的自定义行为
(在文档中被称为traps,我觉得可以理解为一个针对对象各种行为的钩子)
拿它可以做很多有意思的事情,在我们需要对一些对象的行为进行控制时将变得非常有效。
Proxy的语法
创建一个Proxy的实例需要传入两个参数
target要被代理的对象,可以是一个object或者functionhandlers对该代理对象的各种操作行为处理
let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)
proxy.a = 123
console.log(target.a) //
在第二个参数为空对象的情况下,基本可以理解为是对第一个参数做的一次浅拷贝
(Proxy必须是浅拷贝,如果是深拷贝则会失去了代理的意义)
Traps(各种行为的代理)
就像上边的示例代码一样,如果没有定义对应的trap,则不会起任何作用,相当于直接操作了target。
当我们写了某个trap以后,在做对应的动作时,就会触发我们的回调函数,由我们来控制被代理对象的行为。
最常用的两个trap应该就是get和set了。
早年JavaScript有着在定义对象时针对某个属性进行设置getter、setter:
let obj = {
_age: 18,
get age () {
return `I'm ${this._age} years old`
},
set age (val) {
this._age = Number(val)
}
}
console.log(obj.age) // I'm 18 years old
obj.age = 19
console.log(obj.age) // I'm 19 years old
就像这段代码描述的一样,我们设置了一个属性_age,然后又设置了一个get age和set age。
然后我们可以直接调用obj.age来获取一个返回值,也可以对其进行赋值。
这么做有几个缺点:
- 针对每一个要代理的属性都要编写对应的
getter、setter。 - 必须还要存在一个存储真实值的
key(如果我们直接在getter里边调用this.age则会出现堆栈溢出的情况,因为无论何时调用this.age进行取值都会触发getter)。
Proxy很好的解决了这两个问题:
let target = { age: 18, name: 'Niko Bellic' }
let handlers = {
get (target, property) {
return `${property}: ${target[property]}`
},
set (target, property, value) {
target[property] = value
}
}
let proxy = new Proxy(target, handlers)
proxy.age = 19
console.log(target.age, proxy.age) // 19, age : 19
console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic
我们通过创建get、set两个trap来统一管理所有的操作,可以看到,在修改proxy的同时,target的内容也被修改,而且我们对proxy的行为进行了一些特殊的处理。
而且我们无需额外的用一个key来存储真实的值,因为我们在trap内部操作的是target对象,而不是proxy对象。
拿Proxy来做些什么
因为在使用了Proxy后,对象的行为基本上都是可控的,所以我们能拿来做一些之前实现起来比较复杂的事情。
解决对象属性为undefined的问题
在一些层级比较深的对象属性获取中,如何处理undefined一直是一个痛苦的过程,如果我们用Proxy可以很好的兼容这种情况。
(() => {
let target = {}
let handlers = {
get: (target, property) => {
target[property] = (property in target) ? target[property] : {}
if (typeof target[property] === 'object') {
return new Proxy(target[property], handlers)
}
return target[property]
}
}
let proxy = new Proxy(target, handlers)
console.log('z' in proxy.x.y) // false (其实这一步已经针对`target`创建了一个x.y的属性)
proxy.x.y.z = 'hello'
console.log('z' in proxy.x.y) // true
console.log(target.x.y.z) // hello
})()
我们代理了get,并在里边进行逻辑处理,如果我们要进行get的值来自一个不存在的key,则我们会在target中创建对应个这个key,然后返回一个针对这个key的代理对象。
这样就能够保证我们的取值操作一定不会抛出can not get xxx from undefined
但是这会有一个小缺点,就是如果你确实要判断这个key是否存在只能够通过in操作符来判断,而不能够直接通过get来判断。
普通函数与构造函数的兼容处理
如果我们提供了一个Class对象给其他人,或者说一个ES5版本的构造函数。
如果没有使用new关键字来调用的话,Class对象会直接抛出异常,而ES5中的构造函数this指向则会变为调用函数时的作用域。
我们可以使用apply这个trap来兼容这种情况:
class Test {
constructor (a, b) {
console.log('constructor', a, b)
}
}
// Test(1, 2) // throw an error
let proxyClass = new Proxy(Test, {
apply (target, thisArg, argumentsList) {
// 如果想要禁止使用非new的方式来调用函数,直接抛出异常即可
// throw new Error(`Function ${target.name} cannot be invoked without 'new'`)
return new (target.bind(thisArg, ...argumentsList))()
}
})
proxyClass(1, 2) // constructor 1 2
我们使用了apply来代理一些行为,在函数调用时会被触发,因为我们明确的知道,代理的是一个Class或构造函数,所以我们直接在apply中使用new关键字来调用被代理的函数。
以及如果我们想要对函数进行限制,禁止使用new关键字来调用,可以用另一个trap:construct
function add (a, b) {
return a + b
}
let proxy = new Proxy(add, {
construct (target, argumentsList, newTarget) {
throw new Error(`Function ${target.name} cannot be invoked with 'new'`)
}
})
proxy(1, 2) //
new proxy(1, 2) // throw an error
用Proxy来包装fetch
在前端发送请求,我们现在经常用到的应该就是fetch了,一个原生提供的API。
我们可以用Proxy来包装它,使其变得更易用。
let handlers = {
get (target, property) {
if (!target.init) {
// 初始化对象
['GET', 'POST'].forEach(method => {
target[method] = (url, params = {}) => {
return fetch(url, {
headers: {
'content-type': 'application/json'
},
mode: 'cors',
credentials: 'same-origin',
method,
...params
}).then(response => response.json())
}
})
}
return target[property]
}
}
let API = new Proxy({}, handlers)
await API.GET('XXX')
await API.POST('XXX', {
body: JSON.stringify({name: 1})
})
对GET、POST进行了一层封装,可以直接通过.GET这种方式来调用,并设置一些通用的参数。
实现一个简易的断言工具
写过测试的各位童鞋,应该都会知道断言这个东西console.assert就是一个断言工具,接受两个参数,如果第一个为false,则会将第二个参数作为Error message抛出。
我们可以使用Proxy来做一个直接赋值就能实现断言的工具。
let assert = new Proxy({}, {
set (target, message, value) {
if (!value) console.error(message)
}
})
assert['Isn\'t true'] = false // Error: Isn't true
assert['Less than 18'] = 18 >= 19 // Error: Less than 18
统计函数调用次数
在做服务端时,我们可以用Proxy代理一些函数,来统计一段时间内调用的次数。
在后期做性能分析时可能会能够用上:
function orginFunction () {}
let proxyFunction = new Proxy(orginFunction, {
apply (target, thisArg. argumentsList) {
log(XXX)
return target.apply(thisArg, argumentsList)
}
})
全部的traps
这里列出了handlers所有可以定义的行为 (traps):
具体的可以查看MDN-Proxy
里边同样有一些例子
| traps | description |
|---|---|
| get | 获取某个key值 |
| set | 设置某个key值 |
| has | 使用in操作符判断某个key是否存在 |
| apply | 函数调用,仅在代理对象为function时有效 |
| ownKeys | 获取目标对象所有的key |
| construct | 函数通过实例化调用,仅在代理对象为function时有效 |
| isExtensible | 判断对象是否可扩展,Object.isExtensible的代理 |
| deleteProperty | 删除一个property |
| defineProperty | 定义一个新的property |
| getPrototypeOf | 获取原型对象 |
| setPrototypeOf | 设置原型对象 |
| preventExtensions | 设置对象为不可扩展 |
| getOwnPropertyDescriptor | 获取一个自有属性 (不会去原型链查找) 的属性描述 |
参考资料
拿Proxy可以做哪些有意思的事儿的更多相关文章
- JavaScript的Proxy可以做哪些有意思的事儿
摘要: 神奇而有趣的Proxy. 原文:拿Proxy可以做哪些有意思的事儿 作者:贾顺名 Fundebug经授权转载,版权归原作者所有. Proxy是什么 首先,我们要清楚,Proxy是什么意思,这个 ...
- 游戏AI系列内容 咋样才能做个有意思的AI呢
游戏AI系列内容 咋样才能做个有意思的AI呢 写在前面的话 怪物AI怎么才能做的比较有意思.其实这个命题有点大,我作为一个仅仅进入游戏行业两年接触怪物AI还不到一年的程序员来说,来谈这个话题,我想我是 ...
- 用python做些有意思的事——分析QQ聊天记录——私人订制
之前,写了这篇文章,用python提取全部群成员的发言时间,并简单做了下分析.先补充一下,针对特定单个群成员(这里以 小小白 为例)消息记录的获取. 代码比较简单,主要是正则表达式的书写.(附: ...
- 用python做些有意思的事——分析QQ聊天记录
####################################### 已更新续集,戳这里. ######################################## 是这样的,有位学 ...
- 做SEO推广必须要做的9件事儿
SEO推广是由网站优化网络运营媒体宣传结合的一种技术,而现在恰好就是媒体最为流行,真因为如此很多的站长之知道利用自媒体推广网站,结果推广了几年网站权重只有2到3而已,导致和谐问题的关键就是没有结合其他 ...
- python之命令行可以做的事儿
前言 说起python,大家都知道可以做很多有趣的事儿.知乎.CSDN上很多大牛做过很多或高大上或实用的事儿.参见这篇文章Python趣味代码整合之提升学生编程兴趣, 另外,我发现命令行也可以做很多事 ...
- ES5和ES6那些你必须知道的事儿(三)
ES5和ES6那些你必须知道的事儿 ES6新增的东西 一.块级作用域:关键字let,常量const let与var的区别: a.不会进行变量声明提升 b.变量不允许被重复定义 c.变量不允许被删除 d ...
- Object.defineProperties()与Proxy对象代理
Object.defineProperties() 了不起啊..vue.js通过它实现双向绑定的 Object.defineProperties(obj,props) 方法直接在一个对象上定义新的属性 ...
- Mysql读写分离方案-MySQL Proxy环境部署记录
Mysql的读写分离可以使用MySQL Proxy和Amoeba实现,其实也可以使用MySQL-MMM实现读写分离的自动切换.MySQL Proxy有一项强大功能是实现"读写分离" ...
随机推荐
- Java深入了解TreeSet
Java中的TreeSet是Set的一个子类,TreeSet集合是用来对象元素进行排序的,同样他也可以保证元素的唯一.那TreeSet为什么能保证元素唯一,它是怎样排序的呢?先看一段代码: publi ...
- Spring3 MVC使用@ResponseBody的乱码问题及解决办法
近日用Spring3的MVC写东西,深感其之于Webwork/Struts2的便利,但是在通过@ResponseBody这个annotation输出一个json字符串的时候,发现页面上获得的json字 ...
- JVM笔记8-虚拟机性能监控与故障处理工具
1.JDK命令行工具 Java开发人员肯定都知道JDK的bin目录有“java.exe”,"javac.exe"这两个命令行工具,但并非所有程序员都了解过JDK的bin目录之中其他 ...
- 为何90%的IT技术人员不适合做老大
老男孩老师终于写了一篇让我觉得不错的文章,哈哈,转一下. 什么是格局? 格局就是能够很好的平衡短期利益和长期利益. 过分注重短期利益的人必然会失去长期利益,到头来一定会很普通. 例如:跳槽不断,可能短 ...
- 当今商业中使用的三种十分重要的IT应用系统
本文为读书笔记,其中内容摘自<信息时代的管理信息系统>第八版第二章 当今商业中使用的三种十分重要的IT应用系统: 供应链管理(SCM) 客户关系管理(CRM) 电子协同(e-collabo ...
- Microsoft Visual C++ Compiler for Python 2.7真正下载地址
真正下载地址: http://origin.www.ms.akadns.NET/en-us/download/details.aspx?id=44266 那个微软地址好像不可用.
- 0510JS流程语句
|--跳转语句|----break; 终止整个循环,不再进行判断|----continue; 终止本次循环,接着去判断是否执行下次循环 |-选择(判断)结构|--if 如果|----if(条件1){ ...
- thinkphp框架实现删除上传的文件
public function article_delete(){ $article_id = I('get.article_id'); $model = M('zx_article'); $data ...
- Vimium快捷键记录
作为一个Geek必备的技能 从今天起在这里仅记录下使用过的快捷键和心得(翻译自 ?) version: 1.59 版本不同,快捷键有所不同 1. 下载安装地址(自备梯子) Chrome商店:https ...
- VM虚拟机链接克隆及linux eth0网卡的快速设置方法
对于后台开发者来说,在学习过程中必然接触众多中间件,在自己的虚拟机进行操作甚至搭建cluster是很常见的事情. 我在初学者时摸索出一套快速的克隆虚拟机方法.分享给大家. 产品VMware® Work ...