Анализ 34 вопросов интервью Vue, которые должны знать старшие разработчики интерфейса (3)

Vue.js


предисловие

Из предыдущих статей мы поняли, что отклик страницы определяется изменением данных, возвращаемым функцией данных в экземпляре Vue, и мы также сосредоточились на том, как связаны отклик страницы и изменение данных, и, соответственно, в В Vue2.x и 3.x реализован принцип отклика страницы на основе изменения данных в двух версиях от нуля до единицы.

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

В то же время мы также поймем реализацию исходного кода асинхронного метода NextTick в Vue и увидим, как метод NextTick связан с асинхронным API браузера.

Обратите внимание, что исходная версия Vue, используемая в этой статье, — 2.6.11.

Что такое асинхронный рендеринг?

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

Познакомьтесь с механизмом асинхронного рендеринга на примере.

import Vue from 'Vue'
new Vue({
  el: '#app',
  template: '<div>{{val}}</div>',
  data () {
    return {
      val: 'init'
    }
  },
  mounted () {
    this.val = '我是第一次页面渲染'
    // debugger 
    this.val = '我是第二次页面渲染'
    const st = Date.now()
    while(Date.now() - st < 3000) {}
  }
})

В приведенном выше коде атрибут val присваивается дважды в смонтированном, если отрисовка страницы полностью синхронизирована с изменением данных, то страница должна отрисовываться дважды в смонтированном.

Из-за внутреннего механизма рендеринга Vue страница будет отображаться только один раз, а ответ, полученный первым назначением, объединяется с ответом, полученным вторым назначением, а окончательное значение — только рендеринг одной страницы.

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

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

Почему Vue рендерится асинхронно?

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

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

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

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

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

Как реализовать асинхронный рендеринг в Vue?

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

Возьмем приведенный выше пример, когда val назначается в первый раз, страница будет отображать соответствующий текст, но фактические изменения рендеринга будут временно сохранены.Когда val назначается во второй раз, изменения, которые будут вызваны, будут временно снова сохраняется. Он передается в функцию обратного вызова асинхронного API Promise.then. После выполнения всего синхронного кода выполняется функция обратного вызова функции then, а затем она проходит через глобальный массив, в котором хранятся изменения данных. , и определить приоритет данных во всех массивах. , и, наконец, объединить в набор данных, которые необходимо отобразить на странице, и выполнить операции рендеринга страницы.

После выполнения асинхронной очереди выполняется обход и выполнение глобального массива, в котором хранятся изменения страниц.Во время выполнения будут выполняться некоторые операции скрининга для обработки данных, которые неоднократно подвергались манипуляциям.Набор рендеринга данных.

Асинхронный API, который запускает рендеринг здесь, отдает приоритет Promise, за которым следует MutationObserver.Если MutationObserver отсутствует, он будет рассматривать setImmediate, а если нет setImmediate, последним рассмотрением является setTimeout.

Далее мы разберем процесс асинхронного рендеринга Vue на уровне исходного кода.

Далее мы проанализируем его шаг за шагом с точки зрения исходного кода.

1. Когда мы используем this.val='343' для присвоения значения, запускается функция установки Object.defineProperty, привязанная к свойству val, и функция установки запускает выполнение функции уведомления по подписке.

defineReactive() {
  ...
  set: function reactiveSetter (newVal) {
    ...
    dep.notify();
    ...
  }
  ...
}

2. В функции уведомления выполните метод обновления во всех наблюдателях компонентов подписки.

