拿 Proxy 可以做哪些有意思的事兒

2021-12-24 SegmentFault思否
Proxy是什麼

首先,我們要清楚, Proxy是什麼意思,這個單詞翻譯過來,就是 代理

可以理解為,有一個很火的明星,開通了一個微博帳號,這個帳號非常活躍,回復粉絲、到處點讚之類的,但可能並不是真的由本人在維護的。而是在背後有一個其他人 or 團隊來運營,我們就可以稱他們為代理人,因為他們發表的微博就代表了明星本人的意思。

P.S. 強行舉例子,因為本人不追星,只是猜測可能會有這樣的運營團隊

這個代入到 JavaScript當中來,就可以理解為對 對象或者 函數的代理操作。

JavaScript中的Proxy

Proxy是ES6中提供的新的API,可以用來定義對象各種基本操作的自定義行為(在文檔中被稱為 traps,我覺得可以理解為一個針對對象各種行為的鉤子)。拿它可以做很多有意思的事情,在我們需要對一些對象的行為進行控制時將變得非常有效。

Proxy的語法

創建一個 Proxy的實例需要傳入兩個參數

target 要被代理的對象,可以是一個 object或者 function

handlers對該代理對象的各種操作行為處理

let target = {}

let handlers = {} // do nothing

let proxy = new Proxy(target, handlers)

proxy.a = 123

console.log(target.a) // 123

在第二個參數為空對象的情況下,基本可以理解為是對第一個參數做的一次淺拷貝(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,然後又設置了一個 getage和 setage。然後我們可以直接調用 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的代理對象。這樣就能夠保證我們的取值操作一定不會拋出 cannotgetxxxfromundefined。

但是這會有一個小缺點,就是如果你確實要判斷這個 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)     // 3

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,則會將第二個參數作為 Errormessage拋出。

我們可以使用 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裡邊同樣有一些例子

trapsdescriptionget獲取某個 key值set設置某個 key值has使用 in操作符判斷某個 key是否存在apply函數調用,僅在代理對象為 function時有效ownKeys獲取目標對象所有的 keyconstruct函數通過實例化調用,僅在代理對象為 function時有效isExtensible判斷對象是否可擴展, Object.isExtensible的代理deleteProperty刪除一個 propertydefineProperty定義一個新的 propertygetPrototypeOf獲取原型對象setPrototypeOf設置原型對象preventExtensions設置對象為不可擴展getOwnPropertyDescriptor獲取一個自有屬性 (不會去原型鏈查找) 的屬性描述參考資料

Magic Methods in JavaScript? Meet Proxy!

How to use JavaScript Proxies for Fun and Profit

MDN-Proxy

杭州賽區即將開始,6 月 2 號,等你相聚!

