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

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


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

一、Object.defineProperty實現觀察者機制
二、Object.defineProperty的缺點
三、利用proxy實現簡易的實現觀察者機制
四、總結

1、Object.defineProperty實現觀察者機制

這裏咱們照顧一下小白同窗,首先咱們來補充一下ES5中對Object.defineProperty() 方法的定義和一些基礎知識,而後手寫一個簡易的響應式,最後再結合vue源碼簡析。
vue

1.1 Object.defineProperty基礎知識

developer.mozilla.org對Object.defineProperty()定義react

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

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

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

1.2 建立對象

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

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

let vm = {
        name: '掘金'
      }
      console.log(vm)複製代碼


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

//    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 這樣的對象,就變得可控被觀察,也就是咱們說的被劫持,當咱們改變或者獲取這對象的屬性的時候,咱們就能夠控制到它。ide

下面咱們經過改變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裏功能的原理,或許對每一個前端人都會有一些提高。

相關文章
相關標籤/搜索