亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript中的ES6 Proxy的具體使用

 更新時(shí)間:2019年06月16日 09:35:51   作者:rxliuli  
這篇文章主要介紹了JavaScript中的ES6 Proxy的具體使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

場(chǎng)景

就算只是扮演,也會(huì)成為真實(shí)的自我的一部分。對(duì)人類的精神來(lái)說(shuō),真實(shí)和虛假其實(shí)并沒(méi)有明顯的界限。入戲太深不是一件好事,但對(duì)于你來(lái)說(shuō)并不成立,因?yàn)閼蛑械哪悴攀钦嬲夏愕纳矸莸哪?。如今的你是真?shí)的,就算一開(kāi)始你只是在模仿著這種形象,現(xiàn)在的你也已經(jīng)成為了這種形象。無(wú)論如何,你也不可能再回到過(guò)去了。
Proxy 代理,在 JavaScript 似乎很陌生,卻又在生活中無(wú)處不在?;蛟S有人在學(xué)習(xí) ES6 的時(shí)候有所涉獵,但卻并未真正了解它的使用場(chǎng)景,平時(shí)在寫業(yè)務(wù)代碼時(shí)也不會(huì)用到這個(gè)特性。

相比于文縐縐的定義內(nèi)容,想必我們更希望了解它的使用場(chǎng)景,使其在真正的生產(chǎn)環(huán)境發(fā)揮強(qiáng)大的作用,而不僅僅是作為一個(gè)新的特性 -- 然后,實(shí)際中完全沒(méi)有用到!

  • 為函數(shù)添加特定的功能
  • 代理對(duì)象的訪問(wèn)
  • 作為膠水橋接不同結(jié)構(gòu)的對(duì)象
  • 監(jiān)視對(duì)象的變化
  • 還有更多。。。

如果你還沒(méi)有了解過(guò) Proxy 特性,可以先去MDN Proxy 上查看基本概念及使用。

為函數(shù)添加特定的功能

下面是一個(gè)為異步函數(shù)自動(dòng)添加超時(shí)功能的高階函數(shù),我們來(lái)看一下它有什么問(wèn)題

/**
 * 為異步函數(shù)添加自動(dòng)超時(shí)功能
 * @param timeout 超時(shí)時(shí)間
 * @param action 異步函數(shù)
 * @returns 包裝后的異步函數(shù)
 */
function asyncTimeout(timeout, action) {
 return function(...args) {
 return Promise.race([
  Reflect.apply(action, this, args),
  wait(timeout).then(Promise.reject),
 ])
 }
}

一般而言,上面的代碼足以勝任,但問(wèn)題就在這里,不一般的情況 -- 函數(shù)上面包含自定義屬性呢?
眾所周知,JavaScript 中的函數(shù)是一等公民,即函數(shù)可以被傳遞,被返回,以及,被添加屬性!

例如下面這個(gè)簡(jiǎn)單的函數(shù) get,其上有著 _name 這個(gè)屬性

const get = async i => i
get._name = 'get'

一旦使用上面的 asyncTimeout 函數(shù)包裹之后,問(wèn)題便會(huì)出現(xiàn),返回的函數(shù)中 _name 屬性不見(jiàn)了。這是當(dāng)然的,畢竟實(shí)際上返回的是一個(gè)匿名函數(shù)。那么,如何才能讓返回的函數(shù)能夠擁有傳入函數(shù)參數(shù)上的所有自定義屬性呢?

一種方式是復(fù)制參數(shù)函數(shù)上的所有屬性,但這點(diǎn)實(shí)現(xiàn)起來(lái)其實(shí)并不容易,真的不容易,不信你可以看看 Lodash 的 clone 函數(shù)。那么,有沒(méi)有一種更簡(jiǎn)單的方式呢?答案就是 Proxy,它可以代理對(duì)象的指定操作,除此之外,其他的一切都指向原對(duì)象。

下面是 Proxy 實(shí)現(xiàn)的 asyncTimeout 函數(shù)

/**
 * 為異步函數(shù)添加自動(dòng)超時(shí)功能
 * @param timeout 超時(shí)時(shí)間
 * @param action 異步函數(shù)
 * @returns 包裝后的異步函數(shù)
 */
function asyncTimeout(timeout, action) {
 return new Proxy(action, {
 apply(_, _this, args) {
  return Promise.race([
  Reflect.apply(_, _this, args),
  wait(timeout).then(Promise.reject),
  ])
 },
 })
}

