Прочитайте исходный код вместе со мной 丨 Коллекция зависимостей исходного кода Vue

Vue.js
Прочитайте исходный код вместе со мной 丨 Коллекция зависимостей исходного кода Vue

Читая исходный код, я лично думаю, что больше пользы от того, какие очки знаний вы извлекли из исходного кода.Многие из основного исходного кода Vue очень тонкие, давайте обратим внимание на реализацию его «коллекции зависимостей».

совет: версия Vue: v2.6.12, браузер: Google, метод чтения: обратитесь к пакету Vue в статическом html<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>Чтение с точками останова

Статья немного длинновата, сделайте чашку кофе и медленно читайте~

Что я узнал из «коллекции зависимостей»?

1. Шаблон наблюдателя

Основные понятия шаблона Observer:

Обратите внимание, что цель изменяется -> уведомить [уведомление] -> наблюдатели -> обновить [обновление]

Следующий код является результатом операции в исходном коде Vue, которая может сделать головы друзей простой структурой:

Глоссарий:
dep: depend [зависимость], «зависимость» здесь можно понимать как «цель наблюдения».
подписчики: подписчики [подписчики], где «подписчики» эквивалентны «наблюдателям».

// 基础数据
data: {
  a: 1, // 关联 dep:id=0 的对象,a如果发生变化,this.a=3,调用 notify,
  b: 2, // 关联 dep:id=1 的对象...
  // ...
}

dep = {
  id: 0,
  // 通知观察者们
  notify() {
    this.subs.forEach(item => {
      item.update();
    });
  },
  // 观察者们
  subs: [
    {
      id: 1,
      update() {
        // 被目标者通知,做点什么事
      }
    },
    {
      id: 2,
      update() {
        // 被目标者通知,做点什么事
      }
    }
  ]

};

dep = {
  id: 1,
  //...

2. defineProperty перехватывает объекты первого уровня/многоуровневые

Для перехвата объектов первого уровня, я думаю, мои друзья смогут.
Вот объяснение инкапсуляции перехватчика для многоуровневых настроек объекта, посмотрите на этот код:

const obj = { message: { str1: 'hello1', str2: 'hello2' } };
function observer(obj) {
  if (!(obj !== null && typeof obj === 'object')) {
    return;
  }
  walk(obj);
}
function walk(obj) {
  let keys = Object.keys(obj);
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
}
function defineReactive(obj, key) {
  let val = obj[key];
  observer(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log('get :>> ', key, val);
      return val;
    },
    set(newVal) {
      console.log('set :>> ', key, newVal);
      observer(newVal);
      val = newVal;
    }
  });
}
observer(obj);

объяснять:observer Этот метод указывает, что если он в настоящее время является объектом, он будет по-прежнему перехватываться инкапсуляцией обхода.

Работаем с obj и видим вывод консоли:

obj.message
// get :>>  message { str1: "hello1", str2: "hello2"}

/* 这个例子说明了:不管是在 get/set str1,都会先触发 message 的 get*/
obj.message.str1
// get :>>  message { str1: "hello1", str2: "hello2" }
// get :>>  str1 hello1
obj.message.str1="123"
// get :>>  message { str1: "123", str2: "hello2" }
// set :>>  str1 123

// 重点:
obj.message={test: "test"}
// set :>>  message { test: "test" }
obj.message.test='test2'
// get :>>  message { test: "test2" }
// set :>>  test test2
/* 
有些小伙伴可能会有疑惑,这里进行 obj.message={test: "test"} 赋值一个新对象的话,
不就无法检测到属性的变化,为什么执行 obj.message.test='test2' 还会触发到 set 呢?
返回到上面,在 defineReactive 方法拦截器 set 中,我们做了这样一件事:
set(newVal) {
  // 这里调用 observer 方法重新遍历,如果当前是一个对象,就会继续被遍历封装拦截
  observer(newVal)
  // ...
}
*/

Расширение до фактического бизнес-сценария: «получить информацию о пользователе, а затем отобразить ее». Я установил данные вuserInfo: {}, ajax получает результат и присваивает егоthis.userInfo = { id: 1, name: 'refined' }, вы можете отобразить его в шаблоне{{ userInfo.name }}, затем сделайтеthis.userInfo.name = "xxx", также будет выполнять адаптивный рендеринг.

