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

Vue watch原理源碼層深入講解

 更新時間:2022年10月21日 15:23:05   作者:Young soul2  
watch 是由用戶定義的數(shù)據(jù)監(jiān)聽,當監(jiān)聽的屬性發(fā)生改變就會觸發(fā)回調(diào),這項配置在業(yè)務(wù)中是很常用。在面試時,也是必問知識點,一般會用作和 computed 進行比較。那么本文就來帶大家從源碼理解 watch 的工作流程,以及依賴收集和深度監(jiān)聽的實現(xiàn)

由于我在從源碼看vue(v2.7.10)的computed的實現(xiàn)原理中詳細的講解過computed的實現(xiàn),本篇跟computed的原理類似。我就帶大家簡單分析一下。

添加依賴

代碼如下:

<template>
  <div>
    {{a}}
    <button @click="addModule">新增</button>
  </div>
</template>
<script>
export default {
  name: "TestWebpackTest",
  mounted() {
    console.log(this);
  },
  data() {
    return {
      num: 1,
      a:2
    };
  },
  watch:{
    a: function (val, oldVal) {
      console.log(val, oldVal)
    },
  },  
  methods: {
    addModule() {
      this.a++;
    }
  }
};
</script>
<style lang="scss">
div {
  .test {
    width: 10px;
    height: 15px;
    background-color: blue;
  }
}
</style>

初始化watch方法發(fā)生在initState(vm)方法中,該方法執(zhí)行initWatch方法:

function initState(vm) {
  var opts = vm.$options;
  ...
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}
...
function initWatch(vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (isArray(handler)) {
      for (var i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    }
    else {
      createWatcher(vm, key, handler);
    }
  }
}
...
function createWatcher(vm, expOrFn, handler, options) {
   if (isPlainObject(handler)) {
     options = handler;
     handler = handler.handler;
   }
   if (typeof handler === 'string') {
     handler = vm[handler];
   }
   return vm.$watch(expOrFn, handler, options);
 }

initWatch函數(shù)會判斷當前的watch方法a是不是個數(shù)組,不是數(shù)組執(zhí)行else的 createWatcher(vm, key, handler)方法。主要執(zhí)行vm.$watch(expOrFn, handler, options)方法:

Vue.prototype.$watch = function (expOrFn, cb, options) {
  var vm = this;
  ...
  options = options || {};
  options.user = true;
  var watcher = new Watcher(vm, expOrFn, cb, options);
  ...
  return function unwatchFn() {
    watcher.teardown();
  };
};

$watch方法主要是實例化了一個觀察者Watcher:

function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      ...
      this.dirty = this.lazy; // for lazy watchers
      this.deps = [];
      this.newDeps = [];
      ...
      // expOrFn = 'a'
      if (isFunction(expOrFn)) {
        this.getter = expOrFn;
      }
      else {
        this.getter = parsePath(expOrFn);
        ...
      }
      this.value = this.lazy ? undefined : this.get();
    }

由于expOrFn是字符串’a’,所以會執(zhí)行 parsePath(expOrFn)方法:

function parsePath(path) {
  ...
  // ['a']
  var segments = path.split('.');
  return function (obj) {
    for (var i = 0; i < segments.length; i++) {
      if (!obj)
        return;
      obj = obj[segments[i]];
    }
    return obj;
  };
}

該方法返回一個函數(shù),并賦值給watcher實例的getter方法。此時執(zhí)行完this.getter = parsePath(expOrFn)方法,繼續(xù)執(zhí)行this.get()方法:

Watcher.prototype.get = function () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  }
  catch (e) {
   ...
  }
  finally {
    ...
    popTarget();
    this.cleanupDeps();
  }
  return value;
};

該方法執(zhí)行pushTarget將Dep.target設(shè)置為當前觀察者(watcher),然后執(zhí)行 this.getter.call(vm, vm)方法,由于getter方法是parsePath(expOrFn)方法的返回函數(shù):

// obj = 'vm' segments = ['a']
function (obj) {
  for (var i = 0; i < segments.length; i++) {
     if (!obj)
       return;
     obj = obj[segments[i]];
   }
   return obj;
 }

這里可以看出遍歷watch方法的key值,這里是’a’,然后去當前的vm實例中獲取該變量,觸發(fā)該變量的getter方法從而建立該觀察者和該變量之間的關(guān)系。

當前的watch方法a有一個deps放的就是發(fā)布者,該發(fā)布者的更新要觸發(fā)訂閱者,所以subs里面放的是watch方法a的watcher。

觸發(fā)依賴

觸發(fā)依賴的過程很簡單,當數(shù)據(jù)改變時會觸發(fā)變量的setter方法。會獲取該變量的訂閱者,并執(zhí)行訂閱者中的update方法:

Dep.prototype.notify = function (info) {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  ...
  for (var i = 0, l = subs.length; i < l; i++) {
    if (info) {
      var sub = subs[i];
      sub.onTrigger &&
        sub.onTrigger(__assign({ effect: subs[i] }, info));
    }
    subs[i].update();
  }
};
Watcher.prototype.update = function () {
 // this.lazy = false
  if (this.lazy) {
    this.dirty = true;
  }
  ...
  else {
    queueWatcher(this);
  }
};

最后會執(zhí)行queueWatcher(this)方法,接下來一系列的過程就是異步執(zhí)行watcher.run()方法:

Watcher.prototype.run = function () {
  if (this.active) {
    var value = this.get();
    if (value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep) {
      // set new value
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        var info = "callback for watcher \"".concat(this.expression, "\"");
        // this.cb是watch方法a的函數(shù)
        invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info);
      }
      else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
};

該方法獲取將新值和舊值放入invokeWithErrorHandling函數(shù)中:

function invokeWithErrorHandling(handler, context, args, vm, info) {
  var res;
  try {
    res = args ? handler.apply(context, args) : handler.call(context);
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
      res._handled = true;
    }
  }
  catch (e) {
    handleError(e, vm, info);
  }
  return res;
}

該方法執(zhí)行回調(diào),至此watch方法a執(zhí)行完畢。

總結(jié)

  • 初始化執(zhí)行initWatch(vm, opts.watch)方法創(chuàng)建watcher并定義了watcher的getter方法,隨后觸發(fā)getter方法去觸發(fā)變量的getter方法建立變量和watcher相互之間的聯(lián)系。
  • 變量發(fā)生變化會觸發(fā)變量的訂閱者的update方法并執(zhí)行run方法去獲取最新的值,并通過執(zhí)行訂閱者的cb方法傳入新舊值。

到此這篇關(guān)于Vue watch原理源碼層深入講解的文章就介紹到這了,更多相關(guān)Vue watch原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

剑川县| 武城县| 治多县| 英超| 忻城县| 嘉义市| 丰镇市| 黄陵县| 福泉市| 内江市| 永寿县| 涿州市| 神农架林区| 马鞍山市| 山东省| 南漳县| 朝阳市| 石嘴山市| 平远县| 正镶白旗| 镇坪县| 台州市| 松滋市| 玛沁县| 安义县| 长寿区| 贵德县| 顺平县| 马鞍山市| 信宜市| 岳池县| 汪清县| 方正县| 扎鲁特旗| 银川市| 棋牌| 教育| 桃江县| 静海县| 水城县| 耒阳市|