[Vue響應式原理]從Object.defineProperty到proxy實現觀察者機制的探索

不知不覺,Vue的做者尤雨溪公佈了Vue3.0版本的開發計劃,發佈到如今已經一年了,看來Vue3.0的發佈尚需時日,在開發計劃中,下圖這段話:Vue3.0版本中將基於Proxy來改造觀察者模式。說明Vue3.0講再也不借助於ES5的Object.defineProperty,轉而使用最新的Proxy語法實現Vue最根本的響應式原理(注又名:數據劫持,下文統稱響應式原理)。前端

下文主要簡述從Object.defineProperty到proxy的實現觀察者機制探索,目前關於深刻響應式原理的文章已經不少了,不少都寫的很好,本文不作過深的vue裏的源碼解析,只是淺入探索和本身動手手寫一個簡易的Object.defineProperty實現觀察者機制,以及手寫一個簡易的由Proxy實現觀察者機制,固然最終以做者發佈爲準。主要有如下幾個知識點帶你們一塊兒進入vue

一、Object.defineProperty實現觀察者機制
二、Object.defineProperty的缺點
三、利用proxy實現簡易的實現觀察者機制
四、總結
1、Object.defineProperty實現觀察者機制
這裏咱們照顧一下小白同窗,首先咱們來補充一下ES5中對Object.defineProperty() 方法的定義和一些基礎知識,而後手寫一個簡易的響應式,最後再結合vue源碼簡析數組

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

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

這裏咱們重點關注一下語法中的第三個參數屬性描述符:descriptorthis

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

1.2 建立對象
一般咱們建立對象來一步一步瞭解這個Object.defineProperty() 方法和屬性描述符descriptor裏面鍵值的用法雙向綁定

【1】正常咱們建立一個對象,以下,而後控制檯打印他們咱們能夠看到代理

let vm = {
    name: '掘金'
  }
  console.log(vm)

圖片描述
【2】接下來咱們經過Object.defineProperty建立一個對象,並設置這個對象要定義或者修改的屬性「name」code

// let vm = {
// name: '掘金'
// }

let vm = Object.defineProperty({},"name",{
  get() {
    console.log("執行get");
    return "掘金"
  },
  set(newValue) {
    console.log("執行set");
    console.log("新值:" + newValue);
  }
})

console.log(vm)

圖片描述
其實這兩種都建立一個對象的方式,經過Object.defineProperty建立的對象,咱們能夠看到,多了上面說的兩個存取描述符鍵值方法get 和set 這樣的對象,就變得可控被觀察,也就是咱們說的被劫持,當咱們改變或者獲取這對象的屬性的時候,咱們就能夠控制到它

下面咱們經過改變vm.name = "juejin",咱們經過控制檯能夠看到

// let vm = {
// name: '掘金'
// }

let vm = Object.defineProperty({},"name",{
  get() {
    console.log("執行get");
    return "掘金"
  },
  set(newValue) {
    console.log("執行set");
    console.log("新值:" + newValue);
  }
})

vm.name = "juejin";
//console.log(vm)

1.3 實現觀察者機制,響應式對象
let vm = {
id:"juejin",
name:"掘金"};
let keys = Object.keys(vm);
keys.forEach(key=>{
let value = vm[key];
Object.defineProperty(vm, key,{

get() {
    console.log("執行get");
    return value
 },
 set(newValue){
    console.log("執行set");
    if(newValue!=value){
       value =  newValue;
     }
   }
})

})
vm.id = "test";
console.log(vm)
圖片描述

這裏主要是遍歷對象中的每個屬性,每一個屬性都是賦予get和set,讓對象中的每個屬性的改變都會被監測到,也就是實現了響應式

1.4 vue源碼中的響應式原理簡析
上面的例子咱們試一下,用數組對象發現是不能生效的,那麼在vue裏數組是怎麼實現響應式原理的呢,咱們能夠看到vue源碼目錄src/core/observer/index.js裏,其實他是對對象進行了判斷,若是是數組對象,就會走observeArray()方法,並且你會發現裏面還有一個arrayMethods,裏面是對數組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進行了重寫,重寫事後的方法不只能實現原有的功能,還能發佈消息給訂閱者。其餘對象,都走walk()方法。

1.5 把數據渲染到頁面上
當咱們檢測到對象更新了,如何同步更新到頁面上呢?

一、首先,咱們要找到做用域範圍內(vue,裏會有個 el:"#app")的節點,所有頁面內容都會渲染到這個結點裏面

二、而後遍歷結點上全部含有使用該對象的地方,也就是Mustache語法 (雙大括號) 的文本插值的地方,例如 {{ vm }}

三、綁定視圖更新

2、Object.defineProperty的缺點
2.1 沒法監聽對象非已有的屬性的添加和已有屬性的刪除
只會對對象原有的所有屬性進行作數據劫持,也就是說Vue 不容許動態添加或者刪除對象已有屬性,它是不作數據劫持的,也就不能實現響應式

舉例

<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>
新增,你會發現控制檯打印的vm已經新增了use屬性,而頁面並無響應式改變
圖片描述

刪除,你會發現控制檯打印的vm已經刪除了id屬性,而頁面並無響應式改變

圖片描述

2.2 數組變異
數組對象也不能經過屬性或者索引控制數組,好比length,index實現響應式,經過1.4 裏咱們也看到vue源碼只是對數組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進行了重寫,可是索引控制數組是沒有辦法實現響應式的

2.3 解決以上的辦法。 使用Vue.set(object, propertyName, value) 方法
改進上面2.1裏的新增屬性方式,你會發現頁面就實現了響應式,至於Vue.set方法介紹移步

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

3、利用proxy實現簡易的實現觀察者機制
3.1proxy基礎知識
Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)
語法
let p = new Proxy(target, handler);
參數說明
target用Proxy包裝的目標對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理)
handler一個對象,其屬性是當執行一個操做時定義代理的行爲的函數

let vm = {

id:"juejin",
  name:"掘金"
}
let newVm = new Proxy(vm,{
  get(target,key){
    console.log("執行get");
    return target[key];
   },
  set(target,key,newValue){
    console.log("執行set");
    if(target[key]!==newValue)
    target[key] = newValue;
  }
})
newVm.use = "codercao"
console.log(newVm)

你會發現用Proxy 也同樣實現了一個簡易的觀察者機制,固然深刻研究的話,你還能夠實現雙向綁定。

4、結尾到這裏咱們這篇文章到此就結束了,至於最終做者會怎麼用proxy來寫這個觀察者機制,待vue3.0發佈能夠一看究竟,文章主要是帶你們實踐探索一下Object.defineProperty實現觀察者機制,順便提了一下,這個Object.defineProperty缺陷和處理辦法,而後引入proxy,屬於比較初級的嘗試,Vue發展到如今幾年了,其實大部分人對其應用已經遊刃有餘了,關注源碼和實踐vue裏功能的原理,或許對每一個前端人都會有一些提高。

相關文章
相關標籤/搜索