3. Перехват массивов с помощью defineProperty 丨 Object.create прототипное наследование 丨 цепочка прототипов 丨 АОП

Все мы знаем, что defineProperty может перехватывать только объекты, а у Vue есть умное расширение для перехвата массивов:

var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];
methodsToPatch.forEach(function (method) {
  var original = arrayProto[method];
  Object.defineProperty(arrayMethods, method, {
    enumerable: true,
    configurable: true,
    value: function mutator(...args) {
      console.log('set and do something...');
      var result = original.apply(this, args);
      return result;
    }
  });
});
function protoAugment(target, src) {
  target.__proto__ = src;
}
var arr = [1, 2, 3];
protoAugment(arr, arrayMethods);

arr.push(4)
// set and do something...

объяснять:Object.create(arrayProto); В качестве наследования стиля прототипа, а именноarrayMethods.__proto__ === Array.prototype === true, поэтому токarrayMethodsВы можете использовать все методы массивов.

в кодеtarget.__proto__ = src,Прямо сейчасarr.__proto__ = arrayMethods, мы уже сами определили несколько методов для arrayMethods, например, push.

Теперь мы продолжаемarr.push, вы можете позвонить вarrayMethodsПользовательский толчок, и есть еще внутренние звонкиArray.prototype.pushРодной метод. Таким образом, мы завершили перехват и можем обнаружить модификацию содержимого массива.

Цепной механизм прототипа:Array.prototypeМетод push сам по себе есть, но механизм цепочки прототипов таков, что arr проходит__proto__Нашел arrayMethods.push, нашел, он не посмотрит.

Можно заметить, что эти методы инкапсуляции'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', все предполагают, что содержимое массива будет изменено, так что, если я хочу вызвать метод arr.map?Или только что упомянутый механизм цепочки прототипов, arrayMethods не имеет метода карты, поэтому я продолжаю следовать__proto__посмотреть вниз и найтиArray.prototype.map.

Я должен сказать, что пакет расширения этого массива может многому научиться, хвалите его~

Приведенные выше примеры — это все изменения содержимого массива. Детали мелких партнеров обнаружат, что если я присвою значения всему массиву, например:arr = [4,5,6], не могу остановить это, да. На самом деле я просто разделил этот пример и пример второго пункта выше. нам просто нужно столкнутьсяobserverметод, чтобы сделать такое суждение, что

function observer(value) {
  if (!(value !== null && typeof value === 'object')) {
    return;
  }
  if (Array.isArray(value)) {
    protoAugment(value, arrayMethods);
  } else {
    walk(value);
  }
}

Концепция перехвата между многоуровневыми объектами и массивами на самом деле очень похожа, за исключением того, что объектам нужно только проходить и инкапсулировать перехватчики уровень за уровнем, а массивы нужно инкапсулировать с идеей АОП.

4. Магия микрозадач — цикл событий

Вот пример из первых рук:

var waiting = false;
function queue(val) {
  console.log(val);
  nextTick();
}
function nextTick() {
  if (!waiting) {
    waiting = true;
    Promise.resolve().then(() => {
      console.log('The queue is over, do something...');
    });
  }
}

queue(1);
queue(2);
queue(3);

// 1
// 2
// 3
// The queue is over, do something...

Объяснение: Микрозадача обещания не будет выполняться до тех пор, пока не будет выполнен метод основной программы. Это также может объяснить, почему действие обновления Vue является асинхронным [т. е. мы не можем работать с домом немедленно], потому что это может улучшить производительность рендеринга, что будет подробно обсуждаться позже.

5. Волшебное использование замыканий

Вот также пример из первых рук.Лично я думаю, что это использование замыкания является ключом к достижению сбора зависимостей~

var id = 0;
var Dep = function () {
  this.id = id++;
};
Dep.prototype.notify = function notify() {
  console.log('id :>> ', this.id, ',通知依赖我的观察者们');
};
function defineReactive(obj, key) {
  var dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {},
    set() {
      dep.notify();
    }
  });
}
var obj = { str1: 'hello1', str2: 'hello2' };
defineReactive(obj, 'str1');
defineReactive(obj, 'str2');
obj.str1 = 'hello1-change';
obj.str2 = 'hello2-change';

// id :>>  0 ,通知依赖我的观察者们
// id :>>  1 ,通知依赖我的观察者们

