最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Vue逐步實(shí)現(xiàn)Watch屬性詳解

 更新時(shí)間:2022年08月15日 11:13:52   作者:夏日  
這篇文章主要介紹了使用Vue逐步實(shí)現(xiàn)Watch屬性詳解,watch對(duì)象中的value分別支持函數(shù)、數(shù)組、字符串、對(duì)象,較為常用的是函數(shù)的方式,當(dāng)想要觀察一個(gè)對(duì)象以及對(duì)象中的每一個(gè)屬性的變化時(shí),便會(huì)用到對(duì)象的方式

watch

對(duì)于watch的用法,在Vue文檔 中有詳細(xì)描述,它可以讓我們觀察data中屬性的變化。并提供了一個(gè)回調(diào)函數(shù),可以讓用戶在屬性值變化后做一些事情。

watch對(duì)象中的value分別支持函數(shù)、數(shù)組、字符串、對(duì)象,較為常用的是函數(shù)的方式,當(dāng)想要觀察一個(gè)對(duì)象以及對(duì)象中的每一個(gè)屬性的變化時(shí),便會(huì)用到對(duì)象的方式。

下面是官方的一個(gè)例子,相信在看完之后就能對(duì)watch幾種用法有大概的了解:

var vm = new Vue({
  data: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // string method name
    b: 'someMethod',
    // the callback will be called whenever any of the watched object properties change regardless of their nested depth
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // the callback will be called immediately after the start of the observation
    d: {
      handler: 'someMethod',
      immediate: true
    },
    // you can pass array of callbacks, they will be called one-by-one
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }
})
vm.a = 2 // => new: 2, old: 1

初始化watch

在了解了watch的用法之后,我們開始實(shí)現(xiàn)watch

在初始化狀態(tài)initState時(shí),會(huì)判斷用戶在實(shí)例化Vue時(shí)是否傳入了watch選項(xiàng),如果用戶傳入了watch,就會(huì)進(jìn)行watch的初始化操作:

// src/state.js
function initState (vm) {
  const options = vm.$options;
  if (options.watch) {
    initWatch(vm);
  }
}

initWatch中本質(zhì)上是為每一個(gè)watch中的屬性對(duì)應(yīng)的回調(diào)函數(shù)都創(chuàng)建了一個(gè)watcher

// src/state.js
function initWatch (vm) {
  const { watch } = vm.$options;
  for (const key in watch) {
    if (watch.hasOwnProperty(key)) {
      const userDefine = watch[key];
      if (Array.isArray(userDefine)) { // userDefine是數(shù)組,為數(shù)組中的每一項(xiàng)分別創(chuàng)建一個(gè)watcher
        userDefine.forEach(item => {
          createWatcher(vm, key, item);
        });
      } else {
        createWatcher(vm, key, userDefine);
      }
    }
  }
}

createWatcher中得到的userDefine可能是函數(shù)、對(duì)象或者字符串,需要分別進(jìn)行處理:

function createWatcher (vm, key, userDefine) {
  let handler;
  if (typeof userDefine === 'string') { // 字符串,從實(shí)例上取到對(duì)應(yīng)的method
    handler = vm[userDefine];
    userDefine = {};
  } else if (typeof userDefine === 'function') { // 函數(shù)
    handler = userDefine;
    userDefine = {};
  } else { // 對(duì)象,userDefine中可能會(huì)包含用戶傳入的deep,immediate屬性
    handler = userDefine.handler;
    delete userDefine.handler;
  }
  // 用處理好的參數(shù)調(diào)用vm.$watch
  vm.$watch(key, handler, userDefine);
}

createWatcher中對(duì)參數(shù)進(jìn)行統(tǒng)一處理,之后調(diào)用了vm.$watch,在vm.$watch中執(zhí)行了Watcher的實(shí)例化操作:

export function stateMixin (Vue) {
  // some code ...
  Vue.prototype.$watch = function (exprOrFn, cb, options) {
    const vm = this;
    const watch = new Watcher(vm, exprOrFn, cb, { ...options, user: true });
  };
}

此時(shí)new Watcher時(shí)傳入的參數(shù)如下:

  • vm: 組件實(shí)例
  • exprOrFnwatch選項(xiàng)對(duì)應(yīng)的key
  • cbwatch選項(xiàng)中key對(duì)應(yīng)的value中提供給用戶處理邏輯的回調(diào)函數(shù),接收keydata中的對(duì)應(yīng)屬性的舊值和新值作為參數(shù)
  • options{user: true, immediate: true, deep: true}immediatedeep屬性當(dāng)key對(duì)應(yīng)的value為對(duì)象時(shí),用戶可能會(huì)傳入

Watcher中會(huì)判斷options中有沒有user屬性來區(qū)分是否是watch屬性對(duì)應(yīng)的watcher:

class Watcher {
  constructor (vm, exprOrFn, cb, options = {}) {
    this.user = options.user;
    if (typeof exprOrFn === 'function') {
      this.getter = this.exprOrFn;
    }
    if (typeof exprOrFn === 'string') { // 如果exprFn傳入的是字符串,會(huì)從實(shí)例vm上進(jìn)行取值
      this.getter = function () {
        const keys = exprOrFn.split('.');
        // 后一次拿到前一次的返回值,然后繼續(xù)進(jìn)行操作
        // 在取值時(shí),會(huì)收集當(dāng)前Dep.target對(duì)應(yīng)的`watcher`,這里對(duì)應(yīng)的是`watch`屬性對(duì)應(yīng)的`watcher`
        return keys.reduce((memo, cur) => memo[cur], vm);
      };
    }
    this.value = this.get();
  }

  get () {
    pushTarget(this);
    const value = this.getter();
    popTarget();
    return value;
  }

  // some code ...  
}

這里有倆個(gè)重要的邏輯:

  • 由于傳入的exprOrFn是字符串,所以this.getter的邏輯就是從vm實(shí)例上找到exprOrFn對(duì)應(yīng)的值并返回
  • watcher實(shí)例化時(shí),會(huì)執(zhí)行this.get,此時(shí)會(huì)通過this.getter方法進(jìn)行取值。取值就會(huì)觸發(fā)對(duì)應(yīng)屬性的get方法,收集當(dāng)前的watcher作為依賴
  • this.get的返回值賦值給this.value,此時(shí)拿到的就是舊值

當(dāng)觀察的屬性值發(fā)生變化后,會(huì)執(zhí)行其對(duì)應(yīng)的set方法,進(jìn)而執(zhí)行收集的watch對(duì)應(yīng)的watcherupdate方法:

class Watcher {

  // some code ...
  update () {
    queueWatcher(this);
  }

  run () {
    const value = this.get();
    if (this.user) {
      this.cb.call(this.vm, value, this.value);
      this.value = value;
    }
  }
}

和渲染watcher相同,update方法中會(huì)將對(duì)應(yīng)的watch watcher去重后放到異步隊(duì)列中執(zhí)行,所以當(dāng)用戶多次修改watch屬性觀察的值時(shí),并不會(huì)不停的觸發(fā)對(duì)應(yīng)watcher 的更新操作,而只是以它最后一次更新的值作為最終值來執(zhí)行this.get進(jìn)行取值操作。

當(dāng)我們拿到觀察屬性的最新值之后,執(zhí)行watcher中傳入的回調(diào)函數(shù),傳入新值和舊值。

下面畫圖來梳理下這個(gè)過程:

deep、immdediate屬性

當(dāng)用戶傳入immediate屬性后,會(huì)在watch初始化時(shí)便立即執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。其具體的執(zhí)行位置是在Watcher實(shí)例化之后:

Vue.prototype.$watch = function (exprOrFn, cb, options) {
  const vm = this;
  const watcher = new Watcher(vm, exprOrFn, cb, { ...options, user: true });
  if (options.immediate) { // 在初始化后立即執(zhí)行watch
    cb.call(vm, watcher.value);
  }
};

此時(shí)watcher.value是被觀察的屬性當(dāng)前的值,由于此時(shí)屬性還沒有更新,所以老值為undefined。

如果watch觀察的屬性為對(duì)象,那么默認(rèn)對(duì)象內(nèi)的屬性更新,并不會(huì)觸發(fā)對(duì)應(yīng)的回調(diào)函數(shù)。此時(shí),用戶可以傳入deep選項(xiàng),來讓對(duì)象內(nèi)部屬性更新也調(diào)用對(duì)應(yīng)的回調(diào)函數(shù):

class Watcher {
  // some code ...
  get () {
    pushTarget(this);
    const value = this.getter();
    if (this.deep) { // 繼續(xù)遍歷value中的每一項(xiàng),觸發(fā)它的get方法,收集當(dāng)前的watcher
      traverse(value);
    }
    popTarget();
    return value;
  }
}

當(dāng)用戶傳入deep屬性后,get方法中會(huì)執(zhí)行traverse方法來遍歷value中的每一個(gè)值,這樣便可以繼續(xù)觸發(fā)value中屬性對(duì)應(yīng)的get方法,為其收集當(dāng)前的watcher作為依賴。這樣在value 內(nèi)部屬性更新時(shí),也會(huì)通知其收集的watch watcher進(jìn)行更新操作。

traverse的邏輯只是遞歸遍歷傳入數(shù)據(jù)的每一個(gè)屬性,當(dāng)遇到簡(jiǎn)單數(shù)據(jù)類型時(shí)便停止遞歸:

// traverse.js
// 創(chuàng)建一個(gè)Set,遍歷之后就會(huì)將其放入,當(dāng)遇到環(huán)引用的時(shí)候不會(huì)行成死循環(huán)
const seenObjects = new Set();

export function traverse (value) {
  _traverse(value, seenObjects);
  // 遍歷完成后,清空Set
  seenObjects.clear();
}

function _traverse (value, seen) {
  const isArr = Array.isArray(value);
  const ob = value.__ob__;
  // 不是對(duì)象并且沒有被觀測(cè)過的話,終止調(diào)用
  if (!isObject(value) || !ob) {
    return;
  }
  if (ob) {
    // 每個(gè)屬性只會(huì)有一個(gè)在Observer中定義的dep
    const id = ob.dep.id;
    if (seen.has(id)) { // 遍歷過的對(duì)象和數(shù)組不再遍歷,防止環(huán)結(jié)構(gòu)造成死循環(huán)
      return;
    }
    seen.add(id);
  }
  if (isArr) {
    value.forEach(item => {
      // 繼續(xù)遍歷數(shù)組中的每一項(xiàng),如果為對(duì)象的話,會(huì)繼續(xù)遍歷數(shù)組的每一個(gè)屬性,即對(duì)對(duì)象屬性執(zhí)行取值操作,收集watch watcher
      _traverse(item, seen);
    });
  } else {
    const keys = Object.keys(value);
    for (let i = 0; i < keys.length; i++) {
      // 繼續(xù)執(zhí)行_traverse,這里會(huì)對(duì) 對(duì)象 中的屬性進(jìn)行取值
      _traverse(value[keys[i]], seen);
    }
  }
}

需要注意的是,這里利用Set來存儲(chǔ)每個(gè)屬性對(duì)應(yīng)的depid。這樣當(dāng)出現(xiàn)環(huán)時(shí),Set中已經(jīng)存儲(chǔ)過了其對(duì)應(yīng)depid,便會(huì)終止遞歸。

結(jié)語

