產品經理身旁過,需求變動逃不過。
測試姐姐眯眼笑,今晚bug必然多。
據悉Vue3.0
的正式版將要在本月(8月)發佈,從發佈到正式投入到正式項目中,還須要必定的過渡期,但咱們不能一直等到Vue3
正式投入到項目中的時候纔去學習,提早學習,讓你更快一步掌握Vue3.0
,升職加薪迎娶白富美就靠它了。不過在學習Vue3
以前,還須要先了解一下Proxy
,它是Vue3.0
實現數據雙向綁定的基礎。javascript
本文是做者關於Vue3.0系列的第一篇文章,後續做者將會每週發佈一篇Vue3.0相關,若是喜歡,麻煩給小編一個贊,謝謝
做爲一個單身鋼鐵直男程序員,小王最近逐漸喜歡上了前臺小妹,不過呢,他又和前臺小妹不熟,因此決定委託與前端小妹比較熟的UI
小姐姐幫忙給本身搭橋引線。小王因而請UI
小姐姐吃了一頓大餐,而後拿出一封情書委託它轉交給前臺小妹,情書上寫的 我喜歡你,我想和你睡覺
,不愧鋼鐵直男。不過這樣寫確定是沒戲的,UI
小姐姐吃人嘴短,因而幫忙改了情書,改爲了我喜歡你,我想和你一塊兒在晨輝的沐浴下起牀
,而後交給了前臺小妹。雖然有沒有撮合成功不清楚啊,不過這個故事告訴咱們,小王活該單身狗。前端
其實上面就是一個比較典型的代理模式的例子,小王想給前臺小妹送情書,由於不熟因此委託UI小姐姐
,UI
小姐姐至關於代理人,代替小王完成了送情書的事情。java
經過上面的例子,咱們想一想Vue
的數據響應原理,好比下面這段代碼ios
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
。axios
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
完善下面的代碼邏輯.app
function observe(obj, callback) {} const obj = observe( { name: '子君', sex: '男' }, (key, value) => { console.log(`屬性[${key}]的值被修改成[${value}]`) } ) // 這段代碼執行後,輸出 屬性[name]的值被修改成[妹紙] obj.name = '妹紙' // 這段代碼執行後,輸出 屬性[sex]的值被修改成[女] obj.sex = '女'
看了上面的代碼,但願你們能夠先自行實現如下,下面咱們分別用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 = '前端有的玩'
使用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
和Reflect
就學不了Vue3.0
了嗎?其實懂不懂這個是不影響學習Vue3.0
的,可是若是想深刻 去理解Vue3.0
,仍是頗有必要了解這些的。好比常常會有人在使用Vue2
的時候問,爲何我數組經過索引修改值以後,界面沒有變呢?當你瞭解到Object.defineProperty
的使用方式與限制以後,就會恍然大悟,原來如此。本文以後,小編將爲你們帶來Vue3.0
系列文章,歡迎關注,一塊兒學習。同時本文首發於公衆號【前端有的玩】,用玩的姿式學前端,就在【前端有的玩】