您好,登錄后才能下訂單哦!
這篇文章主要講解了“Proxy使用實例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Proxy使用實例分析”吧!
作為一個單身鋼鐵直男程序員,小王最近逐漸喜歡上了前端小妹,不過呢,他又和前臺小妹不熟,所以決定委托與前端小妹比較熟的UI
小姐姐幫忙給自己搭橋引線。小王于是請UI
小姐姐吃了一頓大餐,然后拿出一封情書委托它轉交給前臺小妹,情書上寫的 我喜歡你,我想和你睡覺
,不愧鋼鐵直男。不過這樣寫肯定是沒戲的,UI
小姐姐吃人嘴短,于是幫忙改了情書,改成了我喜歡你,我想和你一起在晨輝的沐浴下起床
,然后交給了前臺小妹。雖然有沒有撮合成功不清楚啊,不過這個故事告訴我們,小王活該單身狗。
其實上面就是一個比較典型的代理模式的例子,小王想給前臺小妹送情書,因為不熟所以委托UI小姐姐
,UI
小姐姐相當于代理人,代替小王完成了送情書的事情。
通過上面的例子,我們想想Vue
的數據響應原理,比如下面這段代碼
const xiaowang = { love: '我喜歡你,我想和你睡覺' } // 送給小姐姐情書 function sendToMyLove(obj) { console.log(obj.love) return '流氓,滾' } console.log(sendToMyLove(xiaowang))
如果沒有UI
小姐姐代替送情書,顯示結局是悲慘的,想想Vue2.0
的雙向綁定,通過Object.defineProperty
來監聽的屬性 get
,set
方法來實現雙向綁定,這個Object.defineProperty
就相當于UI
小姐姐
const xiaowang = { loveLetter: '我喜歡你,我想和你睡覺' } // UI小姐姐代理 Object.defineProperty(xiaowang,'love', { get() { return xiaowang.loveLetter.replace('睡覺','一起在晨輝的沐浴下起床') } }) // 送給小姐姐情書 function sendToMyLove(obj) { console.log(obj.love) return '小伙子還挺有詩情畫意的么,不過老娘不喜歡,滾' } console.log(sendToMyLove(xiaowang))
雖然依然是一個悲慘的故事,因為送奔馳的成功率可能會更高一些。但是我們可以看到,通過Object.defineproperty
可以對對象的已有屬性進行攔截,然后做一些額外的操作。
在Vue2.0
中,數據雙向綁定就是通過Object.defineProperty
去監聽對象的每一個屬性,然后在get
,set
方法中通過發布訂閱者模式來實現的數據響應,但是存在一定的缺陷,比如只能監聽已存在的屬性,對于新增刪除屬性就無能為力了,同時無法監聽數組的變化,所以在Vue3.0
中將其換成了功能更強大的Proxy
。
(推薦教程:Vue 2教程)
Proxy
是ES6
新推出的一個特性,可以用它去攔截js
操作的方法,從而對這些方法進行代理操作。
比如我們可以通過Proxy
對上面的送情書情節進行重寫:
const xiaowang = { loveLetter: '我喜歡你,我想和你睡覺' } const proxy = new Proxy(xiaowang, { get(target,key) { if(key === 'loveLetter') { return target[key].replace('睡覺','一起在晨輝的沐浴下起床') } } }) // 送給小姐姐情書 function sendToMyLove(obj) { console.log(obj.loveLetter) return '小伙子還挺有詩情畫意的么,不過老娘不喜歡,滾' } console.log(sendToMyLove(proxy))
請分別使用Object.defineProperty
和Proxy
完善下面的代碼邏輯.
function observe(obj, callback) {} const obj = observe( { name: '子君', sex: '男' }, (key, value) => { console.log(`屬性[${key}]的值被修改為[${value}]`) } ) // 這段代碼執行后,輸出 屬性[name]的值被修改為[妹紙] obj.name = '妹紙' // 這段代碼執行后,輸出 屬性[sex]的值被修改為[女] obj.name = '女'
看了上面的代碼,希望大家可以先自行實現以下,下面我們分別用Object.defineProperty
和Proxy
去實現上面的邏輯.
使用Object.defineProperty
/** * 請實現這個函數,使下面的代碼邏輯正常運行 * @param {*} obj 對象 * @param {*} callback 回調函數 */ function observe(obj, callback) { const newObj = {} Object.keys(obj).forEach(key => { Object.defineProperty(newObj, key, { configurable: true, enumerable: true, get() { return obj[key] }, // 當屬性的值被修改時,會調用set,這時候就可以在set里面調用回調函數 set(newVal) { obj[key] = newVal callback(key, newVal) } }) }) return newObj } const obj = observe( { name: '子君', sex: '男' }, (key, value) => { console.log(`屬性[${key}]的值被修改為[${value}]`) } ) // 這段代碼執行后,輸出 屬性[name]的值被修改為[妹紙] obj.name = '妹紙' // 這段代碼執行后,輸出 屬性[sex]的值被修改為[女] obj.name = '女'
使用Proxy
function observe(obj, callback) { return new Proxy(obj, { get(target, key) { return target[key] }, set(target, key, value) { target[key] = value callback(key, value) } }) } const obj = observe( { name: '子君', sex: '男' }, (key, value) => { console.log(`屬性[${key}]的值被修改為[${value}]`) } ) // 這段代碼執行后,輸出 屬性[name]的值被修改為[妹紙] obj.name = '妹紙' // 這段代碼執行后,輸出 屬性[sex]的值被修改為[女] obj.name = '女'
通過上面兩種不同實現方式,我們可以大概的了解到Object.defineProperty
和Proxy
的用法,但是當給對象添加新的屬性的時候,區別就出來了,比如
// 添加編程網站 obj.gzh = 'W3Cschool億速云'
使用Object.defineProperty
無法監聽到新增屬性,但是使用Proxy
是可以監聽到的。對比上面兩段代碼可以發現有以下幾點不同
Object.defineProperty
監聽的是對象的每一個屬性,而Proxy
監聽的是對象自身
使用Object.defineProperty
需要遍歷對象的每一個屬性,對于性能會有一定的影響
Proxy
對新增的屬性也能監聽到,但Object.defineProperty
無法監聽到。
Proxy
在MDN
中,關于Proxy
是這樣介紹的: Proxy
對象用于定義基本操作的自定義行為(如屬性查找、賦值、枚舉、函數調用等)。什么意思呢?Proxy
就像一個攔截器一樣,它可以在讀取對象的屬性,修改對象的屬性,獲取對象屬性列表,通過for in
循環等等操作的時候,去攔截對象上面的默認行為,然后自己去自定義這些行為,比如上面例子中的set
,我們通過攔截默認的set
,然后在自定義的set
里面添加了回調函數的調用
Proxy
的語法格式如下
/** * target: 要兼容的對象,可以是一個對象,數組,函數等等 * handler: 是一個對象,里面包含了可以監聽這個對象的行為函數,比如上面例子里面的`get`與`set` * 同時會返回一個新的對象proxy, 為了能夠觸發handler里面的函數,必須要使用返回值去進行其他操作,比如修改值 */ const proxy = new Proxy(target, handler)
在上面的例子里面,我們已經使用到了handler
里面提供的get
與set
方法了,接下來我們一一看一下handler
里面的方法。
handler
里面的方法可以有以下這十三個,每一個都對應的一種或多種針對proxy
代理對象的操作行為
handler.get
當通過proxy
去讀取對象里面的屬性的時候,會進入到get
鉤子函數里面
handler.set
當通過proxy
去為對象設置修改屬性的時候,會進入到set
鉤子函數里面
handler.has
當使用in
判斷屬性是否在proxy
代理對象里面時,會觸發has
,比如
const obj = { name: '子君' } console.log('name' in obj)
handler.deleteProperty
當使用delete
去刪除對象里面的屬性的時候,會進入deleteProperty`鉤子函數
handler.apply
當proxy
監聽的是一個函數的時候,當調用這個函數時,會進入apply
鉤子函數
handle.ownKeys
當通過Object.getOwnPropertyNames
,Object.getownPropertySymbols
,Object.keys
,Reflect.ownKeys
去獲取對象的信息的時候,就會進入ownKeys
這個鉤子函數
handler.construct
當使用new
操作符的時候,會進入construct
這個鉤子函數
handler.defineProperty
當使用Object.defineProperty
去修改屬性修飾符的時候,會進入這個鉤子函數
handler.getPrototypeOf
當讀取對象的原型的時候,會進入這個鉤子函數
handler.setPrototypeOf
當設置對象的原型的時候,會進入這個鉤子函數
handler.isExtensible
當通過Object.isExtensible
去判斷對象是否可以添加新的屬性的時候,進入這個鉤子函數
handler.preventExtensions
當通過Object.preventExtensions
去設置對象不可以修改新屬性時候,進入這個鉤子函數
handler.getOwnPropertyDescriptor
在獲取代理對象某個屬性的屬性描述時觸發該操作,比如在執行 Object.getOwnPropertyDescriptor(proxy, "foo")
時會進入這個鉤子函數
Proxy
提供了十三種攔截對象操作的方法,本文主要挑選其中一部分在Vue3
中比較重要的進行說明,其余的建議可以直接閱讀MDN
關于Proxy
的介紹。
當通過
proxy
去讀取對象里面的屬性的時候,會進入到get
鉤子函數里面
當我們從一個proxy
代理上面讀取屬性的時候,就會觸發get
鉤子函數,get
函數的結構如下
/** * target: 目標對象,即通過proxy代理的對象 * key: 要訪問的屬性名稱 * receiver: receiver相當于是我們要讀取的屬性的this,一般情況 * 下他就是proxy對象本身,關于receiver的作用,后文將具體講解 */ handle.get(target,key, receiver)
我們在工作中經常會有封裝axios
的需求,在封裝過程中,也需要對請求異常進行封裝,比如不同的狀態碼返回的異常信息是不同的,如下是一部分狀態碼及其提示信息:
// 狀態碼提示信息 const errorMessage = { 400: '錯誤請求', 401: '系統未授權,請重新登錄', 403: '拒絕訪問', 404: '請求失敗,未找到該資源' } // 使用方式 const code = 404 const message = errorMessage[code] console.log(message)
但這存在一個問題,狀態碼很多,我們不可能每一個狀態碼都去枚舉出來,所以對于一些異常狀態碼,我們希望可以進行統一提示,如提示為系統異常,請聯系管理員
,這時候就可以使用Proxy
對錯誤信息進行代理處理
// 狀態碼提示信息 const errorMessage = { 400: '錯誤請求', 401: '系統未授權,請重新登錄', 403: '拒絕訪問', 404: '請求失敗,未找到該資源' } const proxy = new Proxy(errorMessage, { get(target,key) { const value = target[key] return value || '系統異常,請聯系管理員' } }) // 輸出 錯誤請求 console.log(proxy[400]) // 輸出 系統異常,請聯系管理員 console.log(proxy[500])
當為對象里面的屬性賦值的時候,會觸發
set
當給對象里面的屬性賦值的時候,會觸發set
,set
函數的結構如下
/** * target: 目標對象,即通過proxy代理的對象 * key: 要賦值的屬性名稱 * value: 目標屬性要賦的新值 * receiver: 與 get的receiver 基本一致 */ handle.set(target,key,value, receiver)
某系統需要錄入一系列數值用于數據統計,但是在錄入數值的時候,可能錄入的存在一部分異常值,對于這些異常值需要在錄入的時候進行處理, 比如大于100
的值,轉換為100
, 小于0
的值,轉換為0
, 這時候就可以使用proxy
的set
,在賦值的時候,對數據進行處理
const numbers = [] const proxy = new Proxy(numbers, { set(target,key,value) { if(value < 0) { value = 0 }else if(value > 100) { value = 100 } target[key] = value // 對于set 來說,如果操作成功必須返回true, 否則會被視為失敗 return true } }) proxy.push(1) proxy.push(101) proxy.push(-10) // 輸出 [1, 100, 0] console.log(numbers)
Vue2.0
在使用Vue2.0
的時候,如果給對象添加新屬性的時候,往往需要調用$set
, 這是因為Object.defineProperty
只能監聽已存在的屬性,而新增的屬性無法監聽,而通過$set
相當于手動給對象新增了屬性,然后再觸發數據響應。但是對于Vue3.0
來說,因為使用了Proxy
, 在他的set
鉤子函數中是可以監聽到新增屬性的,所以就不再需要使用$set
const obj = { name: '子君' } const proxy = new Proxy(obj, { set(target,key,value) { if(!target.hasOwnProperty(key)) { console.log(`新增了屬性${key},值為${value}`) } target[key] = value return true } }) // 新增 公眾號 屬性 // 輸出 新增了屬性gzh,值為前端有的玩 proxy.gzh = '前端有的玩'
當使用
in
判斷屬性是否在proxy
代理對象里面時,會觸發has
/** * target: 目標對象,即通過proxy代理的對象 * key: 要判斷的key是否在target中 */ handle.has(target,key)
一般情況下我們在js
中聲明私有屬性的時候,會將屬性的名字以_
開頭,對于這些私有屬性,是不需要外部調用,所以如果可以隱藏掉是最好的,這時候就可以通過has
在判斷某個屬性是否在對象時,如果以_
開頭,則返回false
const obj = { publicMethod() {}, _privateMethod(){} } const proxy = new Proxy(obj, { has(target, key) { if(key.startsWith('_')) { return false } return Reflect.get(target,key) } }) // 輸出 false console.log('_privateMethod' in proxy) // 輸出 true console.log('publicMethod' in proxy)
當使用
delete
去刪除對象里面的屬性的時候,會進入deleteProperty`攔截器
/** * target: 目標對象,即通過proxy代理的對象 * key: 要刪除的屬性 */ handle.deleteProperty(target,key)
現在有一個用戶信息的對象,對于某些用戶信息,只允許查看,但不能刪除或者修改,對此使用Proxy
可以對不能刪除或者修改的屬性進行攔截并拋出異常,如下
const userInfo = { name: '子君', gzh: '前端有的玩', sex: '男', age: 22 } // 只能刪除用戶名和公眾號 const readonlyKeys = ['name', 'gzh'] const proxy = new Proxy(userInfo, { set(target,key,value) { if(readonlyKeys.includes(key)) { throw new Error(`屬性${key}不能被修改`) } target[key] = value return true }, deleteProperty(target,key) { if(readonlyKeys.includes(key)) { throw new Error(`屬性${key}不能被刪除`) return } delete target[key] return true } }) // 報錯 delete proxy.name
Vue2.0
其實與$set
解決的問題類似,Vue2.0
是無法監聽到屬性被刪除的,所以提供了$delete
用于刪除屬性,但是對于Proxy
,是可以監聽刪除操作的,所以就不需要再使用$delete
了
在上文中,我們提到了Proxy
的handler
提供了十三個函數,在上面我們列舉了最常用的三個,其實每一個的用法都是基本一致的,比如ownKeys
,當通過Object.getOwnPropertyNames
,Object.getownPropertySymbols
,Object.keys
,Reflect.ownKeys
去獲取對象的信息的時候,就會進入ownKeys
這個鉤子函數,使用這個我們就可以對一些我們不像暴露的屬性進行保護,比如一般會約定_
開頭的為私有屬性,所以在使用Object.keys
去獲取對象的所有key
的時候,就可以把所有_
開頭的屬性屏蔽掉。關于剩余的那些屬性,建議大家多去看看MDN
中的介紹。
在上面,我們獲取屬性的值或者修改屬性的值都是通過直接操作target
來實現的,但實際上ES6
已經為我們提供了在Proxy
內部調用對象的默認行為的API
,即Reflect
。比如下面的代碼
const obj = {} const proxy = new Proxy(obj, { get(target,key,receiver) { return Reflect.get(target,key,receiver) } })
大家可能看到上面的代碼與直接使用target[key]
的方式沒什么區別,但實際上Reflect
的出現是為了讓Object
上面的操作更加規范,比如我們要判斷某一個prop
是否在一個對象中,通常會使用到in
,即
const obj = {name: '子君'} console.log('name' in obj)
但上面的操作是一種命令式的語法,通過Reflect
可以將其轉變為函數式的語法,顯得更加規范
Reflect.has(obj,'name')
除了has
,get
之外,其實Reflect
上面總共提供了十三個靜態方法,這十三個靜態方法與Proxy
的handler
上面的十三個方法是一一對應的,通過將Proxy
與Reflect
相結合,就可以對對象上面的默認操作進行攔截處理,當然這也就屬于函數元編程的范疇了。
感謝各位的閱讀,以上就是“Proxy使用實例分析”的內容了,經過本文的學習后,相信大家對Proxy使用實例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。