vue3.0 升級(jí)內(nèi)容
全部用TS重寫的(響應(yīng)式锰霜、vdom、模本編譯)
性能提升桐早,減少代碼量
會(huì)調(diào)整部分API
Proxy重寫響應(yīng)式
vue2.x 馬上要過時(shí)了嗎
vue3.0從正式發(fā)布到推廣癣缅,還需要一段時(shí)間
vue2.x應(yīng)用范圍廣,有大量項(xiàng)目需要維護(hù)升級(jí)
proxy存在兼容性問題哄酝,且不能ployfill
社區(qū)熱門知識(shí)點(diǎn):Proxy重寫響應(yīng)式講解
回顧vue2.*的響應(yīng)式原理 [object.defindeProperty]
object.defindeProperty缺點(diǎn):
- 深度監(jiān)聽需要一次性遞歸
- 無法監(jiān)聽新增屬性/刪除屬性(vue.set/vue.delete)
- 無法原生監(jiān)聽數(shù)組友存,需要特殊處理
vue3_Proxy實(shí)現(xiàn)響應(yīng)式原理
前置知識(shí)
Proxy ES6語法 對(duì)象用于定義基本操作的自定義行為(如屬性查找、賦值陶衅、枚舉屡立、函數(shù)調(diào)用等)。
Proxy 可以理解成搀军, 在目標(biāo)對(duì)象之前架設(shè)一層“ 攔截”膨俐, 外界對(duì)該對(duì)象的訪問, 都必須先通過這層攔截罩句, 因此提供了一種機(jī)制焚刺, 可以對(duì)外界的訪問進(jìn)行過濾和改寫。
語法:const p = new Proxy(target, handler)
target 要使用 Proxy 包裝的目標(biāo)對(duì)象(可以是任何類型的對(duì)象门烂,包括原生數(shù)組乳愉,函數(shù),甚至另一個(gè)代理)
handler 一個(gè)通常以函數(shù)作為屬性的對(duì)象诅福, 各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為匾委。
handler.get() 方法用于攔截對(duì)象的讀取屬性操作。
handler.set() 方法是設(shè)置屬性值操作的捕獲器氓润。
handler.deleteProperty() 方法用于攔截對(duì)對(duì)象屬性的 delete 操作
Reflect 是一個(gè)內(nèi)置的對(duì)象赂乐,它提供攔截 JavaScript 操作的方法這些方法與proxy handlers的方法相同。
Reflect.get(target, propertyKey[, receiver])
Reflect.deleteProperty(target, propertyKey)
Reflect.set(target, propertyKey, value[, receiver])
target: 需要取值的目標(biāo)對(duì)象咖气; key: 需要獲取的值的鍵值挨措;value::設(shè)置的值挖滤。
如果target對(duì)象中指定了getter, receiver則為getter調(diào)用時(shí)的this值
1. Proxy對(duì)數(shù)據(jù)攔截監(jiān)聽的基本使用
1浅役、用Proxy將目標(biāo)對(duì)象data進(jìn)行包裝攔截處理如下:
const proxyData = new Proxy(data, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
console.log('get', key) //監(jiān)聽
return result //返回結(jié)果
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否設(shè)置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
return result //是否刪除成功
},
})
2斩松、定義data為對(duì)象,并對(duì)data對(duì)象進(jìn)行操作時(shí)
const data = {
name: 'lili',
age: 20,
}
// 對(duì)象操作
proxyData.age // get操作 : get age
proxyData.age = 30 // set操作 : set age 30
proxyData.sex = "女" // set操作 : set sex 女
delete proxyData.name // 刪除操作 : delete property name
不足:
在對(duì)象設(shè)置屬性時(shí)觉既,無法確定是新增屬性還是原有屬性惧盹;
3、定義data為數(shù)組瞪讼,并對(duì)data數(shù)組進(jìn)行操作
const data =['a','b','c']
proxyData.push('d')
// get push push()方法觸發(fā)
// get length //獲取數(shù)組長度
// set 3 d //設(shè)置值
// set length 4 設(shè)置數(shù)組長度
不足:
給數(shù)組添加元素時(shí)钧椰,沒必監(jiān)聽 原型的屬性,如push()符欠,只需要監(jiān)聽本身(非原型)的屬性嫡霞,
set 3 d,set length 4 為重復(fù)處理同一個(gè)數(shù)據(jù)希柿,set length 4多余
2. Proxy對(duì)數(shù)據(jù)攔截監(jiān)聽使用的優(yōu)化
針對(duì)以上問題诊沪,對(duì)Proxy對(duì)數(shù)據(jù)攔截監(jiān)聽使用進(jìn)行優(yōu)化
1.在對(duì)象設(shè)置屬性時(shí),無法確定是新增屬性還是原有屬性
在set方法中判斷
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //監(jiān)聽
} else {
console.log('新增的 key')
}
2曾撤、在監(jiān)聽屬性時(shí)端姚,只監(jiān)聽本身(非原型)的屬性
在get方法中,判斷如果是自身的屬性盾戴,才進(jìn)行監(jiān)聽
// Reflect.ownKeys()方法可以返回包含Symbol屬性在內(nèi)的自有屬性寄锐。
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get',key)//監(jiān)聽
}
3、重復(fù)的數(shù)據(jù)不處理
在 set方法中尖啡,重復(fù)數(shù)據(jù)不處理
const oldVal=target[key]
if (value === oldVal) {
return true
}
以上問題,Proxy對(duì)數(shù)據(jù)攔截監(jiān)聽使用的優(yōu)化后:
const proxyData = new Proxy(data, {
// target:目標(biāo)對(duì)象剩膘、 key:被捕獲的屬性名衅斩、receiver:Proxy或者繼承Proxy的對(duì)象
get(target, key, receiver) {
// 只監(jiān)聽 處理本身(非原型)的屬性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) //監(jiān)聽
}
const result = Reflect.get(target, key, receiver)
return result //返回結(jié)果
},
// value 新屬性值。
set(target, key, value, receiver) {
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //監(jiān)聽
} else {
console.log('新增的 key')
}
// 重復(fù)的數(shù)據(jù)不處理
if (value === target[key]) {
return true
}
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否設(shè)置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
return result //是否刪除成功
},
})
定義data為數(shù)組怠褐,并對(duì)data數(shù)組進(jìn)行操作時(shí)
const data =['a','b','c']
proxyData.push('d')
// get length //獲取數(shù)組長度
// set 3 d //設(shè)置值
實(shí)現(xiàn)了只保留了對(duì)自身屬性的監(jiān)聽畏梆,重復(fù)數(shù)據(jù)沒有重復(fù)設(shè)置
3. Proxy實(shí)現(xiàn)響應(yīng)式
實(shí)現(xiàn)思路:
① 創(chuàng)建響應(yīng)式方法reactive(data),該方法可以傳入需要處理的數(shù)據(jù)對(duì)象data
函數(shù)內(nèi)邏輯:
② 判斷 data 是否為 對(duì)象或者數(shù)組奈懒,不是直接返回
③ 創(chuàng)建 Proxy 代理對(duì)象奠涌,Proxy對(duì)象中傳入data
Proxy 代理對(duì)象中的方法配置:
④ 在get()方法對(duì)數(shù)據(jù)的進(jìn)行監(jiān)聽:只監(jiān)聽 處理本身(非原型)的屬性;在返回結(jié)果中采用遞歸調(diào)用reactive(),實(shí)現(xiàn)對(duì)數(shù)據(jù)的深度監(jiān)聽
⑤ 在 set() 方法中進(jìn)行數(shù)據(jù)的新增和更新:判斷是否是新增數(shù)據(jù)磷杏;重復(fù)的數(shù)據(jù)不處理溜畅;
⑥ 在 deleteProperty() 方法中對(duì)數(shù)據(jù)進(jìn)行刪除操作;
實(shí)例:
⑦ 定義數(shù)據(jù)data,傳入響應(yīng)式方法中极祸,返回的值proxyData為實(shí)現(xiàn)響應(yīng)式的可操作數(shù)據(jù)
// 創(chuàng)建響應(yīng)式
function reactive(target = {}) {
if (typeof target != 'object' || target == null) {
// 不是對(duì)象或者數(shù)組慈格,則返回
return target
}
// 代理配置 生成代理對(duì)象
return observed = new Proxy(target, {
// target:目標(biāo)對(duì)象怠晴、 key:被捕獲的屬性名、receiver:Proxy或者繼承Proxy的對(duì)象
get(target, key, receiver) {
// 只監(jiān)聽 處理本身(非原型)的屬性 ,如push()
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) //監(jiān)聽
}
const result = Reflect.get(target, key, receiver)
// return result //返回結(jié)果
// 深度監(jiān)聽
// 性能如何提升的浴捆?
return reactive(result) //遞歸get處理 實(shí)現(xiàn)深度監(jiān)聽
},
// value 新屬性值蒜田。
set(target, key, value, receiver) {
// 判斷是否是新增屬性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key') //監(jiān)聽
} else {
console.log('新增的 key')
}
// 重復(fù)的數(shù)據(jù)不處理
const oldVal = target[key]
if (value === oldVal) {
return true
}
const result = Reflect.set(target, key, value, receiver)
console.log('set', key, value) //set age 30
return result //是否設(shè)置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key) //delete property name
console.log('result', result) //result true
return result //是否刪除成功
},
})
}
// 測試數(shù)據(jù)
const data = {
name: 'lili',
age: 20,
info: {
city: "beijing"
}
}
const proxyData = reactive(data)
vue3.0 與 vue2.* 實(shí)現(xiàn)響應(yīng)式 比較
vue3.0 基于 Proxy 實(shí)現(xiàn)響應(yīng)式
vue2.0 基于 Oject.defineProperty 實(shí)現(xiàn)響應(yīng)式
相較于vue2,Proxy 實(shí)現(xiàn)響應(yīng)式:
- 深度監(jiān)聽,性能更好
- 可監(jiān)聽新增刪除的屬性
- 可監(jiān)聽數(shù)組變化
總結(jié):
- Proxy 能規(guī)避 Oject.defineProperty的問題
- Proxy無法兼容所有瀏覽器选泻,無法 polyfill
Reflect的作用總結(jié)
1.和 Proxy 能力一一對(duì)應(yīng)
2.規(guī)范化冲粤、標(biāo)準(zhǔn)化、函數(shù)式
const obj={a:100,b:200}
'a' in obj //true
Reflect.has(obj,'a')//true
delete obj.b //true
Reflect.deleteProperty(obj,'b') //true
- 3.替代掉Object上的工具函數(shù)
const obj={a:100,b:200}
Object.getOwnPropertyNames(obj) //["a", "b"]
Reflect.ownKeys(obj) // ["a", "b"]