就算只是扮演,也会成为真实的自我的一部分。对人类的精神来说,真实和虚假其实并没有明显的界限。入戏太深不是一件好事,但对于你来说并不成立,因为戏中的你才是真正符合你的身份的你。如今的你是真实的,就算一开始你只是在模仿着这种形象,现在的你也已经成为了这种形象。无论如何,你也不可能再回到过去了。
Proxy 代理,在 JavaScript 似乎很陌生,却又在生活中无处不在。或许有人在学习 ES6 的时候有所涉猎,但却并未真正了解它的使用场景,平时在写业务代码时也不会用到这个特性。
相比于文绉绉的定义内容,想必我们更希望了解它的使用场景,使其在真正的生产环境发挥强大的作用,而不仅仅是作为一个新的特性 -- 然后,实际中完全没有用到!
为函数添加特定的功能
代理对象的访问
作为胶水桥接不同结构的对象
监视对象的变化
还有更多。。。
如果你还没有了解过 Proxy 特性,可以先去上查看基本概念及使用。
为函数添加特定的功能
下面是一个为异步函数自动添加超时功能的高阶函数,我们来看一下它有什么问题
/** * 为异步函数添加自动超时功能 * @param timeout 超时时间 * @param action 异步函数 * @returns 包装后的异步函数 */ function asyncTimeout(timeout, action) { return function(...args) { return Promise.race([ Reflect.apply(action, this, args), wait(timeout).then(Promise.reject), ]) } }
一般而言,上面的代码足以胜任,但问题就在这里,不一般的情况 -- 函数上面包含自定义属性呢?
众所周知,JavaScript 中的函数是一等公民,即函数可以被传递,被返回,以及,被添加属性!
例如下面这个简单的函数 get,其上有着 _name 这个属性
const get = async i => i get._name = 'get'
一旦使用上面的 asyncTimeout 函数包裹之后,问题便会出现,返回的函数中 _name 属性不见了。这是当然的,毕竟实际上返回的是一个匿名函数。那么,如何才能让返回的函数能够拥有传入函数参数上的所有自定义属性呢?
一种方式是复制参数函数上的所有属性,但这点实现起来其实并不容易,真的不容易,不信你可以看看 Lodash 的 clone 函数。那么,有没有一种更简单的方式呢?答案就是 Proxy,它可以代理对象的指定操作,除此之外,其他的一切都指向原对象。
下面是 Proxy 实现的 asyncTimeout 函数
/** * 为异步函数添加自动超时功能 * @param timeout 超时时间 * @param action 异步函数 * @returns 包装后的异步函数 */ function asyncTimeout(timeout, action) { return new Proxy(action, { apply(_, _this, args) { return Promise.race([ Reflect.apply(_, _this, args), wait(timeout).then(Promise.reject), ]) }, }) }
测试一下,是可以正常调用与访问其上的属性的
;(async () => { console.log(await get(1)) console.log(get._name) })()
好了,这便是吾辈最常用的一种方式了 -- 封装高阶函数,为函数添加某些功能。
代理对象的访问
下面是一段代码,用以在页面上展示从后台获取的数据,如果字段没有值则默认展示 ''
模拟一个获取列表的异步请求
async function list() { // 此处仅为构造列表 class Person { constructor({ id, name, age, sex, address } = {}) { this.id = id this.name = name this.age = age this.sex = sex this.address = address } } return [ new Person({ id: 1, name: '琉璃' }), new Person({ id: 2, age: 17 }), new Person({ id: 3, sex: false }), new Person({ id: 4, address: '幻想乡' }), ] }
尝试直接通过解构为属性赋予默认值,并在默认值实现这个功能
;(async () => { // 为所有为赋值属性都赋予默认值 '' const persons = (await list()).map( ({ id = '', name = '', age = '', sex = '', address = '' }) => ({ id, name, age, sex, address, }), ) console.log(persons) })()
下面让我们写得更通用一些
function warp(obj) { const result = obj for (const k of Reflect.ownKeys(obj)) { const v = Reflect.get(obj, k) result[k] = v === undefined ? '' : v } return obj } ;(async () => { // 为所有为赋值属性都赋予默认值 '' const persons = (await list()).map(warp) console.log(persons) })()
暂且先看一下这里的 warp 函数有什么问题?
这里是答案的分割线
所有属性需要预定义,不能运行时决定
没有指向原对象,后续的修改会造成麻烦
吾辈先解释一下这两个问题
所有属性需要预定义,不能运行时决定