Это также связанный объект dep, упомянутый в первом пункте.Теперь каждое свойство может получить доступ к своему собственному объекту dep в лексической области видимости, которая является замыканием.

6. с изменением объема

Это просто для имитации функции рендеринга Vue.

function render() {
  with (this) {
    return `<div>${message}</div>`;
  }
}
var data = { message: 'hello~' };
render.call(data);
// <div>hello~</div>

Это то, что мы обычно<template>не пиши в{{ this.message }}причина, но такие как:

<template>
  <div> {{ message }} </<div>
</template>

Вышеупомянутые 6 пунктов - это то, где я чувствую, что чему-то научился.Конечно, чтобы глубоко понять сбор зависимостей, нам нужно пройти через этот процесс. Если вы в данный момент находитесь перед компьютером, я скажу вам, какую строку точек останова вам нужно нажать, давайте вместе прочитаем исходный код, иди, иди, иди ~

Подробный источник

СОВЕТ. Для удобства чтения я опущу некоторый относительный код, а код похож на «✅: 123», что указывает на то, что необходимо нажать точку останова и включить отладку в браузере Google.ctrl + o, введите :123, чтобы перейти к строке 123.

Создайте новый index.html локально, импортируйте пакет Vue и откройте браузер для просмотра.

<body>
  <div id="app">
    <div>{{message }}</div>
    <button @click="handleClick">change</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'hello world'
      },
      methods: {
        handleClick() {
          this.message = 'hello world 2';
        }
      }
    });
  </script>
</body>

Точка останова ✅ :4700 метод initData

Как следует из названия, инициализируем данные, которые мы написали, и выполняем некоторые операции.В этом методе есть два метода, которые заслуживают нашего внимания.proxy(vm, "_data", key);а такжеobserve(data, true);.

function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};
  var keys = Object.keys(data);
  var i = keys.length;
  while (i--) {
    var key = keys[i];
✅ :4734  proxy(vm, "_data", key);
  }
✅ :4738  observe(data, true);
}

подсказка: встречая метод, мы можем быстро найти его, войдя, как показано на рисунке:

Переходите к методу прокси

function proxy (target, sourceKey, key) { 
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
✅ :4633  }

Анализ: этот метод используется в цикле while, когда он перебирает объекты, которые мы записали в данные. Текущая цель = vm, ключ = сообщение, переходим на эту точку останова 4633, консоль печатает цель, как показано на рисунке:

Выше мы упоминали оwithПример: Vue сделаетrender.call(vm). Таким образом, мы вызовем метод get сообщения, который является записью, и выполним серию операций позже.

Шаг в метод наблюдения

function observe (value, asRootData) {
  if (!isObject(value)) {
    return
  }
  var ob;
✅ :4633  ob = new Observer(value);
}

Анализ: можно понять, что этот метод начинает готовиться к наблюдению наблюдаемых данных за данными, например: присоединение перехватчика get/set к атрибуту, а затем выполнение чего-то в get/set соответственно...

Шагните в новый Observer [наблюдаемый класс]

Это первый основной класс, а затем мы поговорим о двух других классах отдельно, каждый из которых является основным.

var Observer = function Observer (value) {
  this.dep = new Dep();
  if (Array.isArray(value)) {
    protoAugment(value, arrayMethods);
  } else {
✅ :935  this.walk(value);
  }
};

Анализ: есть суждение о массиве или объекте Мы уже говорили о перехвате массива выше, и теперь мы сосредоточимся на методе ходьбы.

Переходите к методу ходьбы

Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
✅ :947  defineReactive$$1(obj, keys[i]);
  }
};  

Продолжайте переходить к методу defineReactive$$1.

function defineReactive$$1 (
  obj, // obj -> data
  key, // key -> 'message'
  val
) {
✅ :1021  var dep = new Dep();
 
  val = obj[key];
 
  var childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = val;
✅ :1041  if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      val = newVal;
      childOb = observe(newVal);
✅ :1070 dep.notify();
    }
  });
}

Анализ: можно сказать, что этот метод является ядром сбора зависимостей.Добавляйте зависимости с помощью метода get и уведомляйте наблюдателя с помощью метода set. Упомянутый выше прокси-метод можно рассматривать как перехватчик первого уровня.Когда мы запускаем перехватчик первого уровня, мы переходим к методу get/set, определенному в перехватчике второго уровня defineReactive$$1.

new Dep() [целевой класс наблюдения] Это второй основной класс.

