1. 程式人生 > 其它 >Object.defineProperty與proxy進行對比

Object.defineProperty與proxy進行對比

技術標籤:Vue個人筆記vueproxy前端

Object.defineProperty() 和 ES2015 中新增的 Proxy 物件,會經常用來做資料劫持.
資料劫持:在訪問或者修改物件的某個屬性時,通過一段程式碼攔截這個行為,進行額外的操作或者修改返回結果.資料劫持最典型的應用------雙向的資料繫結

  • Vue 2.x 利用 Object.defineProperty(),並且把內部解耦為 Observer, Dep, 並使用 Watcher 相連
  • Vue 在 3.x 版本之後改用 Proxy 進行實現

Object.defineProperty

先來用Object.defineProperty實現一下物件的攔截。


<html>
<head>
</head>
<body>
<script>

let data = {
  m:234,
  n:[1,34,4,5676],
  h:{
    c:34
  }
}

function observer(data){
  if(typeof data === 'object'){
    Object.keys(data).forEach(key=>{
      defineReactive(data,key,data[key])
    })
  }
}

function defineReactive
(obj,key,val){ Object.defineProperty(obj,key,{ get(){ console.log('get',key) return val }, set(newVal){ console.log('set',newVal) if(newVal !== val ) val = newVal } }) } observer(data) // 給data新增監聽 data.m = 111 // 觸發defineReactive()裡的set() console.log('data.m的值:'
, data.m) // 觸發defineReactive()裡的get() data.h.c = 111 // 這樣不會觸發defineReactive()裡的set(),但觸發了data.h的get() console.log('data.h.c的值:', data.h.c) // 觸發了data.h的get() </script> </body> </html>

在這裡插入圖片描述

上面通過遍歷data的資料,進行了一次簡單的攔截;看似沒有問題,但如果我們改變data.h.c是不會觸發set鉤子的,為什麼?因為這裡還沒有實現遞迴,所以只攔截了最表面的一層,裡面的則沒有被攔截。

遞迴攔截物件


function defineReactive(obj,key,val){
  observer(val)
  Object.defineProperty(obj,key,{
    get(){
      console.log('get')
      return val
    },
    set(newVal){
      console.log('set')
      if(newVal !== val ) val = newVal
    }
  })
}

在這裡插入圖片描述

遞迴攔截,只要在defineReactive函式再調一次observer函式把要攔截的值傳給它就行。這樣,就實現了物件的多層攔截。但是呢,現在是攔截不到陣列的,當我們呼叫push,pop等方法它是不會觸發set鉤子的,為什麼?因為Object.defineProperty壓根就不支援陣列的攔截。既然它不支援,那麼我們只能攔截它的這些(‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, ‘reverse’)改變自身資料的方法了。

Object.defineProperty陣列的攔截


function arrayMethods(){

  const arrProto = Array.prototype

  const arrayMethods = Object.create(arrProto)

  const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

  methods.forEach(function (method) {

      const original = arrProto[method]

      Object.defineProperty(arrayMethods, method, {

          value: function v(...args) {
              console.log('set arrayMethods')
              return original.apply(this, args)

          }

      })

  })
  return arrayMethods
}

以上就是對這些陣列的原型方法進行了一個攔截,然後把它覆蓋要攔截的陣列的原型就行,下面簡單修改一下observer


function observer(data){
  if(typeof data === 'object'){
    if(Array.isArray(data)){
      data.__proto__ = arrayMethods()
    }else{
      Object.keys(data).forEach(key=>{
        defineReactive(data,key,data[key])
      })
    }
  }
}

在vue中,還會判斷該key有沒有__proto__,如果沒有就直接把這些方法放到這個key的自身上,如果有就直接覆蓋這個__proto__。


function arrayMethods(){

  const arrProto = Array.prototype

  const arrayMethods = Object.create(arrProto)

  const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

  methods.forEach(function (method) {

      const original = arrProto[method]

      Object.defineProperty(arrayMethods, method, {

          value: function v(...args) {
              console.log('set arrayMethods')
              return original.apply(this, args)

          }

      })

  })
  return arrayMethods
}

function observer(data){
  if(typeof data === 'object'){
    if(Array.isArray(data)){
      data.__proto__ = arrayMethods()
    }else{
      Object.keys(data).forEach(key=>{
        defineReactive(data,key,data[key])
      })
    }
  }
}

function defineReactive(obj,key,val){
  observer(val)
  Object.defineProperty(obj,key,{
    get(){
      console.log('get')
      return val
    },
    set(newVal){
      console.log('set')
      if(newVal !== val ) val = newVal
    }
  })
}

observer(data)


以上,就完成了對物件和陣列的攔截(說明:vue2.x中的實現會比這裡複雜,但大概思路和大概實現是這樣),看似辛苦點,換來一個完美的結果挺不錯的。但真的是你想的那樣嗎?試一下呼叫data.n[1] = xxx,它是不會觸發set鉤子的,這也是在proxy出現之前,無能無力的,所以在vue中提供了 s e t , set, set,deleteAPI。

proxy

這裡就不介紹proxy了,就當你對它有了解過了。直接上程式碼

  let data = {
  m:234,
  n:[1,34,4,5676],
  h:{
    c:34
  }
}

function defineReactive(obj){
  Object.keys(obj).forEach((key) => {
    if(typeof obj[key] === 'object'){
      obj[key] = defineReactive(obj[key])
    }
  })
  return new Proxy(obj,{
    get(target,key){
      console.log('get')
      return target[key]
    },
    set(target,key,val){
      console.log('set')
      return target[key] = val
    }
  })
}

 data = defineReactive(data)

就這麼一點程式碼就實現了對物件和陣列的攔截(說明:這裡不是vue3的實現方法,vue3怎麼實現的,我還不知道,還沒看過它的原始碼,有興趣自己去看一下,然後順便告訴我一下怎麼實現的,哈哈哈),Object.defineProperty實現不了的,它能實現;Object.defineProperty實現的了,它也能實現。無論你調push,pop等方法它能攔截,你調data.n[1] = xxx也能攔截,簡直不要太爽

最後

這裡只是通過對物件和陣列的攔截,來體驗了一下proxy的威力;proxy能做的遠遠不止這樣。