相關焦點

  • ES6 Proxy 可以做哪些有意思的事情?
    Proxy 提供了哪些攔截方式?Proxy 一般是用來架設在目標對象之上的一層攔截,來實現對目標對象訪問和修改的控制。Proxy 是一個構造函數,使用的時候需要配合 new 操作符,直接調用會報錯。,而 Object.defineProperty 不可以,需要你手動再去做一次監聽。
  • 照相技術傳入晚清後,發生了哪些有意思的事兒?
    民間對於輪船、火車和洋槍洋炮都感興趣,甚至可以說喜歡。但對於有線電報和照相,卻心懷疑慮。不僅擔心照相會把人的魂給攝走,甚至覺得那一排排的電線桿子以及上面的電線有蹊蹺。傳教士李提摩太提到過這樣一個故事,甲午前,洋人工程師在山東安裝有線電報線的時候,德州有家人家孩子丟了,民間哄傳是被電線攝走了魂魄然後被洋人拐走了。所以,直到辛亥革命發生,各地一有點什麼事兒,就有人砍電線桿。
  • 可以拿回家做的手工活 在家做的手工活有哪些
    那麼這麼多手工活外放,哪些是可以拿回家做的手工活呢?因為現在很多人在家帶帶小孩,同時想接點手工活做做,一來補貼家用,二來也充實點自己。而一些服裝加工,燒焊加工都要些設備,不怎麼適合在家裡做,因此鋅鋅媽家的手工活非常適合在家裡做的手工活:我們的手工活有主要做網紅髮夾加工,都是跟著視頻教程一步步做,找到規律做起來很簡單。
  • 利用 Charles Proxy 下載舊版本 iOS App
    logo,內部文件結構和滴滴自己的 App 幾乎沒有差異…… 而大洋彼岸真正的 Uber 公司卻是發布了真正的新版 Uber,不僅界面簡化美觀,同時整合多家網際網路公司服務,讓你可以在路途看終點附近的餐館,電影院,甚至直接訂票。
  • 你下班後做了哪些提高自己的事兒?
    你下班後做了哪些提高自己的事兒大概一個月就有成效了,別人見到第一句都是「哎呀你瘦了哇」,管住嘴邁開腿,可以改變人生。其實基本上幾個詞就可以概括出來 看書,背書和輕度娛樂。在準備司法考試跟英語六級,還考慮著要不要考研。反正是朝著我想要的方向奔去,這樣努力的感覺真好。
  • 鹿晗《Reloaded II》背後的10個有意思的事兒
    從視頻網站上滿滿的彈(wu)幕(wu) 我們看到了大家對《Lu》這首歌的喜愛,錄製《ReloadedII》這張專輯時的一些有意思的事兒,必須分享出來了
  • 小的時候,為躲避打針,你都做過哪些哭笑不得的事兒?
    小的時候,為躲避打針,你都做過哪些哭笑不得的事兒?可以試試下面這幾個方法。第一,父母在平時多給孩子講一講,打預防針到底有什麼好處?可以買一些相關的繪本,漫畫來給孩子作為睡前故事跟孩子聊一聊,講一講。或者是家長有的時候生病了打針的時候,可以讓孩子看到,讓孩子知道:生病了就要打針,這樣病才能好。
  • 光遇:你做過哪些壞事?這四種很有意思,甚至還有些可愛
    要閒聊的時候,小叔喜歡跟玩家互動,發現大家除了正常的跑圖之外,最喜歡去「養老」,並非是指彈琴、聊天,而是做一些「壞壞」的事,或者捉弄、逗樂。今天小叔就盤點了四件事,可能並非那麼壞,反而有些可愛,只是一定會讓你生氣。今日話題:你做過哪些壞事?這四種很有意思,甚至還有些可愛!
  • 有骨氣,做啥事兒都不服輸的生肖
    閱讀本文前,請您先點擊上面的藍色字體「晨起佛音」,再點擊「關注」,這樣您就可以繼續免費收到文章了
  • 下班以後可以做哪些副業掙錢?
    (摘自維基百科)這年頭就連明星都在紛紛發展副業,開傳媒公司開火鍋店開服裝品牌,都已經不是新鮮事了~那作為普通人的我們,下了班以後可以發展哪些副業來更靠近暴富一點點呢?今天我們一起來看看豆友們是如何「斜槓」起來的吧,希望能給你一些啟發。* 本文內容整理自豆瓣小組帖子,及豆瓣話題#作為「斜槓青年」的你都在做著什麼副業#
  • 經期可以做哪些運動減肥
    核心提示:對於女性來說,做好經期保健非常的關鍵,而在現實生活中,有些正在減肥的朋友卻不想由於月經的到來而停止減肥的步伐,其實經期適當的減肥並不會給身體健康帶來傷害,但是大家要注意不可以節食,更不能進行激烈的運動,那麼,經期可以做哪些運動減肥呢?我們一起來看下面的介紹。 經期可以做哪些運動減肥?
  • 有哪些網際網路兼職可以做?網絡營銷兼職有哪些?
    有哪些網際網路兼職可以做?網絡營銷兼職有哪些?本人目前在杭州一家網際網路企業工作,97年,女生,中部省份普通家庭出身,畢業於上海一所二本學校,這個問題主要是說大學生線上兼職的,那我就講講當時我讀大學時候的一些經歷吧。
  • 年紀輕輕,卻把事兒看的很透徹的星座
    年紀輕輕的,卻把事兒看的很透徹,看的很明白,就說這三大星座的人吧,可真的上活得透徹,看的明白,對朋友的心思也是一樣的,跟著星座洞君一起來看看都有哪些星座吧
  • 有意思的文案,專治不夠有意思的你
    這不僅是有意思的文案,更是有洞察、有深度的文案。也許你會問,什麼樣的文案才算有意思?那怎麼做到有洞察和有深度呢?別急,下面我就從三個方面來叨叨:第一,什麼樣的文案才算有意思;第二,有意思的文案有哪些共性;第三,為什麼有意思對文案而言特別重要。
  • 做哪些事情可以提升生活品質?
    千萬不要不捨得扔,留著沒用的東西只是降低了自己的負罪感,只有記住了扔掉東西那一刻的心疼,才能讓自己在下次購物時一眼看出哪些是自己需要的,哪些將會成為垃圾。@宋閃嘗試屏蔽一部分並不那麼相關的人,在這個取捨的過程中,你會知道哪些人對你重要,當然,如果偶爾想知道一些人的近況,點進他的個人相冊看一下就行了。再比如淘寶。看看《怦然心動的整理魔法》、《斷舍離》這些吧,等你扔個幾箱衣服、幾箱亂七八糟的小物件,你就能明白哪些東西可以買了,上淘寶剁手的時間也會越來越短了。
  • 看似漫不經心,其實真的對你有意思。
    今天小編站在女生的角度上說說哪些話是代表對你有意思,大家別以為這些言語是在調戲或者說是對感情的不負責任,錯過了就是一輩子啊。比如說你打算什麼時候談戀愛,一個女孩能夠對你說出這種話,事實上已經把你納入了自己的選擇,他的內心也早已開始為你動蕩。
  • 新郎新娘迎賓的注意事項有哪些? 跟你聊聊結婚迎賓的那些事兒
    新郎新娘迎賓的注意事項有哪些? 跟你聊聊結婚迎賓的那些事兒時間:2020-04-21 12:54   來源:查查吧   責任編輯:沫朵 川北在線核心提示:原標題:新郎新娘迎賓的注意事項有哪些?跟你聊聊結婚迎賓的那些事兒 每個人都有結婚的那一天,而有些人雖然到了結婚的這一天,但在禮儀上還都不懂,不知道結婚中新郎新娘需要有哪些禮儀,而在婚禮開始前,新郎新娘是需要在門口迎賓的,那麼,新郎新娘迎賓有哪些要求   原標題:新郎新娘迎賓的注意事項有哪些?
  • 買哪些險種才是最好的?聊聊車險那些事兒
    買哪些險種才是最好的?聊聊車險那些事兒時間:2020-12-03 12:36   來源:股城網   責任編輯:沫朵 川北在線核心提示:原標題:車險第三年一般多少?買哪些險種才是最好的?聊聊車險那些事兒 很多人在買車之後,都是不知道車險的存在,要知道車險是很重要的一點,在你上路之後,可以給你一定的保障,讓你自己不再感到害怕。
  • 電視劇《破冰行動》有哪些有意思的梗!
    一起來看看,新劇《破冰行動》有哪些有意思的梗?一、緝毒警察工資2800?第一集裡,小毒販和李飛(黃景瑜飾)有如下對話:毒販:你一個月掙多少錢?李飛:我就掙那點死工資!毒販:我知道,三千不到嘛,兩千八出頭!!兩千八出頭 兩千八出頭 兩千八出頭.