Помните, мы говорили выше, что этот метод является «замыканием»? да, внутри текущего методаObject.defineProperty(obj, key, {Все вышеуказанные переменные/методы принадлежат каждому свойству независимо.

На данный момент мы завершили инкапсуляцию get/set атрибутов данных.

Как полагаться на сбор данных?

Точка останова ✅: 4074

updateComponent = function () {
✅ :4067  vm._update(vm._render(), hydrating);
};

✅ :4074 new Watcher(vm, updateComponent);

Анализ: класс наблюдателя, это третий основной класс, класс наблюдателя. И Observer [наблюдаемый класс стирания], Dep [целевой класс наблюдения], упомянутый выше, всего три. Этот фрагмент кода вызывается перед монтируемым хуком, то есть после того, как мы инкапсулировали данные данных с помощью get/set, мы начнем рендеринг.Перед рендерингом нам нужно создатьОтрисовка наблюдателя, для удобства мы называем его здесьrenderWatcher. КромеrenderWatcher,У нас все еще естьcomputedWatcherа такжеwatchWatcher, двавычисляемое свойствоа такжеслушательНаблюдатели в Vue — это в основном эти три типа наблюдателей.

Шагните в новый Наблюдатель [класс Наблюдателя]

var Watcher = function Watcher (
  vm,
  expOrFn
) {
  this.getter = expOrFn;
  this.deps = [];
  this.newDeps = [];
  this.depIds = new Set();
  this.newDepIds = new Set();
✅ :4467  this.get();
};

анализировать:

  • депс:тайникВсе экземпляры dep используются каждый раз при выполнении функции наблюдателя.
  • depIds:тайникВсе идентификаторы экземпляров dep используются каждый раз, когда функция наблюдателя выполняется для оценки.
  • новые Депсы:место храненияВсе экземпляры dep, используемые в этом выполнении функции наблюдателя.
  • новые DepIds:место храненияВсе идентификаторы экземпляров dep, используемые при выполнении функции наблюдателя на этот раз, для суждения.

Шаг в метод get

Watcher.prototype.get = function get () {
✅ :4474  pushTarget(this);
  var vm = this.vm;
✅ :4478  this.getter.call(vm, vm);
✅ :4491  popTarget();
✅ :4492  this.cleanupDeps();
};

Анализ [Этот анализ более подробный]: pushTarget и popTarget — это пара методов, которые используются для записи текущего наблюдателя и удаления текущего наблюдателя соответственно.

Dep.target = null;
var targetStack = [];
function pushTarget (target) {
  targetStack.push(target);
  Dep.target = target;
}
function popTarget () {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}

Dep.target глобально уникален, потому что одновременно выполняется только одна функция наблюдателя, назначьте текущий экземпляр наблюдателя Dep.target, а затем вы можете узнать текущего наблюдателя, просто обратившись к Dep.target, кто он.

мы идем дальшеthis.getter.call(vm, vm), [Мы кратко пройдемся по следующим шагам]

updateComponent = function () {
✅ :4067  vm._update(vm._render(), hydrating);
};

Ходитьvm._update(vm._render(), hydrating)

Vue.prototype._render = function () {
✅ :3551  vnode = render.call(vm._renderProxy, vm.$createElement);
};

Записиrender.call(vm._renderProxy, vm.$createElement), в Google откроется новая вкладка для выполнения следующей функции

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',[_v(_s(message))]),_v(" "),_c('button',{on:{"click":handleClick}},[_v("change")])])}
})

А вот и ключевая часть, это функция рендеринга Vue. Нам нужно только обратить внимание сейчас, он будет читать this.message здесь, поэтому он вызовет метод get сообщения, то есть текущий наблюдательrenderWatcherзависит от сообщения, поэтому он начинает его «собирать».

Браузер Google, нажмите «Далее» ||> «,

Мы видим, что курсор перескакивает наdefineReactive$$1Внутри метода наш метод get запускает «сбор зависимостей».

    get: function reactiveGetter () {
      var value = val;
✅ :1041  if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },

Текущий Dep.target имеет значение, поэтому выполните dep.depend для запуска зависимостей, шаг вdep.depend

Dep.protJavaScriptotype.depend = function depend () {
  if (Dep.target) {
✅ :731  Dep.target.addDep(this);
  }  
};

шаг вDep.target.addDep(this)

Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
✅ :4059  };

