[Vue響應(yīng)式原理]Object.defineProperty實現(xiàn)觀察者機制的探索

下面這段話:Vue3.0版本中將基于Proxy來改造觀察者模式。說明Vue3.0講不再借助于ES5的Object.defineProperty,轉(zhuǎn)而使用最新的Proxy語法實現(xiàn)Vue最根本的響應(yīng)式原理(注又名:數(shù)據(jù)劫持,下文統(tǒng)稱響應(yīng)式原理)。
下文主要簡述從Object.defineProperty到proxy的實現(xiàn)觀察者機制探索,目前關(guān)于深入響應(yīng)式原理的文章已經(jīng)很多了,很多都寫的很好,本文不做過深的vue里的源碼解析,只是淺入探索和自己動手手寫一個簡易的Object.defineProperty實現(xiàn)觀察者機制,以及手寫一個簡易的由Proxy實現(xiàn)觀察者機制,當(dāng)然最終以作者發(fā)布為準(zhǔn)。主要有以下幾個知識點帶大家一起進(jìn)入

1、Object.defineProperty實現(xiàn)觀察者機制
2、Object.defineProperty的缺點
3、利用proxy實現(xiàn)簡易的實現(xiàn)觀察者機制
4、總結(jié)
一、Object.defineProperty實現(xiàn)觀察者機制
這里我們照顧一下小白同學(xué),首先我們來補充一下ES5中對Object.defineProperty() 方法的定義和一些基礎(chǔ)知識,然后手寫一個簡易的響應(yīng)式,最后再結(jié)合vue源碼簡析

1.1 Object.defineProperty基礎(chǔ)知識
在developer.mozilla.org對Object.defineProperty()定義

Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性, 并返回這個對象
語法:
Object.defineProperty(obj, prop, descriptor)
參數(shù)說明:
1、obj要在其上定義屬性的對象
2、要定義或修改的屬性的名稱
3、將被定義或修改的屬性描述符
返回值:
被傳遞給函數(shù)的對象

這里我們重點關(guān)注一下語法中的第三個參數(shù)屬性描述符:descriptor

對象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。數(shù)據(jù)描述符是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數(shù)對描述的屬性。描述符必須是這兩種形式之一;不能同時是兩者。這里descriptor有6個選鍵值configurable、enumerable、value、writableget、set這里他們分別的介紹可以移步

1.2 創(chuàng)建對象
通常我們創(chuàng)建對象來一步一步了解這個Object.defineProperty() 方法和屬性描述符descriptor里面鍵值的用法

【1】正常我們創(chuàng)建一個對象,如下,然后控制臺打印他們我們可以看到

let vm = {
   name: '簡書'
 }
 console.log(vm)
image.png

【2】接下來我們通過Object.defineProperty創(chuàng)建一個對象,并設(shè)置這個對象要定義或者修改的屬性“name”

let vm = Object.defineProperty({},"name",{
  get() {
    console.log("執(zhí)行g(shù)et");
    return name || "簡書"
  },
  set(newVal) {
    console.log("執(zhí)行set");
    console.log("新值:" + newVal);
    if (newVal !== name) {
      name = newVal;
    }
  }
})

其實這兩種都創(chuàng)建一個對象的方式,通過Object.defineProperty創(chuàng)建的對象,我們可以看到,多了上面說的兩個存取描述符鍵值方法get 和set 這樣的對象,就變得可控被觀察,也就是我們說的被劫持,當(dāng)我們改變或者獲取這對象的屬性的時候,我們就可以控制到它

下面我們通過改變vm.name = "1",我們通過控制臺可以看到
image.png

1.3 實現(xiàn)觀察者機制,響應(yīng)式對象

let vm = {
  id:"jianshu",
  name:"簡書"
};
let keys = Object.keys(vm);
keys.forEach(key=>{
  let value = vm[key];
  Object.defineProperty(vm, key,{
    get() {
      console.log("執(zhí)行g(shù)et");
      return value
   },
   set(newVal){
      console.log("執(zhí)行set");
      if(newVal !== value){
         value =  newVal;
       }
     }
  })
})
vm.id = "test";
console.log(vm)
image.png

這里主要是遍歷對象中的每一個屬性,每個屬性都是賦予get和set,讓對象中的每一個屬性的改變都會被監(jiān)測到,也就是實現(xiàn)了響應(yīng)式

