天天看點

vue 存儲對象 不要監聽_Vue資料響應式

一、setter和getter

var o = {
  property: function ([parameters]) {},
  get property() {},
  set property(value) {},
};
           
let obj3 = {
  姓: "高",
  名: "圓圓",
  get 姓名() {
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18
};

obj3.姓名 = '高媛媛'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)

// 總結:setter 就是這樣用的。用 = xxx 觸發 set 函數
           
二、Object.defineProperty()方法
var o = {}; // 建立一個新對象

// 在對象中添加一個屬性與資料描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
});
// 對象 o 擁有了屬性 a,值為 37
           

思考new Vue 對資料做了什麼

let data0 = {
  n: 0
}

// 需求一:用 Object.defineProperty 定義 n
let data1 = {}

Object.defineProperty(data1, 'n', {
  value: 0
})

console.log(`需求一:${data1.n}`)

// 總結:這煞筆文法把事情搞複雜了?非也,繼續看。

// 需求二:n 不能小于 0
// 即 data2.n = -1 應該無效,但 data2.n = 1 有效

let data2 = {}

data2._n = 0 // _n 用來偷偷存儲 n 的值

Object.defineProperty(data2, 'n', {
  get(){
    return this._n
  },
  set(value){
    if(value < 0) return
    this._n = value
  }
})

console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 設定為 -1 失敗`)
data2.n = 1
console.log(`需求二:${data2.n} 設定為 1 成功`)

// 擡杠:那如果對方直接使用 data2._n 呢?
// 算你狠

// 需求三:使用代理

let data3 = proxy({ data:{n:0} }) // 括号裡是匿名對象,無法通路

function proxy({data}/* 解構指派,别TM老問 */){
  const obj = {}
  // 這裡的 'n' 寫死了,理論上應該周遊 data 的所有 key,這裡做了簡化
  // 因為我怕你們看不懂
  Object.defineProperty(obj, 'n', { 
    get(){
      return data.n
    },
    set(value){
      if(value<0)return
      data.n = value
    }
  })
  return obj // obj 就是代理
}

// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n},設定為 -1 失敗`)
data3.n = 1
console.log(`需求三:${data3.n},設定為 1 成功`)

// 杠精你還有話說嗎?
// 杠精說有!你看下面代碼
// 需求四

let myData = {n:0}
let data4 = proxy({ data:myData }) // 括号裡是匿名對象,無法通路

// data3 就是 obj
console.log(`杠精:${data4.n}`)
myData.n = -1
console.log(`杠精:${data4.n},設定為 -1 失敗了嗎!?`)

// 我現在改 myData,是不是還能改?!你奈我何
// 艹,算你狠





// 需求五:就算使用者擅自修改 myData,也要攔截他

let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) // 括号裡是匿名對象,無法通路

function proxy2({data}/* 解構指派 */){
  // 這裡的 'n' 寫死了,理論上應該周遊 data 的所有 key,這裡做了簡化
  // 因為我怕你們看不懂
  let value = data.n//儲存原有n的值
  Object.defineProperty(data, 'n', {//相當于重新設定了一個n
    get(){
      return value
    },
    set(newValue){
      if(newValue<0)return
      value = newValue
    }
  })
  // 就加了上面幾句,這幾句話會監聽 data

  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      if(value<0)return//這句話多餘了
      data.n = value
    }
  })
  
  return obj // obj 就是代理
}

// data3 就是 obj
console.log(`需求五:${data5.n}`)
myData5.n = -1
console.log(`需求五:${data5.n},設定為 -1 失敗了`)
myData5.n = 1
console.log(`需求五:${data5.n},設定為 1 成功了`)


// 這代碼看着眼熟嗎?
// let data5 = proxy2({ data:myData5 }) 
// let vm = new Vue({data: myData})

// 現在我們可以說說 new Vue 做了什麼了
           
下面是對上面代碼的總結(重點)

Object.defineProperty的作用:

  • 可以給對象添加屬性value
  • 可以給對象添加getter/setter, getter/setter用來對屬性的讀寫進行監控
代理模式

代理的設計模式,意思為vm執行個體負責對myData對象屬性的讀寫。

即vm就是myData的代理,類似于房屋中介,住戶通過中介和房東交流。

例如通常vm.n來操作myData.n,而不是直接操作myData.n

原理
vue 存儲對象 不要監聽_Vue資料響應式
const vm = new Vue({data: myData}) 作用:
  • 核心目的:對于data的任何變動,都得知情,進而重新進行渲染
  • 會讓vm成為myData的代理,即proxy
  • 同時會對myData的所有屬性進行監控
  • 監控的原因是,為了防止myData屬性改變的時候,vm不知情。如果通知vm了,就可以進行操作,然後調用render(data)方法,渲染視圖,即UI=render(data)
  • 全程對data進行修改,并沒有複制和生成新的data,隻有覆寫修改
  • 如果data有多個屬性,n,m,k,那麼就會有get n/get m /get k等
三、資料響應式