dep.addSub(this)Поместите текущий экземпляр наблюдателя в массив subs и оцените, что если текущий наблюдатель будет добавлен в массив subs целью наблюдения, он не будет продолжать добавляться, фильтруя повторяющиеся данные. Перейдите к этой контрольной точке 4059, консоль напечатает dep, например:

dep = {
  id:3,
  subs:[
    renderWatcher 实例
  ]
}

Выпрыгнув и продолжая спускаться, позвоните по номеру 4491.popTarget(), удаляет текущего наблюдателя.

затем шаг вthis.cleanupDeps()

Watcher.prototype.cleanupDeps = function cleanupDeps () {
  var i = this.deps.length;
  while (i--) {
    var dep = this.deps[i];
    if (!this.newDepIds.has(dep.id)) {
      dep.removeSub(this);
    }
  }
  var tmp = this.depIds;
  this.depIds = this.newDepIds;
  this.newDepIds = tmp;
  this.newDepIds.clear();
  tmp = this.deps;
  this.deps = this.newDeps;
  this.newDeps = tmp;
  this.newDeps.length = 0;
};

положи сюдаthis.deps = this.newDeps, кэшируйте его в deps, а затем очистите newDeps для следующей коллекции.

На данный момент мы завершили сбор зависимостей ~

Как обновить зависимые данные, чтобы уведомить наблюдателя о необходимости обновления?

Официальный сайт: как только будет обнаружено изменение данных, Vue откроет очередь и буферизует все изменения данных, происходящие в том же цикле событий. Если один и тот же наблюдатель запускается несколько раз, он будет помещен в очередь только один раз. Эта дедупликация во время буферизации важна, чтобы избежать ненужных вычислений и манипуляций с DOM. Затем, в следующем цикле событий «тик», Vue очищает очередь и выполняет фактическую (дедублированную) работу.

Когда пользователь нажимает кнопку изменения

this.message = 'hello world 2';

Курсор автоматически переходит к установленному методу, соответствующему сообщению, и выполняет dep.notify(), чтобы уведомить наблюдателя о необходимости выполнить действие обновления.

Dep.prototype.notify = function notify () {
  for (var i = 0, l = subs.length; i < l; i++) {
✅ :745  subs[i].update();
  }
};

Шаг в subs[i].update()

Watcher.prototype.update = function update () {
✅ :4543  queueWatcher(this);
};

Шаг в queueWatcher()

function queueWatcher (watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    if (!waiting) {
      waiting = true;
✅ :4403  nextTick(flushSchedulerQueue);
    }
  }
}

метод flushSchedulerQueue

function flushSchedulerQueue () {
  flushing = true;
  var watcher, id;
 
  queue.sort(function (a, b) { return a.id - b.id; });

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    id = watcher.id;
    has[id] = null;
✅ :4311  watcher.run();
  }
}

анализ [в сочетании с вышеуказаннымqueueWatcherа такжеflushSchedulerQueueдва метода]:

flushSchedulerQueueметод:

queue.sortНеобходимость сортировки является причиной:

Убедитесь, что наблюдатели обновляются в том же порядке, в котором они были созданы.

  1. Для компонентов родитель-потомок порядок создания компонентов таков, что сначала создается родительский компонент, а затем создается дочерний компонент, поэтому идентификатор renderWatcher родительского компонента меньше, чем у дочернего компонента.
  2. Для пользовательских наблюдателей [watchWatcher] и renderWatcher пользовательский наблюдатель создается до renderWatcher компонента.
  3. Если дочерний компонент уничтожается во время работы наблюдателя родительского компонента, наблюдатель дочернего компонента пропускается.

queueWatcherметод:

  1. Повторная оценка идентификатора наблюдателя выполняется здесь, потому что вrenderWatchЭто может зависеть от нескольких целей наблюдения.Когда мы одновременно меняем несколько зависимых значений, после того, как решаем, что watcher.id один и тот же, нет необходимости помещать в очередь два обновления, чтобы избежать потребления производительности рендеринга, например :
this.message1 = 'hello world 1';
this.message2 = 'hello world 2';
// 更多...      

или циклически менять одну и ту же зависимость

for (let i = 0; i < 10; i++) {
  this.message++;
}
  1. промывка указывает на статус обновления очереди очереди,flushing=trueОчередь делегатов обновляется.