測(cè)試一下,是可以正常調(diào)用與訪問(wèn)其上的屬性的

;(async () => {
 console.log(await get(1))
 console.log(get._name)
})()

好了,這便是吾輩最常用的一種方式了 -- 封裝高階函數(shù),為函數(shù)添加某些功能。

代理對(duì)象的訪問(wèn)

下面是一段代碼,用以在頁(yè)面上展示從后臺(tái)獲取的數(shù)據(jù),如果字段沒(méi)有值則默認(rèn)展示 ''

模擬一個(gè)獲取列表的異步請(qǐng)求

async function list() {
 // 此處僅為構(gòu)造列表
 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: '幻想鄉(xiāng)' }),
 ]
}

嘗試直接通過(guò)解構(gòu)為屬性賦予默認(rèn)值,并在默認(rèn)值實(shí)現(xiàn)這個(gè)功能

;(async () => {
 // 為所有為賦值屬性都賦予默認(rèn)值 ''
 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 () => {
 // 為所有為賦值屬性都賦予默認(rèn)值 ''
 const persons = (await list()).map(warp)
 console.log(persons)
})()

暫且先看一下這里的 warp 函數(shù)有什么問(wèn)題?

這里是答案的分割線

  • 所有屬性需要預(yù)定義,不能運(yùn)行時(shí)決定
  • 沒(méi)有指向原對(duì)象,后續(xù)的修改會(huì)造成麻煩

吾輩先解釋一下這兩個(gè)問(wèn)題

  1. 所有屬性需要預(yù)定義,不能運(yùn)行時(shí)決定

如果調(diào)用了 list[0].a 會(huì)發(fā)生什么呢?是的,依舊會(huì)是 undefined,因?yàn)?Reflect.ownKeys 也不能找到?jīng)]有定義的屬性(真*undefined),因此導(dǎo)致訪問(wèn)未定義的屬性仍然會(huì)是 undefined 而非期望的默認(rèn)值。

  1. 沒(méi)有指向原對(duì)象,后續(xù)的修改會(huì)造成麻煩

如果我們此時(shí)修改對(duì)象的一個(gè)屬性,那么會(huì)影響到原本的屬性么?不會(huì),因?yàn)?warp 返回的對(duì)象已經(jīng)是全新的了,和原對(duì)象沒(méi)有什么聯(lián)系。所以,當(dāng)你修改時(shí)當(dāng)然不會(huì)影響到原對(duì)象。

Pass: 我們當(dāng)然可以直接修改原對(duì)象,但這很明顯不太符合我們的期望:顯示時(shí)展示默認(rèn)值 '' -- 這并不意味著我們?cè)敢庠谄渌僮鲿r(shí)需要 '',否則我們還要再轉(zhuǎn)換一遍。(例如發(fā)送編輯后的數(shù)據(jù)到后臺(tái))

這個(gè)時(shí)候 Proxy 也可以派上用場(chǎng),使用 Proxy 實(shí)現(xiàn) warp 函數(shù)

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

現(xiàn)在,上面的那兩個(gè)問(wèn)題都解決了!

注: 知名的 GitHub 庫(kù) immer 就使用了該特性實(shí)現(xiàn)了不可變狀態(tài)樹(shù)。

作為膠水橋接不同結(jié)構(gòu)的對(duì)象

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

Pass: 不同公司的項(xiàng)目中的同一個(gè)實(shí)體的結(jié)構(gòu)不一定完全相同,但基本上類似,只是字段名不同罷了。所以使用 Proxy 實(shí)現(xiàn)膠水橋接不同結(jié)構(gòu)的對(duì)象方便我們?cè)诓煌臼褂梦覀兊墓ぞ邘?kù)!

嘛,開(kāi)個(gè)玩笑,其實(shí)在同一個(gè)公司中不同的實(shí)體也會(huì)有類似的結(jié)構(gòu),也會(huì)需要相同的操作,最常見(jiàn)的應(yīng)該是樹(shù)結(jié)構(gòu)數(shù)據(jù)。例如下面的菜單實(shí)體和系統(tǒng)權(quán)限實(shí)體就很相似,也需要相同的操作 -- 樹(shù) <=> 列表 相互轉(zhuǎn)換。

思考一下如何在同一個(gè)函數(shù)中處理這兩種樹(shù)節(jié)點(diǎn)結(jié)構(gòu)

