vue2.x版本中Object.defineProperty物件屬性監聽和關聯

程式猿布歐發表於2022-04-27

前言

在vue2.x版本官方文件中
深入響應式原理 https://cn.vuejs.org/v2/guide/reactivity.html一文的解釋當中,Object.defineProperty將宣告響應式 property資料的狀態轉換為getter和setter。

Object.defineProperty基本使用和概念

官方解釋的概念是

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Object.defineProperty() 方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,並返回此物件。

Object.defineProperty(obj, prop, descriptor)

obj: 要在其上定義屬性的物件
prop: 要定義或修改的屬性的名稱或Symbol
descriptor: 定義或修改的屬性的描述符

* @descriptor
*
* configurable	為 true 時,屬性才能重新被定義,預設為 false。
* enumerable	為 true 時,該屬性才能夠出現在物件的列舉屬性中,此時才能通過for in遍歷屬性。預設為 false。
* writable	為 true 時,value屬性值才能被修改。預設為 false,此時value屬性值為只讀狀態。
* value	該屬性對應的初始值。可以是任何有效的 JavaScript 值(數值,物件,函式等)。預設為 undefined。
* get	一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。當訪問該屬性時,該方法會被執行。預設為 undefined。
* set	一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。當屬性值修改時,觸發執行該方法。該方法將接受唯一引數,即該屬性新的引數值。預設為 undefined。
* 注意事項:get 和 set 它們與value和writable是互斥的。一旦使用它們,則這個屬性就沒有儲存屬性值的能力

Object.defineProperty新增物件屬性

let userInfo = {
  age: "11",
};
console.log("初始userInfo:", userInfo);
// Object.defineProperty新增物件屬性
Object.defineProperty(userInfo, "name", {
  value: "zhangsan",
  enumerable: true, //將enumerable設為true 才能夠出現在物件的列舉屬性
});
console.log("設定name後的userInfo:", userInfo);
console.log("設定name後的userInfo屬性:", Object.keys(userInfo));

當enumerable設定為false的時候,我們通過Object.keys列舉屬性,並不能獲取到name屬性

使用Object.defineProperty修改和監聽屬性值的變化

let userInfo = {
  age: "11",
};

let initAge = null;
Object.defineProperty(userInfo, "age", {
  enumerable: true,
  configurable: true,
  get: function () {
    console.log("get屬性方法,當前物件:", this);
    return initAge;
  },
  set: function (newValue) {
    console.log("set屬性方法", newValue);
    initAge = newValue;
  },
});
userInfo.age = "30";
console.log("最後userInfo", userInfo);

通過get和set操作物件屬性類似於我們在vue開發過程中的計算屬性computed詳解,通過get和set對當前物件進行設定屬性值

Object.defineProperty小結

  • 由上面的的例子我們可以知道,當我們使用Object.defineProperty操作物件的時候,都是直接通過物件屬性進行操作,而不是對整個物件進行修改,刪除和新增,查詢屬性
  • 在使用Object.defineProperty方法操作的時候,一些預設的屬性選項,需要我們注意

Object.defineProperty和vue2.x的聯絡

回到開頭提到的官方文件中描述的,響應式原理中,如下圖

image

圖片源自vue官方文件深入響應式原理: https://cn.vuejs.org/v2/guide/reactivity.html

當元件渲染時,通過物件劫持,遍歷data狀態,那麼需要考慮的點是:

假如在元件執行時,我們需要額外新增狀態的時候,新增一個新的狀態,或者在原有的狀態下,新增一個新的屬性會發生什麼,這個新的狀態並不在元件劫持的狀態之內:

<p>
  <span style="cursor: pointer; color: red" @click="handleUser"
    >點選修改不在data狀態下的值</span
  >
</p>
<p>使用者資訊: name {{ defaultUser.name }}, age: {{ defaultUser.age }}</p>



data() {
    return {
      defaultUser: {
        name: "張三",
      },
    };
  },
  
methods: {
    handleUser() {
      this.defaultUser.age = "23";
      console.log("資料已經發生更改,但是檢視沒有發生更新", this.defaultUser);
    },
  },
  

當我們點選按鈕的時候,檢視中的ui並沒有發生更改,根據官方的文件,我們可以使用 $set 針對新增的狀態進行修改

<p>
      <span style="cursor: pointer; color: red" @click="handleSet"
        >點選$set修改屬性值</span
      >
    </p>
<p>通過$set修改的值:{{ setData.name }}</p>


data() {
    return {
      setData: {},
    };
  },

methods: {
    handleSet() {
      this.$set(this.setData, "name", "張三");
    },
  },

除了後續新增的狀態無法進行修改之外,使用Object.defineProperty劫持的資料,對陣列本身可以操作的到,但是會存在一定的效能問題,具體不做詳細解釋,可以參考以下博文,感謝該博主的博文,vue的框架的作者尤大也做了解釋:

記一次思否問答的問題思考:Vue為什麼不能檢測陣列變動 https://segmentfault.com/a/1190000015783546

結語

根據以上例子結合vue響應式原理,我們可以知道在vue2.x版本中Object.defineProperty存在以下問題:

1.監聽陣列變化下存在效能問題
2.Object.defineProperty只能劫持物件的屬性,並且針對新增的data狀態,無法劫持到,只能通過vue的擴充套件方法$set進行處理

擴充套件vue3使用的proxy

阮一峰-ECMAScript 6 入門
檢視最簡單的例子和使用方法

下文擷取自:阮一峰-ECMAScript 6 入門----> Proxy 可以理解成,在目標物件之前架設一層“攔截”,外界對該物件的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裡表示由它來“代理”某些操作,可以譯為“代理器”

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

Object.defineProperty對比Proxy個人理解

  • Proxy是針對整個物件的變化進行檢測和攔截,可以知道物件的屬性是新增,刪除,還是修改等,都可以通過通過get和set進行監聽得到
  • Object.defineProperty只針對物件得屬性進行操作,結合vue2.x,元件渲染完成,後續新增得屬性,沒辦法劫持到
  • Object.defineProperty針對屬性,Proxy針對整個物件得操作

更多Object.defineProperty和Proxy的對比,以及vue3如果使用proxy實現物件的監聽,感興趣的同學可以去搜尋相關博文,後續有時間再整理。

原始碼地址

文章個人部落格地址:vue2.x版本中Object.defineProperty物件屬性監聽和關聯

歡迎關注公眾號:程式猿布歐,不定期更新一些前端入門文章

創作不易,轉載請註明出處和作者。

相關文章