Ветвь else здесь в основном предназначена для оценки пограничной ситуации,i--, переходя от конца к началу, на самом деле цель состоит в том, чтобы увидеть, не находится ли только что вошедший наблюдатель в текущую очередь обновления. Обратите внимание здесьindexизflushSchedulerQueueМетод определен внутри и является глобальным. Мы видим, что условие для выхода из состояния while:

  • queue[i].id === watcher.id

Мы можем понять это так, сейчас обновляется наблюдатель с id 3, а затем приходит другой наблюдатель с id 3. Это эквивалентно необходимости снова обновить наблюдателя с идентификатором 3, чтобы можно было получить последнее значение для обеспечения правильного рендеринга представления. Объясните кодом, например:

// ...
<div>{{ message }}</div>
// ...

new Vue({
  el: '#app',
  data: {
    message: 'hello world'
  },
  watch: {
    message() {
      this.message = 'hello world 3';
    }
  },
  methods: {
    handleClick() {
      this.message = 'hello world 2';
    }
  }
});

После нажатия кнопки обновить сообщение, используйте часы для отслеживания его изменений, а затем обновите сообщение внутренне.Попробуем прочитать процесс обновления этого кода. Прежде всего, определяемый пользователем наблюдатель [watchWatcher] создается перед renderWatcher, поэтому, когда мы обновляем сообщение, мы сначала выполняем наблюдение, запускаем внутренний метод и снова обновляем сообщение, чтобы убедиться, что представление отображается правильно, нам нужно выполнить обновление этого наблюдателя.

  • queue[i].id < watcher.id

Анализ: в очереди обновления есть три наблюдателя с идентификаторами 1, 2 и 5. В настоящее время обновляется наблюдатель с идентификатором 2. Когда вызывается queueWatcher и передается наблюдатель с идентификатором 3, наблюдатель помещается за 2 . , чтобы наблюдатели обновлялись в том же порядке, в котором они были созданы.

мы все знаем,flushSchedulerQueueМетод — это микрозадача. После завершения операции с очередью, после выполнения основного метода программы, начинается выполнение микрозадачи и выполняется обновление расписания очереди.watcher.run()

На данный момент мы завершили действие по уведомлению наблюдателя об обновлении при изменении цели наблюдения.

Суммировать

Приведенный выше пример является простымrenderWatcherЗамкнутый цикл процесса ,Коллекция зависимостейприбытьУведомить об обновлении. Vue имеетrenderWatcher【Просмотр наблюдателя】,computedWatcher[наблюдатель вычисляемых свойств] иwatchWatcher[Слушающий наблюдатель], в основном эти три типа наблюдателей.

Три основных класса: Dep [целевой класс наблюдения], Observe [наблюдаемый класс] и Watcher [класс наблюдателя].

Мы можем понять, что в процессе уведомления наблюдателя об изменении зависимости все делается для рендеринга представления, во время этого процесса будут выполняться некоторые оптимизации производительности/обработка некоторых крайних случаев, и, наконец, будет гарантироваться целостность рендеринга представления. .

Лично я думаю, что исходный код немного неясен и труден для понимания, но мне все равно придется просмотреть его несколько раз, чтобы ознакомиться с ним. Тем не менее, рекомендуется прочитать исходный код несколько раз лично, и будет немного расплывчато читать резюме других людей, поэтому в этой статье приводится ссылка на точки останова. Помогите небольшим партнерам быстро найти суть исходного кода.

Понимание работы исходного кода также может дать нам больше информации о том, как нам нужно вызывать API, предоставляемый фреймворком, который будет более оптимизирован.

позже

Вышел Vue 3. Прочитав Vue 2, мы можем сравнить и увидеть более мощные функции Vue 3. Здесь больше нет примеровcomputedWatcherа такжеwatchWatcherТеперь, друзья, можно запускать отладку и смотреть ~.

Вы можете получить к нему доступ из метода initState страницы:

function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
✅ :4645  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
✅ :4647  initWatch(vm, opts.watch);
  }
}

Заинтересованные друзья также могут отладить, чтобы увидеть вычисленную сцену.

computed: {
  c1() {
    return this.c2 + 'xxx';
  },
  c2() {
    return this.message + 'xxx';
  }
}

Computed является «ленивым», он не участвует в обновлении очереди, но если атрибут Computed используется в шаблоне, будет получено вычисленное значение.