亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

JavaScript中的ES6 Proxy的具體使用

2024-05-06 16:52:14
字體:
來源:轉載
供稿:網友

場景

就算只是扮演,也會成為真實的自我的一部分。對人類的精神來說,真實和虛假其實并沒有明顯的界限。入戲太深不是一件好事,但對于你來說并不成立,因為戲中的你才是真正符合你的身份的你。如今的你是真實的,就算一開始你只是在模仿著這種形象,現在的你也已經成為了這種形象。無論如何,你也不可能再回到過去了。
Proxy 代理,在 JavaScript 似乎很陌生,卻又在生活中無處不在?;蛟S有人在學習 ES6 的時候有所涉獵,但卻并未真正了解它的使用場景,平時在寫業務代碼時也不會用到這個特性。

相比于文縐縐的定義內容,想必我們更希望了解它的使用場景,使其在真正的生產環境發揮強大的作用,而不僅僅是作為一個新的特性 -- 然后,實際中完全沒有用到!

  • 為函數添加特定的功能
  • 代理對象的訪問
  • 作為膠水橋接不同結構的對象
  • 監視對象的變化
  • 還有更多。。。

如果你還沒有了解過 Proxy 特性,可以先去MDN 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 => iget._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 函數有什么問題?

這里是答案的分割線

  • 所有屬性需要預定義,不能運行時決定
  • 沒有指向原對象,后續的修改會造成麻煩

吾輩先解釋一下這兩個問題

  1. 所有屬性需要預定義,不能運行時決定

如果調用了 list[0].a 會發生什么呢?是的,依舊會是 undefined,因為 Reflect.ownKeys 也不能找到沒有定義的屬性(真*undefined),因此導致訪問未定義的屬性仍然會是 undefined 而非期望的默認值。

  1. 沒有指向原對象,后續的修改會造成麻煩

如果我們此時修改對象的一個屬性,那么會影響到原本的屬性么?不會,因為 warp 返回的對象已經是全新的了,和原對象沒有什么聯系。所以,當你修改時當然不會影響到原對象。

Pass: 我們當然可以直接修改原對象,但這很明顯不太符合我們的期望:顯示時展示默認值 '' -- 這并不意味著我們愿意在其他操作時需要 '',否則我們還要再轉換一遍。(例如發送編輯后的數據到后臺)

這個時候 Proxy 也可以派上用場,使用 Proxy 實現 warp 函數

function warp(obj) { const result = new Proxy(obj, { get(_, k) {  const v = Reflect.get(_, k)  if (v !== undefined) {  return v  }  return '' }, }) return result}

現在,上面的那兩個問題都解決了!

注: 知名的 GitHub 庫 immer 就使用了該特性實現了不可變狀態樹。

作為膠水橋接不同結構的對象

通過上面的例子我們可以知道,即便是未定義的屬性,Proxy 也能進行代理。這意味著,我們可以通過 Proxy 抹平相似對象之間結構的差異,以相同的方式處理類似的對象。

Pass: 不同公司的項目中的同一個實體的結構不一定完全相同,但基本上類似,只是字段名不同罷了。所以使用 Proxy 實現膠水橋接不同結構的對象方便我們在不同公司使用我們的工具庫!

嘛,開個玩笑,其實在同一個公司中不同的實體也會有類似的結構,也會需要相同的操作,最常見的應該是樹結構數據。例如下面的菜單實體和系統權限實體就很相似,也需要相同的操作 -- 樹 <=> 列表 相互轉換。

思考一下如何在同一個函數中處理這兩種樹節點結構

/** * 系統菜單 */class SysMenu { /** * 構造函數 * @param {Number} id 菜單 id * @param {String} name 顯示的名稱 * @param {Number} parent 父級菜單 id */ constructor(id, name, parent) { this.id = id this.name = name this.parent = parent }}/** * 系統權限 */class SysPermission { /** * 構造函數 * @param {String} uid 系統唯一 uuid * @param {String} label 顯示的菜單名 * @param {String} parentId 父級權限 uid */ constructor(uid, label, parentId) { this.uid = uid this.label = label this.parentId = parentId }}

