博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript 魔幻代理
阅读量:6135 次
发布时间:2019-06-21

本文共 4118 字,大约阅读时间需要 13 分钟。

前言

什么是代理?

上小学的时候,李小红来你家叫你出去玩,第一个回应的不是你自己,是你妈:“王小明在家写作业,今天不出去!”

上中学的时候,赵二虎带着小弟们放学在校门口等着揍你,走在前面的不是你自己,是二虎他爸:“考试没及格还学会装黑社会了!”拎起二虎就是一顿胖揍。

上了大学,躺在宿舍里的床上,好饿。出门买饭并交代好不要葱蒜多放辣最后还直接端到床上的不是你自己,是快递小哥。

这些都是代理。

什么是 JavaScript 代理?

用官方的洋文来说,是 :

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

通过 Proxy 我们可以拦截并改变一个对象的几乎所有的根本操作,包括但不限于属性查找、赋值、枚举、函数调用等等。

在生活中,通过代理我们可以自动屏蔽小红的邀请、自动赶走二虎的威胁、自动买好干净的饭端到床上。在 JavaScript 世界里,代理也可以帮你做类似的事情,接下来让我们一起琢磨一番。

初识代理:Hello World

以小学经历为例子,心里是喜欢小红的,于是我们定义:

const me = { name: '小明', like: '小红' }复制代码

这个时候如果调用 console.log(me.like),结果必然是 小红。然而生活并不是这样,作为一个未成年人,总是有各种的代理人围绕在你身边,比如这样:

const meWithProxy = new Proxy(me, {  get(target, prop) {    if (prop === 'like') {      return '学习';    }    return target[prop];  }});复制代码

这个时候如果调用 console.log(me.like) 依然是 小红 ,因为真心不会说谎。但当我们调用 console.log(meWithProxy.like) 的时候,就会可耻的输出 学习 ,告诉大家说我们喜欢的是 学习

小试牛刀:不要停止我的音乐

刚才我们简单了解了代理能够拦截对象属性的获取,可以隐藏真实的属性值而返回代理想要返回的结果,那么对于对象属性的赋值呢?让我们一起来看看。

假设你正在听音乐:

const me = { name: '小明', musicPlaying: true }复制代码

此时如果我们执行 me.musicPlaying = false 这样就轻而易举地停止了你的音乐,那么如果我们挂上代理人:

const meWithProxy = new Proxy(me, {  set(target, prop, value) {    if (prop === 'musicPlaying' && value !== true) {      throw Error('任何妄图停止音乐的行为都是耍流氓!');    }    target[prop] = value;  }});复制代码

这时候如果我们执行 me.musicPlaying = false,就会被毫不留情地掀了桌子:

> meWithProxy.musicPlaying = falseError: 任何妄图停止音乐的行为都是耍流氓!    at Object.set (repl:4:13)>复制代码

释放魔法:封装全宇宙所有 RESTful API

现在我们已经知道通过 Proxy 可以拦截属性的读写操作,那然后呢?没什么用?

仅仅是拦截属性的读写操作,的确没有太大的发挥空间,或许可以方便的做一些属性赋值校验工作等等。但是,或许你还没有意识到一个惊人的秘密:Proxy 在拦截属性读写操作时,并不在乎属性是否真的存在!

那么,也就是说:利用 Proxy,我们可以拦截并不存在的属性的读取。

再进一步思考:利用 Proxy,我们可以在属性读取的那一瞬间,动态构造返回结果。

然而,属性并不局限于字符串、布尔值,属性可以是对象、函数、任何东西。

至此,你想到了什么?

没想到?不要紧!根据刚才的分析,让我们一起通过下面 17 行代码,来封装全宇宙所有的 RESTful API !

