Vue的computed和watch的細節全面分析

1.computed

1.1 定義

是一個計算屬性,相似於過濾器,對綁定到view的數據進行處理html

1.2 get用法

data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }

fullName不可在data裏面定義,
若是定義會報以下圖片的錯誤,由於對應的computed做爲計算屬性定義fullName並返回對應的結果給這個變量,變量不可被重複定義和賦值vue

圖片描述

1.3 get和set用法

data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
  fullName:{
   get(){//回調函數 當須要讀取當前屬性值是執行,根據相關數據計算並返回當前屬性的值
      return this.firstName + ' ' + this.lastName
    },
   set(val){//監視當前屬性值的變化,當屬性值發生變化時執行,更新相關的屬性數據
       //val就是fullName的最新屬性值
       console.log(val)
        const names = val.split(' ');
        console.log(names)
        this.firstName = names[0];
        this.lastName = names[1];
   }
   }
  }

2. watch

2.1 定義

watch是一個觀察的動做vuex

2.2 示例

data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
     firstName: function (val) {
     this.fullName = val + ' ' + this.lastName
  },
  lastName: function (val) {
     this.fullName = this.firstName + ' ' + val
   }
   }

上面是監聽firstName和lastName的變化,可是僅限簡單數據類型segmentfault

2.2 監聽簡單數據類型

data(){
      return{
        'first':2
      }
    },
    watch:{
      first(){
        console.log(this.first)
      }
    },

2.3 監聽複雜數據類型

1.監聽複雜數據類型需用深度監聽api

data(){
      return{
        'first':{
          second:0
        }
      }
    },
    watch:{
      secondChange:{
        handler(oldVal,newVal){
          console.log(oldVal)
          console.log(newVal)
        },
        deep:true
      }
    },

2.console.log打印的結果,發現oldVal和newVal值是同樣的,因此深度監聽雖然能夠監聽到對象的變化,可是沒法監聽到具體對象裏面那個屬性的變化數組

3.oldVal和newVal值同樣的緣由是它們索引同一個對象/數組。Vue 不會保留修改以前值的副本
vm.$watch的深度監聽緩存

圖片描述

4.深度監聽對應的函數名必須爲handler,不然無效果,由於watcher裏面對應的是對handler的調用異步

2.4 監聽對象單個屬性

方法一:能夠直接對用對象.屬性的方法拿到屬性
data(){
          return{
            'first':{
              second:0
            }
          }
        },
        watch:{
          first.second:function(newVal,oldVal){
            console.log(newVal,oldVal);
          }
        },

方法二:watch若是想要監聽對象的單個屬性的變化,必須用computed做爲中間件轉化,由於computed能夠取到對應的屬性值ide

data(){
      return{
        'first':{
          second:0
        }
      }
    },
    computed:{
      secondChange(){
        return this.first.second
      }
    },
    watch:{
      secondChange(){
        console.log('second屬性值變化了')
      }
    },

3 computed和watch的區別

3.1 computed特性

1.是計算值,
2.應用:就是簡化tempalte裏面{{}}計算和處理props或$emit的傳值
3.具備緩存性,頁面從新渲染值不變化,計算屬性會當即返回以前的計算結果,而沒必要再次執行函數函數

3.2 watch特性

1.是觀察的動做,
2.應用:監聽props,$emit或本組件的值執行異步操做
3.無緩存性,頁面從新渲染時值不變化也會執行

3 props傳值

3.1 常見錯誤1

傳入的值想做爲局部變量來使用,直接使用會

props:['listShop'],
    data(){
      return{}
    },
    created(){
      this.listShop=30
}

報錯

圖片描述

這個錯誤是說的避免直接修改父組件傳入的值,由於會改變父組件的值,貼上官網介紹

3.2 解決方案1

簡單數據類型解決方案:
因此能夠在data中從新定義一個變量,改變指向,可是也只是針對簡單數據類型,由於複雜數據類型棧存貯的是指針,

props:['listShop'],
    data(){
      return{
        listShopChild:this.listShop
      }
    },
    created(){
      this.listShopChild=30
    }

這樣就能夠愉快的更改傳入的簡單數據類型的數據啦!不會有任何報錯,也不會影響父組件!

3.4 存在的問題