下面讓我們使用 Proxy 來抹平訪問它們之間的差異

const sysMenuProxy = { parentId: 'parent' }const sysMenu = new Proxy(new SysMenu(1, 'rx', 0), { get(_, k) { if (Reflect.has(sysMenuProxy, k)) {  return Reflect.get(_, Reflect.get(sysMenuProxy, k)) } return Reflect.get(_, k) },})console.log(sysMenu.id, sysMenu.name, sysMenu.parentId) // 1 'rx' 0const sysPermissionProxy = { id: 'uid', name: 'label' }const sysPermission = new Proxy(new SysPermission(1, 'rx', 0), { get(_, k) { if (Reflect.has(sysPermissionProxy, k)) {  return Reflect.get(_, Reflect.get(sysPermissionProxy, k)) } return Reflect.get(_, k) },})console.log(sysPermission.id, sysPermission.name, sysPermission.parentId) // 1 'rx' 0

看起來似乎有點繁瑣,讓我們封裝一下

/** * 橋接對象不存在的字段 * @param {Object} map 代理的字段映射 Map * @returns {Function} 轉換一個對象為代理對象 */function bridge(map) { /** * 為對象添加代理的函數 * @param {Object} obj 任何對象 * @returns {Proxy} 代理后的對象 */ return function(obj) { return new Proxy(obj, {  get(target, k) {  // 如果遇到被代理的屬性則返回真實的屬性  if (Reflect.has(map, k)) {   return Reflect.get(target, Reflect.get(map, k))  }  return Reflect.get(target, k)  },  set(target, k, v) {  // 如果遇到被代理的屬性則設置真實的屬性  if (Reflect.has(map, k)) {   Reflect.set(target, Reflect.get(map, k), v)   return true  }  Reflect.set(target, k, v)  return true  }, }) }}

現在,我們可以用更簡單的方式來做代理了。

const sysMenu = bridge({ parentId: 'parent',})(new SysMenu(1, 'rx', 0))console.log(sysMenu.id, sysMenu.name, sysMenu.parentId) // 1 'rx' 0const sysPermission = bridge({ id: 'uid', name: 'label',})(new SysPermission(1, 'rx', 0))console.log(sysPermission.id, sysPermission.name, sysPermission.parentId) // 1 'rx' 0

如果想看 JavaScirpt 如何處理樹結構數據話,可以參考吾輩的JavaScript 處理樹數據結構

監視對象的變化

接下來,我們想想,平時是否有需要監視對象的變化,然后進行某些處理呢?

例如監視用戶復選框選中項列表的變化并更新對應的需要發送到后臺的 id 拼接字符串。

// 模擬頁面的復選框列表const hobbyMap = new Map() .set(1, '小說') .set(2, '動畫') .set(3, '電影') .set(4, '游戲')const user = { id: 1, // 保存興趣 id 的列表 hobbySet: new Set(), // 發送到后臺的興趣 id 拼接后的字符串,以都好進行分割 hobby: '',}function onClick(id) { user.hobbySet.has(id) ? user.hobbySet.delete(id) : user.hobbySet.add(id)}// 模擬兩次點擊onClick(1)onClick(2)console.log(user.hobby) // ''

下面使用 Proxy 來完成 hobbySet 屬性改變后 hobby 自動更新的操作

/** * 深度監聽指定對象屬性的變化 * 注:指定對象不能是原始類型,即不可變類型,而且對象本身的引用不能改變,最好使用 const 進行聲明 * @param object 需要監視的對象 * @param callback 當代理對象發生改變時的回調函數,回調函數有三個參數,分別是對象,修改的 key,修改的 v * @returns 返回源對象的一個代理 */function watchObject(object, callback) { const handler = { get(_, k) {  try {  // 注意: 這里很關鍵,它為對象的字段也添加了代理  return new Proxy(v, Reflect.get(_, k))  } catch (err) {  return Reflect.get(_, k)  } }, set(_, k, v) {  callback(_, k, v)  return Reflect.set(_, k, v) }, } return new Proxy(object, handler)}// 模擬頁面的復選框列表const hobbyMap = new Map() .set(1, '小說') .set(2, '動畫') .set(3, '電影') .set(4, '游戲')const user = { id: 1, // 保存興趣 id 的列表 hobbySet: new Set(), // 發送到后臺的興趣 id 拼接后的字符串,以都好進行分割 hobby: '',}const proxy = watchObject(user, (_, k, v) => { if (k === 'hobbySet') { _.hobby = [..._.hobbySet].join(',') }})function onClick(id) { proxy.hobbySet = proxy.hobbySet.has(id) ? proxy.hobbySet.delete(id) : proxy.hobbySet.add(id)}// 模擬兩次點擊onClick(1)onClick(2)// 現在,user.hobby 的值將會自動更新console.log(user.hobby) // 1,2

當然,這里實現的 watchObject 函數還非常非常非常簡陋,如果有需要可以進行更深度/強大的監聽,可以嘗試自行實現一下啦!

缺點

說完了這些 Proxy 的使用場景,下面稍微來說一下它的缺點

運行環境必須要 ES6 支持

這是一個不大不小的問題,現代的瀏覽器基本上都支持 ES6,但如果泥萌公司技術棧非常老舊的話(例如支持 IE6),還是安心吃土吧 #笑 #這種公司不離職等著老死

不能直接代理一些需要 this 的對象

這個問題就比較麻煩了,任何需要 this 的對象,代理之后的行為可能會發生變化。例如 Set 對象

const proxy = new Proxy(new Set([]), {})proxy.add(1) // Method Set.prototype.add called on incompatible receiver [object Object]

是不是很奇怪,解決方案是把所有的 get 操作屬性值為 function 的函數都手動綁定 this

const proxy = new Proxy(new Set([]), { get(_, k) { const v = Reflect.get(_, k) // 遇到 Function 都手動綁定一下 this if (v instanceof Function) {  return v.bind(_) } return v },})proxy.add(1)

總結

Proxy 是個很強大的特性,能夠讓我們實現一些曾經難以實現的功能(所以這就是你不支持 ES5 的理由?#打),就連 Vue3+ 都開始使用 Proxy 實現了,你還有什么理由在乎上古時期的 IE 而不用呢?(v^_^)v

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧美大尺度| 亚洲视频自拍偷拍| 亚洲精品免费一区二区三区| 国产成人精品综合久久久| 中文一区二区视频| 国产精品入口免费视| 欧美肥老太性生活视频| 亚洲第一精品福利| 欧美精品成人在线| 91精品国产综合久久香蕉的用户体验| 国产区精品视频| 欧美亚洲另类视频| 亚洲欧美日韩国产中文专区| 亚洲最大成人在线| 亚洲精品综合久久中文字幕| 精品动漫一区二区| 国产精品小说在线| 中文字幕精品在线视频| 国产精品27p| 久久久久www| 亚洲www永久成人夜色| 国产一区二区三区在线播放免费观看| 尤物yw午夜国产精品视频| 久久久亚洲天堂| 亚洲欧美在线磁力| 欧美日韩亚洲高清| 亚洲电影免费观看高清完整版在线| 亚洲精品一区二区网址| 亚洲欧美国产视频| 精品综合久久久久久97| 日韩精品中文字幕久久臀| 久久久免费观看视频| 亚洲成人中文字幕| 有码中文亚洲精品| 久久久国产一区| 欧美另类99xxxxx| 日韩av中文字幕在线播放| 国产精品免费看久久久香蕉| 国产成人拍精品视频午夜网站| 精品毛片三在线观看| 国产一区二区在线免费| 青青久久aⅴ北条麻妃| 亚洲天堂网站在线观看视频| 亚洲精品v天堂中文字幕| 精品久久久久久| 成人国产精品一区二区| 日韩免费在线电影| 欧美视频裸体精品| 欧美国产中文字幕| 亚洲综合小说区| 91高清视频免费观看| 日韩精品亚洲元码| 奇米影视亚洲狠狠色| 日韩av中文字幕在线免费观看| 91精品国产成人| 国产亚洲精品美女久久久久| 亚洲天堂av女优| 中文字幕欧美国内| 久久成人在线视频| 久久久久久久久久久国产| 日韩人在线观看| 亚洲欧美综合区自拍另类| 日本国产高清不卡| 亚洲精品福利免费在线观看| 国产精品v片在线观看不卡| 国产精品露脸av在线| 亚洲精品一区中文字幕乱码| 日韩在线观看免费高清完整版| 欧美精品在线看| 精品中文字幕在线| 日韩经典一区二区三区| 亚洲一区二区三区xxx视频| 亚洲区免费影片| 国产福利视频一区二区| 精品国内自产拍在线观看| 国模精品视频一区二区三区| 亚洲成人av在线播放| 日韩av黄色在线观看| 亚洲精品自在久久| 亚洲社区在线观看| 成人免费在线网址| 青草青草久热精品视频在线观看| 亚洲成av人影院在线观看| 亚洲精品成人免费| 九九热r在线视频精品| 国产成人精品一区二区在线| 国产精品成人一区二区| 国产精品免费电影| 欧美激情一区二区三区成人| 亚洲美女中文字幕| 一区二区三区亚洲| 国产精品福利网| 久久琪琪电影院| 久久久久999| 欧美日韩成人精品| 超碰精品一区二区三区乱码| 日韩欧美成人精品| 18久久久久久| 中文字幕亚洲字幕| 欧美日韩激情视频| 亚洲国产精品99久久| 97国产在线视频| 日韩精品免费综合视频在线播放| 国产日韩综合一区二区性色av| 国产精品福利小视频| 欧美性xxxxx极品娇小| 中文字幕精品视频| 成人免费大片黄在线播放| 中文.日本.精品| 日韩国产精品亚洲а∨天堂免| 亚洲最新av在线| 中文字幕在线日韩| 国产91精品久久久久久久| 欧美激情第99页| 亚洲韩国欧洲国产日产av| 久久久久久91| 欧美性生交大片免费| 狠狠爱在线视频一区| 亚洲黄色片网站| 精品国产乱码久久久久久婷婷| 精品亚洲永久免费精品| 亚洲伦理中文字幕| 国产精品久久久久久五月尺| 久久久精品久久久| 国产精品r级在线| 日韩电影免费在线观看中文字幕| 欧美精品久久久久久久免费观看| 午夜精品一区二区三区在线视频| 成人免费xxxxx在线观看| 国产精品久久久亚洲| 最近2019中文字幕mv免费看| 亚洲欧洲日产国码av系列天堂| 欧美国产日韩二区| 午夜精品久久久久久99热| 亚洲va国产va天堂va久久| 欧美亚洲午夜视频在线观看| 91国内揄拍国内精品对白| 欧美视频免费在线观看| 午夜精品在线观看| 国产精品成人免费电影| 国产精品九九九| 欧美在线性视频| 久久久视频精品| 热99在线视频| 91国语精品自产拍在线观看性色| 成人性生交大片免费看小说| 亚洲精品美女久久久久| 亚洲视频999| 久久久电影免费观看完整版| 午夜精品久久久99热福利| 亚洲精品久久久久久下一站| 欧美一区二区三区四区在线| 欧美精品做受xxx性少妇| 91精品免费久久久久久久久| 性欧美长视频免费观看不卡| 夜色77av精品影院| 亚洲精选在线观看| 国产成人综合精品在线| 不卡av电影在线观看| 中文字幕在线看视频国产欧美| 亚洲欧洲免费视频| 亚洲毛片在线观看.| 538国产精品一区二区在线| 狠狠久久亚洲欧美专区|