JavaScript中的魔幻代理Proxy

时间: 2018-09-21阅读: 620标签: js知识

什么是代理?

让我们看三个案例:

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

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

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

这些都是代理。


什么是 JavaScript 代理?

用官方的洋文来说,是 Proxy

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 = false
Error: 任何妄图停止音乐的行为都是耍流氓!
    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 /users

api.getUsers$Books(42)
// 获取 ID 为 42 的用户的所有书籍
// GET /users/42/books

api.getUsers$Books(42, { params: { page: 2 } })
// 获取 ID 为 42 的用户的所有书籍的第二页
// GET /users/42/books?page=2

api.postUsers({ data: { name: '小明' } })
// 创建名字为 小明 的用户
// POST /users Payload { name: '小明' }

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

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


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

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

Proxy 的基本语法如下:

new Proxy(target, handler)

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

对于 handler 对象,我们刚才仅仅用到了 get、set 函数,而实际上一共有 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 种可代理的操作,还需要读者自行研究并实践方可踏上终极魔幻之旅。同学,我看好你。


参考链接:

本文来源链接:https://knownsec-fed.com/2018-01-27-javascript-mo-huan-dai-li/


解密JavaScript执行上下文

首先我们先了解一下什么是执行上下文栈(Execution context stack)。分别展示了栈、堆和队列,其中栈就是我们所说的执行上下文栈;堆是用于存储对象这种复杂类型,我们复制对象的地址引用就是这个堆内存的地址

javascript中的依赖注入

使用没有依赖的模块,显然这是很难实现的。即使你创建了很好的像黑盒一样的组件,但总有个将所有部分合并起来的地方。这就是依赖注入起作用的地方,当前来看,高效管理依赖的能力是迫切需要的,本文总结了原作者对这个问题的看法。

js中&与&&,|与||的区别

&、|、~都是位操作符,而&&、|、~|都是逻辑操作!。&&是逻辑与运算符假前真后,||是逻辑或运算符真前假后,&是按位与操作两个数值的个位分别相与,同时为1才得1,只要一个为0就为0。

Js常用基础算法

冒泡排序;插入排序 过程就像你拿到一副扑克牌然后对它排序一样;快速排序;回文字符串;翻转字符串;字符串中出现最多次数的字符;数组去重;二分查找

如何掌握并用好defer(延迟执行)

defer:在函数A内用defer关键字调用的函数B会在在函数A return后执行。先看一个基础的例子,了解一下defer的效果,这段代码运行后会打印出:

44道JS难题

国外某网站给出了44道JS难题,这些题涉及面非常广,涵盖JS原型、函数细节、强制转换、闭包等知识,而且都是非常细节的东西,透过这些小细节可以折射出很多高级的JS知识点。

JS 超类和子类

看到javascript高级程序设计的面向对象章节看到超类与子类这个概念词,不懂上度娘查了才知道是怎么一回事。说到超类与子类,就不得不提起原型模式,原型对象,原型链与原型链继承了

js中void 0 与 undefined

偶然看到一个问题:为什么有的编程规范要求用 void 0 代替 undefined?如果不知道这个答案的小伙伴,第一反应就要问void 0是什么鬼?

document.write和innerHTML的区别

document.write只能重绘整个页面,innerHTML可以重绘页面的一部分。 document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。

js中delete关键字

delete关键字的作用:删除对象的属性 语法:delete 对象.属性、可以删除没有使用var关键字声明的全局变量(直接定义在window上面的属性)、删除数组元素、不能删除内置对象的属性、不能直接删除从原型上继承的属性

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全