本文一步步實(shí)現(xiàn)了Vuewatch屬性,并對(duì)內(nèi)部的實(shí)現(xiàn)邏輯提供了筆者相應(yīng)的理解 。到此這篇關(guān)于使用Vue逐步實(shí)現(xiàn)Watch屬性詳解的文章就介紹到這了,更多相關(guān)Vue Watch屬性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決vue-cli項(xiàng)目打包出現(xiàn)空白頁和路徑錯(cuò)誤的問題

    解決vue-cli項(xiàng)目打包出現(xiàn)空白頁和路徑錯(cuò)誤的問題

    今天小編就為大家分享一篇解決vue-cli項(xiàng)目打包出現(xiàn)空白頁和路徑錯(cuò)誤的問題。具有很好的參考價(jià)值。希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • vue2安裝tailwindcss的詳細(xì)步驟

    vue2安裝tailwindcss的詳細(xì)步驟

    這篇文章主要介紹了vue2安裝tailwindcss,本文分步驟結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Vue實(shí)現(xiàn)6位數(shù)密碼效果

    Vue實(shí)現(xiàn)6位數(shù)密碼效果

    這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)6位數(shù)密碼,優(yōu)化iOS WebView卡頓,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • Vue打包后頁面出現(xiàn)空白解決辦法

    Vue打包后頁面出現(xiàn)空白解決辦法

    本文主要介紹了Vue打包后頁面出現(xiàn)空白解決辦法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • vue的表單數(shù)據(jù)收集案例之基本指令和自定義指令詳解

    vue的表單數(shù)據(jù)收集案例之基本指令和自定義指令詳解

    收集表單數(shù)據(jù)可以使用這個(gè)v-model實(shí)現(xiàn)這個(gè)數(shù)據(jù)的綁定,但是在有些輸入框中,還需要一些其他的指令搭配這個(gè)v-model指令結(jié)合使用,這篇文章主要介紹了vue的表單數(shù)據(jù)收集,基本指令和自定義指令,需要的朋友可以參考下
    2023-01-01
  • Element-UI中el-table如何合并相同單元格

    Element-UI中el-table如何合并相同單元格

    這篇文章主要給大家介紹了關(guān)于Element-UI中el-table如何合并相同單元格的相關(guān)資料,el-table的組件的可以合并單元格,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-08-08
  • vue彈窗組件使用方法

    vue彈窗組件使用方法

    彈窗是一個(gè)項(xiàng)目必備的復(fù)用利器,這篇文章主要介紹了vue彈窗組件的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • Vue自定義指令實(shí)現(xiàn)點(diǎn)擊右鍵彈出菜單示例詳解

    Vue自定義指令實(shí)現(xiàn)點(diǎn)擊右鍵彈出菜單示例詳解

    這篇文章主要為大家介紹了Vue自定義指令實(shí)現(xiàn)點(diǎn)擊右鍵彈出菜單示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • vue基于vant實(shí)現(xiàn)上拉加載下拉刷新的示例代碼

    vue基于vant實(shí)現(xiàn)上拉加載下拉刷新的示例代碼

    普遍存在于各種app中的上拉加載下拉刷新功能,本文主要介紹了vue基于vant實(shí)現(xiàn)上拉加載下拉刷新,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Vue父子之間值傳遞的實(shí)例教程

    Vue父子之間值傳遞的實(shí)例教程

    這篇文章主要給大家介紹了關(guān)于Vue父子之間值傳遞的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07

最新評(píng)論

曲沃县| 荃湾区| 荔浦县| 玛沁县| 高密市| 隆子县| 宁强县| 资中县| 洪洞县| 封开县| 会宁县| 施甸县| 普兰店市| 万载县| 依兰县| 贡山| 花垣县| 任丘市| 疏勒县| 丰顺县| 兴义市| 福贡县| 镇原县| 武宁县| 天峨县| 巴青县| 额敏县| 盐山县| 灵武市| 赞皇县| 日土县| 越西县| 茶陵县| 仙桃市| 武陟县| 沙湾县| 凯里市| 新沂市| 江津市| 河东区| 来安县|