Vue3.0「十七」-- vue3.0升級(jí)新特性及Proxy重寫響應(yīng)式講解

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ù)組長(zhǎng)度
// set 3 d //設(shè)置值
// set length 4 設(shè)置數(shù)組長(zhǎng)度

不足:
給數(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ù)組長(zhǎng)度
// 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 //是否刪除成功
    },
  })

}


// 測(cè)試數(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"]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容