/**
 * 系統(tǒng)菜單
 */
class SysMenu {
 /**
 * 構(gòu)造函數(shù)
 * @param {Number} id 菜單 id
 * @param {String} name 顯示的名稱
 * @param {Number} parent 父級(jí)菜單 id
 */
 constructor(id, name, parent) {
 this.id = id
 this.name = name
 this.parent = parent
 }
}
/**
 * 系統(tǒng)權(quán)限
 */
class SysPermission {
 /**
 * 構(gòu)造函數(shù)
 * @param {String} uid 系統(tǒng)唯一 uuid
 * @param {String} label 顯示的菜單名
 * @param {String} parentId 父級(jí)權(quán)限 uid
 */
 constructor(uid, label, parentId) {
 this.uid = uid
 this.label = label
 this.parentId = parentId
 }
}

下面讓我們使用 Proxy 來(lái)抹平訪問(wèn)它們之間的差異

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' 0

const 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

看起來(lái)似乎有點(diǎn)繁瑣,讓我們封裝一下

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

現(xiàn)在,我們可以用更簡(jiǎn)單的方式來(lái)做代理了。

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

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

如果想看 JavaScirpt 如何處理樹(shù)結(jié)構(gòu)數(shù)據(jù)話,可以參考吾輩的JavaScript 處理樹(shù)數(shù)據(jù)結(jié)構(gòu)

監(jiān)視對(duì)象的變化

接下來(lái),我們想想,平時(shí)是否有需要監(jiān)視對(duì)象的變化,然后進(jìn)行某些處理呢?

例如監(jiān)視用戶復(fù)選框選中項(xiàng)列表的變化并更新對(duì)應(yīng)的需要發(fā)送到后臺(tái)的 id 拼接字符串。

// 模擬頁(yè)面的復(fù)選框列表
const hobbyMap = new Map()
 .set(1, '小說(shuō)')
 .set(2, '動(dòng)畫')
 .set(3, '電影')
 .set(4, '游戲')
const user = {
 id: 1,
 // 保存興趣 id 的列表
 hobbySet: new Set(),
 // 發(fā)送到后臺(tái)的興趣 id 拼接后的字符串,以都好進(jìn)行分割
 hobby: '',
}
function onClick(id) {
 user.hobbySet.has(id) ? user.hobbySet.delete(id) : user.hobbySet.add(id)
}

// 模擬兩次點(diǎn)擊
onClick(1)
onClick(2)

console.log(user.hobby) // ''

下面使用 Proxy 來(lái)完成 hobbySet 屬性改變后 hobby 自動(dòng)更新的操作

/**
 * 深度監(jiān)聽(tīng)指定對(duì)象屬性的變化
 * 注:指定對(duì)象不能是原始類型,即不可變類型,而且對(duì)象本身的引用不能改變,最好使用 const 進(jìn)行聲明
 * @param object 需要監(jiān)視的對(duì)象
 * @param callback 當(dāng)代理對(duì)象發(fā)生改變時(shí)的回調(diào)函數(shù),回調(diào)函數(shù)有三個(gè)參數(shù),分別是對(duì)象,修改的 key,修改的 v
 * @returns 返回源對(duì)象的一個(gè)代理
 */