Dep.prototype.notify = function notify () {
  // 拷贝所有组件的watcher
  var subs = this.subs.slice();
  ...
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

3. После выполнения функции обновления по умолчанию lazy равно false, и sync тоже false, напрямую входит в функцию queueWatcher, которая сохраняет все изменения ответов в глобальном массиве.

Watcher.prototype.update = function update () {
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

4. В функции queueWatcher наблюдатель компонента сначала будет сохранен в очереди переменных глобального массива. По умолчанию config.async имеет значение true, и он напрямую входит в выполнение функции nextTick. nextTick — это метод, реализованный асинхронным API браузера. Его функцией обратного вызова является функция flushSchedulerQueue.

function queueWatcher (watcher) {
  ...
  // 在全局队列里存储将要响应的变化update函数
  queue.push(watcher);
  ...
  // 当async配置是false的时候,页面更新是同步的
  if (!config.async) {
    flushSchedulerQueue();
    return
  }
  // 将页面更新函数放进异步API里执行,同步代码执行完开始执行更新页面函数
  nextTick(flushSchedulerQueue);
}

5. После выполнения функции nextTick приходящая функция flushSchedulerQueue снова помещается в глобальный массив callbacks, pending в начальном случае имеет значение false, и в это время сработает timerFunc.

function nextTick (cb, ctx) {
  var _resolve;
  callbacks.push(function () {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    pending = true;
    timerFunc();
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(function (resolve) {
      _resolve = resolve;
    })
  }
}

6. Функция timerFunc реализуется асинхронными API-интерфейсами браузера, такими как Promise, MutationObserver, setImmediate и setTimeout.Функция обратного вызова асинхронного API — это функция flushCallbacks.

var timerFunc;
// 这里Vue内部对于异步API的选用,由Promise、MutationObserver、setImmediate、setTimeout里取一个// 取用的规则是 Promise存在取由Promise,不存在取MutationObserver,MutationObserver不存在setImmediate,// setImmediate不存在setTimeout。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  timerFunc = function () {
    p.then(flushCallbacks);
    if (isIOS) {
      setTimeout(noop);
    }
  };
  isUsingMicroTask = true;
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
   isNative(MutationObserver) ||  
    // PhantomJS and iOS 7.x  
   MutationObserver.toString() === '[object MutationObserverConstructor]')) {
   var counter = 1;
   var observer = new MutationObserver(flushCallbacks);
   var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };} else {
  timerFunc = function () {
    setTimeout(flushCallbacks, 0);
  };
}

7. Функция flushCallbacks будет проходить и выполнять глобальный массив обратного вызова push в nextTick.Глобальный массив обратного вызова фактически является функцией выполнения flushSchedulerQueue push на шаге 5.

// 将nextTick里push进去的flushSchedulerQueue函数进行for循环依次调用
function flushCallbacks () {
  pending = false;
  var copies = callbacks.slice(0);
  callbacks.length = 0;
  for (var i = 0; i < copies.length; i++) {
    copies[i]();
  }
}

8. В функции flushSchedulerQueue, выполняемой путем обратного обхода, flushSchedulerQueue сначала получает приоритет в соответствии с идентификатором, а затем просматривается и выполняется глобальная очередь сохраненного объекта-наблюдателя на шаге 4, запуская функцию рендеринга watcher.run.

function flushSchedulerQueue () {
var watcher, id;
// 安装id从小到大开始排序,越小的越前触发的update
queue.sort(function (a, b) { return a.id - b.id; });
// queue是全局数组,它在queueWatcher函数里,每次update触发的时候将当时的watcher,push进去
  for (index = 0; index < queue.length; index++) {
    ...
    watcher.run(); // 渲染
    ...
  }
}

9. Реализация Watcher.run находится на цепочке прототипов наблюдателя функции конструктора. В исходном состоянии активное свойство является верным, а набор метод прототипового цепи прототипа наблюдается непосредственно.

Watcher.prototype.run = function run () {
  if (this.active) {
    var value = this.get();
    ...
  }
};

10. В функции GET экземпляр объекта Watcher помещает в глобальный массив, запускает метод получения вызывающего экземпляра, а затем извлекает объект Watcher из глобального массива и очищает визуализированный зависимый экземпляр.

Watcher.prototype.get = function get () {
  pushTarget(this);
  // 将实例push到全局数组targetStack
  var vm = this.vm;
  value = this.getter.call(vm, vm);
  ...
}

11. Метод-геттер экземпляра на самом деле является функцией, переданной при его создании, то есть реальной функцией обновления _update следующего vm.

function () {
  vm._update(vm._render(), hydrating);
};