資料響應式是指當資料改變時,UI或者視圖能夠做出相應的反應。比如我用力打你一拳,你會喊痛這就是響應式。

1、vue的data屬于響應式

在const vm = new Vue({data:{n:0}})中,如果我修改vm.n,那麼UI中的n就會響應我

2、Vue的資料響應式

Vue響應式系統即對資料進行修改時,視圖會進行更新;當把JS對象傳入Vue執行個體作為 data 選項,Vue将周遊此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter,getter/setter 對使用者不可見,但是在内部它們讓 Vue 能夠追蹤依賴,在屬性被通路和修改時通知變更。

3、響應式網頁

響應式網頁是指當使用者改變視窗大小,網頁内容會做出響應

四、Object.defineProperty有問題或者說Object.defineProperty()實作資料響應的缺點

Object.defineProperty在添加屬性時必須要屬性名,才能監聽&代理

Object.defineProperty(obj,'n',{...})

必須要有個‘n’才能監聽&代理obj.n

如果隊友沒給你n怎麼辦
           
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    obj: {
      a: 0 // obj.a 會被 Vue 監聽 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      this.obj.b = 1; //請問,頁面中會顯示 1 嗎?
    }
  }
}).$mount("#app");

//請問,頁面中會顯示 1 嗎?
//答案是不會,因為vue沒法監聽一開始就不存在的obj.b
//它隻會檢查obj在不在,是以obj.b不存在,他也不會報錯
下條代碼出解決辦法
           

總結:

  • 檢測不到對象屬性的添加和删除:當你在對象上新加了一個屬性newProperty,目前新加的這個屬性并沒有加入vue檢測資料更新的機制(因為是在初始化之後添加的)。
  • vue.$set是能讓vue知道你添加了屬性, 它會給你做處理,$set内部也是通過調用Object.defineProperty()去處理的 無法監控到數組下标的變化,導緻直接通過數組的下标給數組設定值,不能實時響應。
  • 當data中資料比較多且層級很深的時候,會有性能問題,因為要周遊data中所有的資料并給其設定成響應式的。

解決辦法

1、把key都聲明好,後面不在加屬性(弱智的寫法)

2、使用vue.set和this.$set

vue.set和this.$set的作用

1、新增key

2、自動建立代理和監聽(在沒有的情況下)

3、觸發UI更新(不會立刻更新)

methods:{
    add({
        vue.set(this.obj, 'b', 1)
        // 或者
        this.$set(this.obj, 'b', 1)
    }
}
           
五、數組新增加key(也用set方法)

由于數組的長度可以一直增加,沒有辦法提前把數組的key都聲明出來,Vue也不能直接檢測新增的下标。可以使用set來新增key, 但不會自動添加監聽和代理, 但是尤雨溪纂改了數組的7個API來對數組進行操作,這7個API會自動處理監聽和代理,并更新UI。

推薦使用這7個API來新增數組key:(自行官網檢視)

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

篡改原理

class VueArray extends Array {
  push(...args) {
    const oldLength = this.length; // this
    super.push(...args);
    console.log(" push ");
    for (let i = oldLength; i < this.length; i++) {
      Vue.set(this, i, this[i]);
      // key Vue
    }
  }
}
           
六、vue3中的Proxy

什麼是代理呢,可以了解為在對象之前設定一個“攔截”,當該對象被通路的時候,都必須經過這層攔截。意味着你可以在這層攔截中進行各種操作。比如你可以在這層攔截中對原對象進行處理,傳回你想傳回的資料結構。

ES6 原生提供 Proxy 構造函數,MDN上的解釋為:Proxy 對象用于定義基本操作的自定義行為(如屬性查找,指派,枚舉,函數調用等)。

const p = new Proxy(target, handler);
           
  • target

    : 所要攔截的目标對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
  • handler

    :一個對象{},定義要攔截的行為,不可以寫null
const p = new Proxy({}, {
    get(target, propKey) {
        return '哈哈,你被我攔截了';
    }
});

console.log(p.name);
// 哈哈,你被我攔截了
           

注意Proxy是用來操作對象的。代理的目的是為了拓展對象的能力。

再看一個例子 我們可以實作一個功能:不允許外部修改對象的name屬性。

const p = new Proxy({}, {
    set(target, propKey, value) {
        if (propKey === 'name') {
            throw new TypeError('name屬性不允許修改');
        }
        // 不是 name 屬性,直接儲存
        target[propKey] = value;
    }
});
p.name = 'proxy';
// TypeError: name屬性不允許修改
p.a = 111;
console.log(p.a); // 111
           

總結:

  • Proxy是用來操作對象的,Object.defineProperty() 是用來操作對象的屬性的。
  • vue2.x使用 Object.defineProperty()實作資料的響應式,但是由于 Object.defineProperty()是對對象屬性的操作,是以需要對對象進行深度周遊去對屬性進行操作。
  • vue3.0 用 Proxy 是對對象進行攔截操作,無論是對對象做什麼樣的操作都會走到 Proxy 的處理邏輯中