複雜數據類型在棧中存貯的是指針,因此賦值給新的變量也會改變原始的變量值.那麼應該咋整呢?
1.能夠手動深度克隆一個複雜的數據出來,循環或者遞歸都行

數組深度克隆:

var x = [1,2,3];
var y = [];
for (var i = 0; i < x.length; i++) {
    y[i]=x[i];
}
console.log(y);  //[1,2,3]
y.push(4);
console.log(y);  //[1,2,3,4]
console.log(x);  //[1,2,3]

對象深度克隆:

var x = {a:1,b:2};
var y = {};
for(var i in x){
    y[i] = x[i];
}
console.log(y);  //Object {a: 1, b: 2}
y.c = 3;
console.log(y);  //Object {a: 1, b: 2, c: 3}
console.log(x);  //Object {a: 1, b: 2}

函數深度克隆

var x = function(){console.log(1);};
var y = x;
y = function(){console.log(2);};
x();  //1
y();  //2

爲何函數能夠直接賦值克隆?
因爲函數對象克隆以後的對象會單獨複製一次並存儲實際數據,所以並不會影響克隆以前的對象。因此採用簡單的複製「=」便可完成克隆。

2.Object.assign
只會對只是一級屬性複製,比淺拷貝多深拷貝了一層而已,因此仍是沒法達到深度克隆的目的.
詳請請戳

3.強大的JSON.stringify和JSON.parse

const obj1 = JSON.parse(JSON.stringify(obj));

這是ES5新出來的API,先將對象轉化爲字符串,就是簡單數據類型賦值,再用JSON.parse轉化

3.5 解決方案2

直接用computed改變

computed:{
  listShopChild(){
    return this.listShop
   }
}

3.5 存在的問題

注意:此時用computed時,若是是數組this.$set(arr,1,true)對應的值耶不更新
這個很坑,這個bug我找個好久
若是傳入的值只是在data定義,並未在methods或生命週期鉤子更改,直接改變也會報錯
因此仍是能夠先用局部變量接收,再修改,這個坑比較多

4 應用

4.1 應用1

監聽本組件計算和監聽

4.2 應用2

計算或監聽父傳子的props值

4.3 應用3

分爲簡單數據類型和複雜數據類型監聽,監聽方法如上watch的使用

4.4 應用4

監聽vuex的state或者getters值的變化

computed:{
    stateDemo(){
        return this.$store.state.demoState;
    }
}
watch:{
    stateDemo(){
        console.log('vuex變化啦')
    }
}

5. computed和watch的原理分析

很開心小夥伴們能看到這裏,接下來給你們簡單羅列下他們的原理!

5.1 computed的原理

深刻理解 Vue Computed 計算屬性

5.2 watch的原理

分爲三個過程:實例化Vue、調用$watch方法、屬性變化,觸發回調
Vue的數據依賴實現原理簡析
vue中$watch源碼閱讀筆記

6 簡單實現computed和watch

公共類

function defineReactive(data, key, val, fn) {
      let subs = [] // 新增
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        get: function() {
          // 新增
       if (data.$target) {
        subs.push(data.$target)
      }
      return val
     },
     set: function(newVal) {
      if (newVal === val) return
      fn && fn(newVal)
      // 新增
      if (subs.length) {
        // 用 setTimeout 由於此時 this.data 還沒更新
        setTimeout(() => {
          subs.forEach(sub => sub())
        }, 0)
      }
      val = newVal
    },
   })
 }

6.1 computed實現

function computed(ctx, obj) {
  let keys = Object.keys(obj)
  let dataKeys = Object.keys(ctx.data)
  dataKeys.forEach(dataKey => {
    defineReactive(ctx.data, dataKey, ctx.data[dataKey])
  })
  let firstComputedObj = keys.reduce((prev, next) => {
    ctx.data.$target = function() {
      ctx.setData({ [next]: obj[next].call(ctx) })
    }
    prev[next] = obj[next].call(ctx)
    ctx.data.$target = null
    return prev
  }, {})
  ctx.setData(firstComputedObj)
}

6.1 watch實現

function watch(ctx, obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(ctx.data, key, ctx.data[key], function(value) {
      obj[key].call(ctx, value)
    })
  })
}

https://segmentfault.com/a/11...你們若是發現有什麼錯誤,歡迎指正,共同交流。若是以爲篇文章真的對你有點做用。謝謝親們能看完!

相關文章
相關標籤/搜索