12. После выполнения функции _update экземпляра он дважды передаст виртуальный узел в виртуальную машину.patchметод для выполнения операции рендеринга.

Vue.prototype._update = function (vnode, hydrating) {
  var vm = this;
  ...
  var prevVnode = vm._vnode;
  vm._vnode = vnode;
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
  ...
};

Принцип реализации nextTick

Во-первых, nextTick — это не асинхронный API, предоставляемый самим браузером, а метод асинхронной инкапсуляции, инкапсулированный собственным асинхронным API, предоставляемым самим браузером в Vue.Приведенные выше пункты 5 и 6 являются исходным кодом его реализации.

Его правила выбора для асинхронного API браузера следующие: Promise существует через Promise, затем, если Promise не существует, он использует MutationObserver, MutationObserver не существует, setImmediate и setImmediate не существует, Наконец, для его реализации используется setTimeout.

Из приведенных выше правил доступа также видно, что nextTick может быть микрозадачей или макрозадачей.Из приоритета Promise и MutationObserver видно, что nextTick отдает приоритет микрозадачам, за которыми следуют макрозадачи setImmediate и setTimeout.

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

Может ли Vue выполнять синхронный рендеринг?

1. Vue.config.async = ложь

Конечно можно.В четвертом исходнике мы видим следующий абзац.При значении async в конфиге false, flushSchedulerQueue не добавляется в nextTick, а сразу выполняется flushSchedulerQueue.Это эквивалентно синхронная отрисовка страницы при изменении значения в этих данных.

function queueWatcher (watcher) {
  ...
  // 在全局队列里存储将要响应的变化update函数
  queue.push(watcher);
  ...
  // 当async配置是false的时候,页面更新是同步的
  if (!config.async) {
    flushSchedulerQueue();
    return
  }
  // 将页面更新函数放进异步API里执行,同步代码执行完开始执行更新页面函数
  nextTick(flushSchedulerQueue);
}

В нашем коде разработки вам нужно всего лишь добавить следующее предложение, чтобы ваша страница отображалась синхронно.

import Vue from 'Vue'
Vue.config.async = false

2. this._watcher.sync = истина

В исходном коде выполнения метода обновления Watch вы можете видеть, что, когда this.sync имеет значение true, рендеринг в это время также является синхронным.

Watcher.prototype.update = function update () {
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

В коде разработки необходимо изменить атрибут синхронизации наблюдателя на true.Для изменения атрибута синхронизации наблюдателя необходимо выполнить только this._watcher.sync=true перед операцией изменения данных, которую необходимо отобразить. синхронно.В это время страница будет выполняться синхронно.Render action.

При следующем написании страница будет отображать val как 1, а не 2, а окончательный результат рендеринга равен 3, но официальный сайт не рекомендует это использование, пожалуйста, используйте его с осторожностью.

new Vue({
  el: '#app',
  sync: true,
  template: '<div>{{val}}</div>',
  data () {
    return { val: 0 }
  },
  mounted () {
    this._watcher.sync = true
    this.val = 1
    debugger
    this._watcher.sync = false
    this.val = 2
    this.val = 3
  }
})

Суммировать

В этой статье представлены причины, по которым Vue использует асинхронный рендеринг страниц, и анализируется вся ссылка операции перед рендерингом с точки зрения исходного кода.В то же время анализируется прямая связь между реализацией асинхронного метода nextTick во Vue и родной асинхронный API. Наконец, с точки зрения исходного кода я узнал, что Vue не является неспособным к синхронному рендерингу.Когда на нашей странице требуется синхронный рендеринг, соответствующая конфигурация может быть удовлетворена.

References

[1] https://github.com/vuejs/vue

[2] https://cn.vuejs.org/

постскриптум

Если вы хотите обсудить технологию или у вас есть какие-либо комментарии или предложения по этой статье, вы можете отсканировать приведенный ниже QR-код, подписаться на общедоступную учетную запись WeChat «Full Stacker» и добро пожаловать в WeChat автора для взаимодействия с автором в любое время. . Добро пожаловать! Искренне надеемся встретиться с вами.

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