function watchObject(object, callback) {
 const handler = {
 get(_, k) {
  try {
  // 注意: 這里很關(guān)鍵,它為對(duì)象的字段也添加了代理
  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)
}

// 模擬頁(yè)面的復(fù)選框列表
const hobbyMap = new Map()
 .set(1, '小說(shuō)')
 .set(2, '動(dòng)畫')
 .set(3, '電影')
 .set(4, '游戲')
const user = {
 id: 1,
 // 保存興趣 id 的列表
 hobbySet: new Set(),
 // 發(fā)送到后臺(tái)的興趣 id 拼接后的字符串,以都好進(jìn)行分割
 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)
}
// 模擬兩次點(diǎn)擊
onClick(1)
onClick(2)

// 現(xiàn)在,user.hobby 的值將會(huì)自動(dòng)更新
console.log(user.hobby) // 1,2

當(dāng)然,這里實(shí)現(xiàn)的 watchObject 函數(shù)還非常非常非常簡(jiǎn)陋,如果有需要可以進(jìn)行更深度/強(qiáng)大的監(jiān)聽(tīng),可以嘗試自行實(shí)現(xiàn)一下啦!

缺點(diǎn)

說(shuō)完了這些 Proxy 的使用場(chǎng)景,下面稍微來(lái)說(shuō)一下它的缺點(diǎn)

運(yùn)行環(huán)境必須要 ES6 支持

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

不能直接代理一些需要 this 的對(duì)象

這個(gè)問(wèn)題就比較麻煩了,任何需要 this 的對(duì)象,代理之后的行為可能會(huì)發(fā)生變化。例如 Set 對(duì)象

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

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

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

總結(jié)

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • JS動(dòng)態(tài)創(chuàng)建Table,Tr,Td并賦值的具體實(shí)現(xiàn)

    JS動(dòng)態(tài)創(chuàng)建Table,Tr,Td并賦值的具體實(shí)現(xiàn)

    這篇文章介紹了JS動(dòng)態(tài)創(chuàng)建Table,Tr,Td并賦值的具體實(shí)現(xiàn),有需要的朋友可以參考一下
    2013-07-07
  • layer.confirm點(diǎn)擊第一個(gè)按鈕關(guān)閉彈出框的方法

    layer.confirm點(diǎn)擊第一個(gè)按鈕關(guān)閉彈出框的方法

    今天小編就為大家分享一篇layer.confirm點(diǎn)擊第一個(gè)按鈕關(guān)閉彈出框的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-09-09
  • 更優(yōu)雅的微信小程序骨架屏實(shí)現(xiàn)詳解

    更優(yōu)雅的微信小程序骨架屏實(shí)現(xiàn)詳解

    這篇文章主要介紹了更優(yōu)雅的微信小程序骨架屏實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 微信小程序?qū)崿F(xiàn)上傳照片代碼實(shí)例解析

    微信小程序?qū)崿F(xiàn)上傳照片代碼實(shí)例解析

    這篇文章主要介紹了微信小程序?qū)崿F(xiàn)上傳照片代碼實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Typescript中索引簽名使用詳解

    Typescript中索引簽名使用詳解

    這篇文章主要介紹了Typescript中索引簽名使用詳解,需要的朋友可以參考下
    2023-08-08
  • Webpack的Loader和Plugin的區(qū)別

    Webpack的Loader和Plugin的區(qū)別

    這篇文章主要介紹了Webpack的Loader和Plugin的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • js獲取元素的偏移量offset簡(jiǎn)單方法(必看)

    js獲取元素的偏移量offset簡(jiǎn)單方法(必看)

    下面小編就為大家?guī)?lái)一篇js獲取元素的偏移量offset簡(jiǎn)單方法(必看)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • 利用D3.js實(shí)現(xiàn)最簡(jiǎn)單的柱狀圖示例代碼

    利用D3.js實(shí)現(xiàn)最簡(jiǎn)單的柱狀圖示例代碼

    D3.js是一個(gè)基于數(shù)據(jù)操作文檔JavaScript庫(kù)。D3幫助你給數(shù)據(jù)帶來(lái)活力通過(guò)使用HTML、SVG和CSS。D3重視Web標(biāo)準(zhǔn)為你提供現(xiàn)代瀏覽器的全部功能,而不是給你一個(gè)專有的框架。最近在學(xué)習(xí)D3.js,這個(gè)例子是通過(guò)d3.js畫一個(gè)簡(jiǎn)單的柱狀圖。下面來(lái)一起看看吧。
    2016-12-12
  • 一文搞懂V8引擎的垃圾回收機(jī)制

    一文搞懂V8引擎的垃圾回收機(jī)制

    我們平時(shí)在寫代碼的過(guò)程中,好像很少需要自己手動(dòng)進(jìn)行垃圾回收,那么V8是如何來(lái)減少內(nèi)存占用,從而避免內(nèi)存溢出而導(dǎo)致程序崩潰的情況的,為了更高效地回收垃圾,V8引入了兩個(gè)垃圾回收器,它們分別針對(duì)不同場(chǎng)景進(jìn)行工作
    2023-06-06
  • js實(shí)現(xiàn)class樣式的修改、添加及刪除的方法

    js實(shí)現(xiàn)class樣式的修改、添加及刪除的方法

    這篇文章主要介紹了js實(shí)現(xiàn)class樣式的修改、添加及刪除的方法,主要通過(guò)修改標(biāo)簽的className來(lái)實(shí)現(xiàn)這一功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-01-01

最新評(píng)論