1.4 vue源碼中的響應(yīng)式原理簡析
上面的例子我們試一下,用數(shù)組對象發(fā)現(xiàn)是不能生效的,那么在vue里數(shù)組是怎么實現(xiàn)響應(yīng)式原理的呢,我們可以看到vue源碼目錄src/core/observer/index.js里,其實他是對對象進(jìn)行了判斷,如果是數(shù)組對象,就會走observeArray()方法,而且你會發(fā)現(xiàn)里面還有一個arrayMethods,里面是對數(shù)組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進(jìn)行了重寫,重寫過后的方法不僅能實現(xiàn)原有的功能,還能發(fā)布消息給訂閱者。其他對象,都走walk()方法。
1.5 把數(shù)據(jù)渲染到頁面上
當(dāng)我們檢測到對象更新了,如何同步更新到頁面上呢?
1、首先,我們要找到作用域范圍內(nèi)(vue,里會有個 el:"#app")的節(jié)點,全部頁面內(nèi)容都會渲染到這個結(jié)點里面
2、然后遍歷結(jié)點上所有含有使用該對象的地方,也就是Mustache語法 (雙大括號) 的文本插值的地方,例如 {{ vm }}
3、綁定視圖更新
二、Object.defineProperty的缺點
2.1 無法監(jiān)聽對象非已有的屬性的添加和已有屬性的刪除
只會對對象原有的全部屬性進(jìn)行做數(shù)據(jù)劫持,也就是說Vue 不允許動態(tài)添加或者刪除對象已有屬性,它是不做數(shù)據(jù)劫持的,也就不能實現(xiàn)響應(yīng)式

舉例

<template>
<div>

<h1>{{ vm }}</h1>
<button @click="addAttribute">新增屬性</button>
<button @click="delAttribute">刪除屬性</button>
</div>
</template>

<script>
export default {
data() {

return { 
  vm:{
    id:"juejin",
    name:"掘金"
  }
}
},
methods: {

addAttribute() {
  this.vm.use = "codercao"
  console.log(this.vm)
},
delAttribute() {
  for(let k in this.vm) {
   if(k=='id'){
     delete this.vm[k]
   }
  }
  console.log(this.vm)
}
},
}
</script>

新增,你會發(fā)現(xiàn)控制臺打印的vm已經(jīng)新增了use屬性,而頁面并沒有響應(yīng)式改變
刪除,你會發(fā)現(xiàn)控制臺打印的vm已經(jīng)刪除了id屬性,而頁面并沒有響應(yīng)式改變
2.2 數(shù)組變異
數(shù)組對象也不能通過屬性或者索引控制數(shù)組,比如length,index實現(xiàn)響應(yīng)式,通過1.4 里我們也看到vue源碼只是對數(shù)組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進(jìn)行了重寫,但是索引控制數(shù)組是沒有辦法實現(xiàn)響應(yīng)式的

2.3 解決以上的辦法。 使用Vue.set(object, propertyName, value) 方法
改進(jìn)上面2.1里的新增屬性方式,你會發(fā)現(xiàn)頁面就實現(xiàn)了響應(yīng)式,至于Vue.set方法介紹移步

addAttribute() {
 //this.vm.use = "codercao"
 this.$set(this.vm,'use','codercao')
  console.log(this.vm)
},

三、利用proxy實現(xiàn)簡易的實現(xiàn)觀察者機制
3.1proxy基礎(chǔ)知識
Proxy 對象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)
語法
let p = new Proxy(target, handler);
參數(shù)說明
target用Proxy包裝的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)
handler一個對象,其屬性是當(dāng)執(zhí)行一個操作時定義代理的行為的函數(shù)

let vm = {

  id:"jianshu",
  name:"簡書"
}
let newVm = new Proxy(vm,{
  get(target,key){
    console.log("執(zhí)行g(shù)et");
    return target[key];
   },
  set(target,key,newVal){
    console.log("執(zhí)行set");
    if(target[key]!==newVal)
    target[key] = newVal;
  }
})
newVm.use = "codercao"
console.log(newVm)

你會發(fā)現(xiàn)用Proxy 也一樣實現(xiàn)了一個簡易的觀察者機制,當(dāng)然深入研究的話,你還可以實現(xiàn)雙向綁定。

四、結(jié)尾
到這里我們這篇文章到此就結(jié)束了,至于最終作者會怎么用proxy來寫這個觀察者機制,待vue3.0發(fā)布可以一看究竟,文章主要是帶大家實踐探索一下Object.defineProperty實現(xiàn)觀察者機制,順便提了一下,這個Object.defineProperty缺陷和處理辦法,然后引入proxy,屬于比較初級的嘗試,Vue發(fā)展到現(xiàn)在幾年了,其實大部分人對其應(yīng)用已經(jīng)游刃有余了,關(guān)注源碼和實踐vue里功能的原理,或許對每個前端人都會有一些提升。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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