學習Vue3.0,先來了解一下Proxy

產品經理身旁過,需求變動逃不過。
測試姐姐眯眼笑,今晚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中將其換成了功能更強大的Proxyaxios

瞭解Proxy

ProxyES6新推出的一個特性,能夠用它去攔截 js操做的方法,從而對這些方法進行代理操做。

用Proxy重寫上面的例子

好比咱們能夠經過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.definePropertyProxy完善下面的代碼邏輯.app

function observe(obj, callback) {}

const obj = observe(
  {
    name: '子君',
    sex: '男'
  },
  (key, value) => {
    console.log(`屬性[${key}]的值被修改成[${value}]`)
  }
)

// 這段代碼執行後,輸出 屬性[name]的值被修改成[妹紙]
obj.name = '妹紙'

// 這段代碼執行後,輸出 屬性[sex]的值被修改成[女]
obj.sex = '女'

看了上面的代碼,但願你們能夠先自行實現如下,下面咱們分別用Object.definePropertyProxy去實現上面的邏輯.函數

  1. 使用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 = '女'
  1. 使用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.definePropertyProxy的用法,可是當給對象添加新的屬性的時候,區別就出來了,好比

// 添加公衆號字段
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裏面提供的getset方法了,接下來咱們一一看一下handler裏面的方法。

handler 裏面的方法列表

handler裏面的方法能夠有如下這十三個,每個都對應的一種或多種針對proxy代理對象的操做行爲

  1. handler.get

    當經過proxy去讀取對象裏面的屬性的時候,會進入到get鉤子函數裏面

  2. handler.set

    當經過proxy去爲對象設置修改屬性的時候,會進入到set鉤子函數裏面

  3. handler.has

    當使用in判斷屬性是否在proxy代理對象裏面時,會觸發has,好比

    const obj = {
      name: '子君'
    }
    console.log('name' in obj)
  4. handler.deleteProperty

    當使用delete去刪除對象裏面的屬性的時候,會進入deleteProperty`鉤子函數

  5. handler.apply

    proxy監聽的是一個函數的時候,當調用這個函數時,會進入apply鉤子函數

  6. handle.ownKeys

    當經過Object.getOwnPropertyNames,Object.getownPropertySymbols,Object.keys,Reflect.ownKeys去獲取對象的信息的時候,就會進入ownKeys這個鉤子函數

  7. handler.construct

    當使用new操做符的時候,會進入construct這個鉤子函數

  8. handler.defineProperty

    當使用Object.defineProperty去修改屬性修飾符的時候,會進入這個鉤子函數

  9. handler.getPrototypeOf

    當讀取對象的原型的時候,會進入這個鉤子函數

  10. handler.setPrototypeOf

    當設置對象的原型的時候,會進入這個鉤子函數

  11. handler.isExtensible

    當經過Object.isExtensible去判斷對象是否能夠添加新的屬性的時候,進入這個鉤子函數

  12. handler.preventExtensions

    當經過Object.preventExtensions去設置對象不能夠修改新屬性時候,進入這個鉤子函數

  13. handler.getOwnPropertyDescriptor

    在獲取代理對象某個屬性的屬性描述時觸發該操做,好比在執行 Object.getOwnPropertyDescriptor(proxy, "foo") 時會進入這個鉤子函數

Proxy提供了十三種攔截對象操做的方法,本文主要挑選其中一部分在Vue3中比較重要的進行說明,其他的建議能夠直接閱讀MDN關於Proxy的介紹。

詳細介紹

get

當經過 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,set函數的結構以下

/**
 * target: 目標對象,即經過proxy代理的對象
 * key: 要賦值的屬性名稱
 * value: 目標屬性要賦的新值
 * receiver: 與 get的receiver 基本一致
 */
handle.set(target,key,value, receiver)
示例

某系統須要錄入一系列數值用於數據統計,可是在錄入數值的時候,可能錄入的存在一部分異常值,對於這些異常值須要在錄入的時候進行處理, 好比大於100的值,轉換爲100, 小於0的值,轉換爲0, 這時候就可使用proxyset,在賦值的時候,對數據進行處理

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 = '前端有的玩'

has

當使用 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)

deleteProperty

當使用 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

其餘操做

在上文中,咱們提到了Proxyhandler提供了十三個函數,在上面咱們列舉了最經常使用的三個,其實每個的用法都是基本一致的,好比ownKeys,當經過Object.getOwnPropertyNames,Object.getownPropertySymbols,Object.keys,Reflect.ownKeys去獲取對象的信息的時候,就會進入ownKeys這個鉤子函數,使用這個咱們就能夠對一些咱們不像暴露的屬性進行保護,好比通常會約定_開頭的爲私有屬性,因此在使用Object.keys去獲取對象的全部key的時候,就能夠把全部_開頭的屬性屏蔽掉。關於剩餘的那些屬性,建議你們多去看看MDN中的介紹。

Reflect

在上面,咱們獲取屬性的值或者修改屬性的值都是經過直接操做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上面總共提供了十三個靜態方法,這十三個靜態方法與Proxyhandler上面的十三個方法是一一對應的,經過將ProxyReflect相結合,就能夠對對象上面的默認操做進行攔截處理,固然這也就屬於函數元編程的範疇了。

總結

有的同窗可能會有疑惑,我不會ProxyReflect就學不了Vue3.0了嗎?其實懂不懂這個是不影響學習Vue3.0的,可是若是想深刻 去理解Vue3.0,仍是頗有必要了解這些的。好比常常會有人在使用Vue2的時候問,爲何我數組經過索引修改值以後,界面沒有變呢?當你瞭解到Object.defineProperty的使用方式與限制以後,就會恍然大悟,原來如此。本文以後,小編將爲你們帶來Vue3.0系列文章,歡迎關注,一塊兒學習。同時本文首發於公衆號【前端有的玩】,用玩的姿式學前端,就在【前端有的玩】

相關文章
相關標籤/搜索