import axios from 'axios';const api = new Proxy({}, {  get(target, prop) {    const method = /^[a-z]+/.exec(prop)[0];    const path = '/' + prop          .substring(method.length)          .replace(/([a-z])([A-Z])/g, '$1/$2')          .replace(/\$/g, '/$/')          .toLowerCase();    return (...args) => { // <------ 返回动态构造的函数!      const url = path.replace(/\$/g, () => args.shift());      const options = args.shift() || {};      console.log('Requesting: ', method, url, options);      return axios({ method, url,  ...options });    }  }});复制代码

定义了 api 这个代理之后,我们就可以像下面这样调用:

api.get()// GET /api.getUsers()// 获取所有用户// GET /usersapi.getUsers$Books(42)// 获取 ID 为 42 的用户的所有书籍// GET /users/42/booksapi.getUsers$Books(42, { params: { page: 2 } })// 获取 ID 为 42 的用户的所有书籍的第二页// GET /users/42/books?page=2api.postUsers({ data: { name: '小明' } })// 创建名字为 小明 的用户// POST /users Payload { name: '小明' }复制代码

以上所有的函数都在你调用的那一瞬间,通过代理人的魔法之手动态生成,供我们随意取用。

简洁、优雅,哇~ 真是太棒啦!

终极魔幻:通读代理人的魔法秘笈

到此,我们仅仅使用 Proxy 改造了对象的属性获取、赋值操作,而对于 Proxy 来说,只是冰山一角。

Proxy 的基本语法如下:

new Proxy(target, handler)复制代码

其中 target 是即将被代理的对象(比如:想要出门找小红玩耍的 me),handler 就是代理的魔法之手,用来拦截、改造 target 的行为。

对于 handler 对象,我们刚才仅仅用到了 getset 函数,而实际上一共有 13 种可代理的操作:

  • handler.getPrototypeOf()

    在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。

  • handler.setPrototypeOf()

    在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。

  • handler.isExtensible()

    在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。

  • handler.preventExtensions()

    在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。

  • handler.getOwnPropertyDescriptor()

    在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。

  • handler.defineProperty()

    在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。

  • handler.has()

    在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。

  • handler.get()

    在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。

  • handler.set()

    在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。

  • handler.deleteProperty()

    在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。

  • handler.ownKeys()

    在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。

  • handler.apply()

    在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。

  • handler.construct()

    在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。

对于以上 13 种可代理的操作,还需要读者自行研究并实践方可踏上终极魔幻之旅。

同学,我看好你。


参考链接:


关注微信公众号:创宇前端(KnownsecFED),码上获取更多优质干货!

转载地址:http://oteua.baihongyu.com/

你可能感兴趣的文章
《C语言解惑》—— 2.9 输出值的操作符
查看>>
Project Volta 让 Android 续航提升了多少?
查看>>
《树莓派实战秘籍》——1.7 技巧07使用过压获得更高的性能
查看>>
《SAS 统计分析与应用从入门到精通(第二版)》一1.4 SAS系统的文件管理
查看>>
《Java遗传算法编程》—— 2.9 小结
查看>>
RHCE 系列(七):在 Linux 客户端配置基于 Kerberos 身份验证的 NFS 服务器
查看>>
《流量的秘密: Google Analytics网站分析与优化技巧(第3版)》一第1章 为什么了解网站流量对你的业务至关重要1.1 网站效果评估——为什么要这样做...
查看>>
我安身立命的编程指导
查看>>
JVMTI开发教程之Class统计信息柱状图
查看>>
Spring Boot 整合 Mybatis Annotation 注解的完整 Web 案例
查看>>
《众妙之门——网页设计专业之道》——2.4 总结
查看>>
直击云栖大会·成都峰会:安全、教育与1100生态密码
查看>>
在 Linux 下使用 RAID(八):当软件 RAID 故障时如何恢复和重建数据
查看>>
hibernate配置文件hibernate.cfg.xml的详细解释
查看>>
《第一本Docker书(修订版)》——1.4 Docker与配置管理
查看>>
如何将 Ubuntu14.04 Server 安全的升级到14.10
查看>>
DevOps:软件架构师行动指南3.7 更多阅读材料
查看>>
《解读NoSQL》——2.5 比较ACID和BASE——两种可靠的数据库事务方法
查看>>
《Node学习指南》一第1章 Node.js:启动与运行1.1 搭建Node开发环境
查看>>
《Android游戏开发详解》——第2章,第2.22节小结
查看>>