JavaScript 魔幻代理

前言

什麼是代理?

上小學的時候,李小紅來你家叫你出去玩,第一個迴應的不是你本身,是你媽:「王小明在家寫做業,今天不出去!」javascript

上中學的時候,趙二虎帶着小弟們放學在校門口等着揍你,走在前面的不是你本身,是二虎他爸:「考試沒及格還學會裝黑社會了!」拎起二虎就是一頓胖揍。前端

上了大學,躺在宿舍裏的牀上,好餓。出門買飯並交代好不要蔥蒜多放辣最後還直接端到牀上的不是你本身,是快遞小哥。java

這些都是代理。ios

什麼是 JavaScript 代理?

用官方的洋文來講,是 Proxyaxios

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

經過 Proxy 咱們能夠攔截並改變一個對象的幾乎全部的根本操做,包括但不限於屬性查找、賦值、枚舉、函數調用等等。微信

在生活中,經過代理咱們能夠自動屏蔽小紅的邀請、自動趕走二虎的威脅、自動買好乾淨的飯端到牀上。在 JavaScript 世界裏,代理也能夠幫你作相似的事情,接下來讓咱們一塊兒琢磨一番。app

初識代理:Hello World

以小學經歷爲例子,內心是喜歡小紅的,因而咱們定義:函數

const me = { name: '小明', like: '小紅' }
複製代碼

這個時候若是調用 console.log(me.like),結果必然是 小紅。然而生活並非這樣,做爲一個未成年人,老是有各類的代理人圍繞在你身邊,好比這樣:post

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 對象,咱們剛纔僅僅用到了 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),碼上獲取更多優質乾貨!

相關文章
相關標籤/搜索