задний план
Прошел почти год с момента официального выпуска Vue 3. Я считаю, что многие мелкие партнеры уже использовали Vue 3 в производственной среде. Сегодня Vue.js 3.2 имеетофициальный релиз, и на этот разminor
Версия обновления в основном отражена в оптимизации уровня исходного кода, на уровне пользователя, по сути, не так много изменений. Один поймал мою точку зрения, чтобы повысить отзывчивую производительность:
- More efficient ref implementation (~260% faster read / ~50% faster write)
- ~40% faster dependency tracking
- ~17% less memory usage
Переведеноref
Повышение эффективности чтения API260%
, эффективность записи повышается примерно50%
, повышение эффективности сбора зависимостей составляет около40%
, а также уменьшить примерно17%
использование памяти.
Это просто подвесная жареная оптимизация дней AH, потому что вы знаете, что отзывчивая система является одним из основных реализации Vue.js, средство оптимизации для оптимизации его производительности для всех используемых Vue.js разработка приложения.
И эта оптимизация осуществляется не официальным персоналом Vue, а большим парнем в сообществе.@basvanmeursпредложенный, актуальныйоптимизировать кодОн был представлен 9 октября 2020 года, но из-за серьезных изменений во внутренней реализации чиновник дождался выпуска Vue.js 3.2, прежде чем включать код.
На этот раз адаптивная оптимизация производительности, предложенная basvanmeurs, действительно очень радует, не только значительно улучшая производительность Vue 3 во время выполнения, но и потому, что такой основной код может быть получен благодаря вкладу сообщества, а это означает, что Vue 3 становится все более и более популярным. люди обращают внимание; вклад некоторых способных разработчиков в основной код может заставить Vue 3 развиваться дальше и лучше.
Мы знаем, что по сравнению с Vue 2, Vue 3 сделал много оптимизаций, часть из которых заключается в том, что реализация реагирования на данные реализована с помощьюObject.defineProperty
API измененProxy
API.
Когда продвигали Vue 3, было официально объявлено, что он оптимизировал производительность адаптивной реализации.Так какие же аспекты отражены в оптимизации? Некоторые друзья думают, чтоProxy
Производительность API лучше, чемObject.defineProperty
Да не совсем, на самом делеProxy
по производительности, чемObject.defineProperty
Плохо, пожалуйста, обратитесь к деталямThoughts on ES6 Proxies PerformanceЭта статья, и я также проверил ее, вывод такой же, как и выше, вы можете обратиться к этомуrepo.
теперь, когдаProxy
Медленно, почему Vue 3 выбрал его для реализации реагирования на данные? потому чтоProxy
По сути, это захват объекта, чтобы он мог не только следить за изменением значения атрибута объекта, но и следить за добавлением и удалением атрибутов объекта;Object.defineProperty
заключается в добавлении соответствующего свойства к существующему свойству объектаgetter
а такжеsetter
, поэтому он может отслеживать только изменения значения этого свойства, но не добавление и удаление свойств объекта.
Оптимизация производительности отзывчивости на самом деле отражается в превращении глубоко вложенных объектов в адаптивные сценарии. В реализации Vue 2, когда данные становятся отзывчивыми на этапе инициализации компонента, они будут выполняться рекурсивно, когда подсвойство все еще является объектом.Object.defineProperty
Определите отзывчивость подобъектов; в реализации Vue 3 только при доступе к свойствам объекта будет оцениваться тип подсвойств, чтобы решить, выполнять ли рекурсивно или нетreactive
, который на самом деле является адаптивной реализацией отложенного определения подобъектов, что в определенной степени улучшит производительность.
Таким образом, по сравнению с Vue 2, Vue 3 делает некоторые оптимизации в адаптивной части реализации, но эффект на самом деле ограничен. Оптимизация отзывчивой производительности в Vue.js 3.2 на этот раз действительно совершила качественный скачок.Далее давайте возьмем некоторые сложные блюда, проанализируем конкретные оптимизации на уровне исходного кода и причины, лежащие в основе этих оптимизаций, технические соображения.
Принцип гибкой реализации
Так называемый отзывчивый, то есть когда мы изменяем данные, вы можете автоматически делать определенные вещи; соответствующие рендерингу компонента измененные данные могут автоматически запускать повторный рендеринг компонентов.
Vue 3 реагирует на достижения, в основном за счетProxy
API захватывает чтение и запись объектов данных, и когда мы получаем доступ к данным, он запускаетgetter
Выполнить сбор зависимостей; когда данные будут изменены, он будет запущенsetter
Уведомление об отправке.
Далее давайте кратко проанализируем реализацию сбора зависимостей и отправки уведомлений (до Vue.js 3.2).
Коллекция зависимостей
Прежде всего, давайте рассмотрим процесс сбора зависимостей, суть которого заключается в срабатывании триггера при доступе к адаптивным данным.getter
Функция,track
Функция собирает зависимости:
let shouldTrack = true
// 当前激活的 effect
let activeEffect
// 原始数据对象 map
const targetMap = new WeakMap()
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每个 target 对应一个 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每个 key 对应一个 dep 集合
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 收集当前激活的 effect 作为依赖
dep.add(activeEffect)
// 当前激活的 effect 收集 dep 集合作为依赖
activeEffect.deps.push(dep)
}
}
Прежде чем разбирать реализацию этой функции, давайте подумаем, какие зависимости нужно собирать.Наша цель добиться отзывчивости, то есть при изменении данных мы можем автоматически делать какие-то вещи, например выполнять определенные функции, поэтому зависимости мы собираем — это функция побочного эффекта, которая выполняется после изменения данных.
track
Функция имеет три параметра, из которыхtarget
представляет необработанные данные;type
Указывает тип этой коллекции зависимостей;key
Представляет атрибут для доступа.
track
создал глобал вне функцииtargetMap
как исходный объект данныхMap
, его ключtarget
, значениеdepsMap
, как иждивенецMap
;этоdepsMap
Ключtarget
изkey
, значениеdep
собирать,dep
В коллекциях хранятся зависимые функции побочных эффектов. Для простоты понимания взаимосвязь между ними можно представить следующей схемой:
Поэтому каждый раз, когда вы выполняетеtrack
функция, которая является функцией побочного эффекта текущей активацииactiveEffect
Как зависимость, затем собранаtarget
СвязанныйdepsMap
вести перепискуkey
Коллекция зависимостей подdep
середина.
Распространить уведомление
Уведомление об отправке происходит на этапе обновления данных, ядро должно срабатывать при изменении ответных данных.setter
функция для выполненияtrigger
Уведомление о функции распределения:
const targetMap = new WeakMap()
function trigger(target, type, key) {
// 通过 targetMap 拿到 target 对应的依赖集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// 没有依赖,直接返回
return
}
// 创建运行的 effects 集合
const effects = new Set()
// 添加 effects 的函数
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
effects.add(effect)
})
}
}
// SET | ADD | DELETE 操作之一,添加对应的 effects
if (key !== void 0) {
add(depsMap.get(key))
}
const run = (effect) => {
// 调度执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接运行
effect()
}
}
// 遍历执行 effects
effects.forEach(run)
}
trigger
Функция имеет три параметра, из которыхtarget
Представляет целевой примитивный объект;type
Указывает тип обновления;key
Указывает свойство для изменения.
trigger
Функция в основном делает четыре вещи:
-
от
targetMap
Залезайtarget
Соответствующий набор зависимостейdepsMap
; -
создать прогон
effects
собирать; -
согласно с
key
отdepsMap
найти соответствующийeffect
добавить вeffects
собирать; -
траверс
effects
Выполните связанную функцию побочного эффекта.
Поэтому каждый раз, когда вы выполняетеtrigger
функция, согласноtarget
а такжеkey
,отtargetMap
Найдите все связанные функции побочных эффектов в обходе и выполните их снова.
В процессе описания, основанного на уведомлении о сборе и распространении, мы все упоминаем слово: функция побочного эффекта, в зависимости от процесса сбора, мы ставимactiveEffect
(в настоящее время активирует функцию побочного эффекта) как набор зависимостей, что это такое? Далее, давайте взглянем на истинное лицо функций побочных эффектов.
функция побочных эффектов
Итак, что такое функция побочного эффекта? Прежде чем представить ее, давайте рассмотрим исходное требование отзывчивости, то есть мы можем автоматически делать определенные вещи, изменяя данные. Вот простой пример:
import { reactive } from 'vue'
const counter = reactive({
num: 0
})
function logCount() {
console.log(counter.num)
}
function count() {
counter.num++
}
logCount()
count()
Определяем реактивные объектыcounter
, затем вlogCount
Чтобы получить доступ кcounter.num
, мы хотим выполнитьcount
модификация функцииcounter.num
значение, оно может быть выполнено автоматическиlogCount
функция.
Согласно нашему предыдущему анализу процесса сбора зависимостей, еслиlogCount
даactiveEffect
, то требование может быть выполнено, но, очевидно, не может быть выполнено, так как код выполняется до тех пор, покаconsole.log(counter.num)
Эта линия, когда она сама по себеlogCount
Операция в функции игнорируется.
Что же тогда делать? На самом деле, пока мы бежимlogCount
Перед функцией поставитьlogCount
назначить наactiveEffect
Достаточно:
activeEffect = logCount
logCount()
Следуя этому направлению мысли, мы можем использовать идею функций высшего порядка дляlogCount
Сделайте один пакет:
function wrapper(fn) {
const wrapped = function(...args) {
activeEffect = fn
fn(...args)
}
return wrapped
}
const wrappedLog = wrapper(logCount)
wrappedLog()
wrapper
сама является функцией, она принимаетfn
В качестве аргумента возвращает новую функциюwrapped
, а затем сохраните глобальную переменнуюactiveEffect
,когдаwrapped
При выполнении поставитьactiveEffect
Установить какfn
, затем выполнитеfn
Вот и все.
Итак, когда мы выполняемwrappedLog
После этого перейдите к изменениюcounter.num
, он будет автоматически выполнятьсяlogCount
функция.
Фактически, Vue 3 использует аналогичный подход и имеетeffect
Функция побочных эффектов, давайте посмотрим на ее реализацию:
// 全局 effect 栈
const effectStack = []
// 当前激活的 effect
let activeEffect
function effect(fn, options = EMPTY_OBJ) {
if (isEffect(fn)) {
// 如果 fn 已经是一个 effect 函数了,则指向原始函数
fn = fn.raw
}
// 创建一个 wrapper,它是一个响应式的副作用的函数
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
// lazy 配置,计算属性会用到,非 lazy 则直接执行一次
effect()
}
return effect
}
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effect.active) {
// 非激活状态,则判断如果非调度执行,则直接执行原始函数。
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
// 清空 effect 引用的依赖
cleanup(effect)
try {
// 开启全局 shouldTrack,允许依赖收集
enableTracking()
// 压栈
effectStack.push(effect)
activeEffect = effect
// 执行原始函数
return fn()
}
finally {
// 出栈
effectStack.pop()
// 恢复 shouldTrack 开启之前的状态
resetTracking()
// 指向栈最后一个 effect
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++
// 标识是一个 effect 函数
effect._isEffect = true
// effect 自身的状态
effect.active = true
// 包装的原始函数
effect.raw = fn
// effect 对应的依赖,双向指针,依赖包含对 effect 的引用,effect 也包含对依赖的引用
effect.deps = []
// effect 的相关配置
effect.options = options
return effect
}
В сочетании с приведенным выше кодомeffect
внутренне, выполнивcreateReactiveEffect
функция для создания новогоeffect
функция, для и внешняяeffect
функциональное различие, мы называем этоreactiveEffect
функцию, а также добавить к ней некоторые дополнительные свойства (я отметил их в комментариях). Кроме того,effect
Функция также поддерживает передачу параметра конфигурации для поддержки большего количестваfeature
, здесь не будет распространяться.
reactiveEffect
Функция — это реактивная функция с побочным эффектом, которая при выполненииtrigger
Когда процесс отправляет уведомление, выполнениеeffect
Это оно.
Согласно нашему предыдущему анализу,reactiveEffect
Функции должны делать только две вещи: сделать глобальнуюactiveEffect
Укажите на него, а затем выполните обернутые примитивыfn
.
Но на самом деле его реализация сложнее, в первую очередь он будет судитьeffect
Статусactive,
Это фактически средство контроля, позволяющееactive
состояние и незапланированное выполнение, выполнение исходной функции напрямуюfn
и вернуться.
Тогда судитеeffectStack
содержит ли онeffect
, если не поставитьeffect
вставить в стек. Как мы упоминали ранее, просто установитеactiveEffect = effect
Вот и все, так зачем проектировать структуру стека здесь?
Фактически, учитывая следующие вложенныеeffect
Сценарий:
import { reactive} from 'vue'
import { effect } from '@vue/reactivity'
const counter = reactive({
num: 0,
num2: 0
})
function logCount() {
effect(logCount2)
console.log('num:', counter.num)
}
function count() {
counter.num++
}
function logCount2() {
console.log('num2:', counter.num2)
}
effect(logCount)
count()
Каждый раз, когда мы выполняемeffect
функционировать, если толькоreactiveEffect
функция, возложенная наactiveEffect
, то для этого вложенного сценария выполнитеeffect(logCount2)
назад,activeEffect
ещеeffect(logCount2)
вернутьreactiveEffect
функцию, чтобы последующий доступcounter.num
Когда зависимость собирает соответствующийactiveEffect
Это неправильно, в это время мы выполняем внешнеcount
модификация функцииcounter.num
После казни нетlogCount
функция, ноlogCount2
функции, окончательный вывод выглядит следующим образом:
num2: 0
num: 0
num2: 0
И наш ожидаемый результат должен быть следующим:
num2: 0
num: 0
num2: 0
num: 1
Поэтому для вложенныхeffect
сценарий, мы не можем просто назначитьactiveEffect
, следует учитывать, что выполнение самой функции представляет собой операцию push-and-pop, поэтому мы также можем спроектироватьeffectStack
, так что каждый раз, когда вы вводитеreactiveEffect
Функция сначала помещает его в стек, а затемactiveEffect
указать на этоreactiveEffect
функцию, то вfn
После завершения выполнения извлеките стек, а затем поместитеactiveEffect
направлениеeffectStack
Последний элемент, внешний слойeffect
соответствующий функцииreactiveEffect
.
Здесь мы также замечаем деталь, которая будет выполняться до того, как будет помещена в стек.cleanup
функция пустаяreactiveEffect
Зависимости, соответствующие функциям. в исполненииtrack
функция, в дополнение к сбору текущих активированныхeffect
как зависимость, также черезactiveEffect.deps.push(dep)
Пучокdep
так какactiveEffect
зависимости, так что вcleanup
когда мы сможем найтиeffect
соответствующийdep
, затем поставьтеeffect
от нихdep
удалено в.cleanup
Код функции выглядит так:
function cleanup(effect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
зачем нужноcleanup
Шерстяная ткань? Если вы столкнулись с этим сценарием:
<template>
<div v-if="state.showMsg">
{{ state.msg }}
</div>
<div v-else>
{{ Math.random()}}
</div>
<button @click="toggle">Toggle Msg</button>
<button @click="switchView">Switch View</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
msg: 'Hello World',
showMsg: true
})
function toggle() {
state.msg = state.msg === 'Hello World' ? 'Hello Vue' : 'Hello World'
}
function switchView() {
state.showMsg = !state.showMsg
}
return {
toggle,
switchView,
state
}
}
}
</script>
В сочетании с кодом вы можете знать, что представление этого компонента будет основано наshowMsg
Управление отображением переменныхmsg
или случайное число, когда мы нажимаемSwitch View
кнопку, значение переменной будет изменено.
если нетcleanup
, когда шаблон отображается впервые,activeEffect
является функцией рендеринга побочного эффекта компонента, потому что шаблонrender
посетил, когдаstate.msg
, поэтому выполняется сбор зависимостей с функцией рендеринга побочного эффекта какstate.msg
зависимость, мы называем этоrender effect
. Затем мы нажимаемSwitch View
кнопка, вид переключается на отображение случайных чисел, затем снова нажимаемToggle Msg
кнопка, так как измененоstate.msg
Уведомление будет отправлено, найденоrender effect
И выполнить, он запускает повторный рендеринг компонента.
Но это поведение на самом деле не так, как ожидалось, потому что, когда мы нажимаемSwitch View
Кнопка, когда представление переключается на отображение случайных чисел, это также вызывает повторную визуализацию компонента, но представление в это время не визуализируется.state.msg
, поэтому изменения в нем не должны влиять на повторный рендеринг компонента.
Итак, в компонентеrender effect
Перед выполнением, если прошлоcleanup
Очистите зависимости, прежде чем мы сможем удалитьstate.msg
собралrender effect
полагаться. На этот раз, когда мы модифицируемstate.msg
Когда зависимости нет, повторный рендеринг компонента не будет запущен, как и ожидалось.
Оптимизация для гибкой реализации
Принцип адаптивной реализации был проанализирован ранее, и вроде бы все в порядке, так что тут еще можно оптимизировать?
Оптимизация сбора зависимостей
В настоящее время каждый раз, когда выполняется функция побочного эффекта, ее необходимо выполнить в первую очередь.cleanup
Очистите зависимости, а затем повторно соберите зависимости во время выполнения функции побочного эффекта, что включает в себя многоSet
Добавление и удаление операций для коллекций. Во многих сценариях зависимости изменяются редко, поэтому есть место для оптимизации.
Чтобы сократить количество добавляемых и удаляемых коллекций, нам необходимо определить состояние каждой зависимой коллекции, например, была ли она собрана заново или уже собрана.
Так что здесь нам нужно дать коллекциюdep
Добавьте два свойства:
export const createDep = (effects) => {
const dep = new Set(effects)
dep.w = 0
dep.n = 0
return dep
}
вw
Указывает, был ли он собран,n
Указывает, является ли он новым.
Затем создайте несколько глобальных переменных,effectTrackDepth
,trackOpBit
,maxMarkerBits
.
вeffectTrackDepth
Указывает рекурсивное вложенное выполнениеeffect
глубина функции;trackOpBit
Используется для определения состояния коллекции зависимостей;maxMarkerBits
Представляет максимальное количество битов в маркере.
Далее посмотрите на их приложения:
function effect(fn, options) {
if (fn.effect) {
fn = fn.effect.fn
}
// 创建 _effect 实例
const _effect = new ReactiveEffect(fn)
if (options) {
// 拷贝 options 中的属性到 _effect 中
extend(_effect, options)
if (options.scope)
// effectScope 相关处理逻辑
recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
// 立即执行
_effect.run()
}
// 绑定 run 函数,作为 effect runner
const runner = _effect.run.bind(_effect)
// runner 中保留对 _effect 的引用
runner.effect = _effect
return runner
}
class ReactiveEffect {
constructor(fn, scheduler = null, scope) {
this.fn = fn
this.scheduler = scheduler
this.active = true
// effect 存储相关的 deps 依赖
this.deps = []
// effectScope 相关处理逻辑
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
if (!effectStack.includes(this)) {
try {
// 压栈
effectStack.push((activeEffect = this))
enableTracking()
// 根据递归的深度记录位数
trackOpBit = 1 << ++effectTrackDepth
// 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect
if (effectTrackDepth <= maxMarkerBits) {
// 给依赖打标记
initDepMarkers(this)
}
else {
cleanupEffect(this)
}
return this.fn()
}
finally {
if (effectTrackDepth <= maxMarkerBits) {
// 完成依赖标记
finalizeDepMarkers(this)
}
// 恢复到上一级
trackOpBit = 1 << --effectTrackDepth
resetTracking()
// 出栈
effectStack.pop()
const n = effectStack.length
// 指向栈最后一个 effect
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
}
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
можно увидеть,effect
Реализация функции была доработана и скорректирована в определенной степени, и она используется внутри.ReactiveEffect
класс создает_effect
экземпляр, и функция возвращаетrunner
указывает наReactiveEffect
Категорияrun
метод.
То есть выполнение функций побочных эффектовeffect
функция, которая на самом деле выполняет этоrun
функция.
когдаrun
Когда функция выполняется, мы замечаемcleanup
Функция больше не выполняется по умолчанию, в инкапсулированной функцииfn
Перед выполнением сначала выполнитеtrackOpBit = 1 << ++effectTrackDepth
ЗаписыватьtrackOpBit
, а затем сравните, превышает ли глубина рекурсииmaxMarkerBits
, если он превышает (обычно нет) старый все равно выполняетсяcleanup
логика, выполнить, если не превышеноinitDepMarkers
Отметьте зависимость, чтобы увидеть ее реализацию:
const initDepMarkers = ({ deps }) => {
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].w |= trackOpBit // 标记依赖已经被收集
}
}
}
initDepMarkers
Реализация функции очень проста, пересекающаяся_effect
Примерdeps
атрибут, для каждогоdep
изw
атрибут помечен какtrackOpBit
ценность .
будет выполняться следующимfn
Функция — это функция, инкапсулированная функцией побочного эффекта, такой как рендеринг для компонента,fn
Это функция рендеринга компонента.
когдаfn
Когда функция выполняется, она получает доступ к ответным данным и запускает ихgetter
, а затем выполнитьtrack
Функция выполняет сбор зависимостей. Соответственно, были внесены некоторые коррективы в процесс сбора зависимостей:
function track(target, type, key) {
if (!isTracking()) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每个 target 对应一个 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每个 key 对应一个 dep 集合
depsMap.set(key, (dep = createDep()))
}
const eventInfo = (process.env.NODE_ENV !== 'production')
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
function trackEffects(dep, debuggerEventExtraInfo) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
// 标记为新依赖
dep.n |= trackOpBit
// 如果依赖已经被收集,则不需要再次收集
shouldTrack = !wasTracked(dep)
}
}
else {
// cleanup 模式
shouldTrack = !dep.has(activeEffect)
}
if (shouldTrack) {
// 收集当前激活的 effect 作为依赖
dep.add(activeEffect)
// 当前激活的 effect 收集 dep 集合作为依赖
activeEffect.deps.push(dep)
if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) {
activeEffect.onTrack(Object.assign({
effect: activeEffect
}, debuggerEventExtraInfo))
}
}
}
Мы обнаружили, что при созданииdep
, выполнивcreateDep
метод выполняется, кроме того, вdep
активирован доeffect
Прежде чем собирать как зависимость, он будет судить об этомdep
Было ли оно собрано, если оно было собрано, его не нужно собирать снова. Кроме того, здесь будет решено, чтоdep
Является ли это новой зависимостью, если нет, отметьте ее как новую.
Далее давайте посмотрим наfn
Логика после выполнения:
finally {
if (effectTrackDepth <= maxMarkerBits) {
// 完成依赖标记
finalizeDepMarkers(this)
}
// 恢复到上一级
trackOpBit = 1 << --effectTrackDepth
resetTracking()
// 出栈
effectStack.pop()
const n = effectStack.length
// 指向栈最后一个 effect
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
При условии выполнения флага зависимости необходимо выполнитьfinalizeDepMarkers
Заполните разметку зависимостей, чтобы увидеть его реализацию:
const finalizeDepMarkers = (effect) => {
const { deps } = effect
if (deps.length) {
let ptr = 0
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
// 曾经被收集过但不是新的依赖,需要删除
if (wasTracked(dep) && !newTracked(dep)) {
dep.delete(effect)
}
else {
deps[ptr++] = dep
}
// 清空状态
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
deps.length = ptr
}
}
finalizeDepMarkers
Главное — найти те зависимости, которые были собраны, но не собраны в новом раунде сбора зависимостей.deps
удалено в. По сути, это для решения упомянутых выше потребностей.cleanup
Сценарий: к реактивному объекту нет доступа во время рендеринга нового компонента, тогда его изменения не должны вызывать повторный рендеринг компонента.
Вышеприведенное реализует оптимизацию части сбора зависимостей, вы можете видеть, что по сравнению с предыдущим выполнением каждый разeffect
Функции должны сначала очистить зависимости, а затем добавить зависимости, Текущая реализация будет выполняться каждый разeffect
Состояние зависимостей помечается перед обернутой функцией, и собранные зависимости не будут повторно собираться в процессе.effect
Функция также удаляет зависимости, которые были собраны, но не собраны в новом раунде сбора зависимостей.
После оптимизации дляdep
Количество операций, зависящих от коллекций, сокращается, что естественным образом оптимизирует производительность.
Реактивная оптимизация API
Оптимизация отзывчивого API в основном отражена вref
,computed
и другие оптимизации API.
кref
Возьмем в качестве примера API, давайте посмотрим на его реализацию перед оптимизацией:
function ref(value) {
return createRef(value)
}
const convert = (val) => isObject(val) ? reactive(val) : val
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
// 如果传入的就是一个 ref,那么返回自身即可,处理嵌套 ref 的情况。
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl {
constructor(_rawValue, _shallow = false) {
this._rawValue = _rawValue
this._shallow = _shallow
this.__v_isRef = true
// 非 shallow 的情况,如果它的值是对象或者数组,则递归响应式
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// 给 value 属性添加 getter,并做依赖收集
track(toRaw(this), 'get' /* GET */, 'value')
return this._value
}
set value(newVal) {
// 给 value 属性添加 setter
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// 派发通知
trigger(toRaw(this), 'set' /* SET */, 'value', newVal)
}
}
}
ref
функция возвращенаcreateRef
возвращаемое значение выполнения функции, аcreateRef
Внутри вложение обрабатывается в первую очередьref
случае, если входящийrawValue
Такжеref
, затем вернитесь напрямуюrawValue
; затем вернутьсяRefImpl
экземпляр объекта.
а такжеRefImpl
Внутренняя реализация, в основном захват ее экземпляраvalue
атрибутgetter
а такжеsetter
.
при посещенииref
объектvalue
свойство, вызоветgetter
воплощать в жизньtrack
Функция выполняет сбор зависимостей и возвращает ее значение; при измененииref
объектvalue
значение, оно сработаетsetter
установить новое значение и выполнитьtrigger
уведомление об отправке функции, если новое значениеnewVal
является типом объекта или массива, а затем преобразует его вreactive
объект.
Далее рассмотрим изменения, связанные с реализацией этой части в Vue.js 3.2:
class RefImpl {
constructor(value, _shallow = false) {
this._shallow = _shallow
this.dep = undefined
this.__v_isRef = true
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? value : convert(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
triggerRefValue(this, newVal)
}
}
}
Основное изменение заключается в том, чтоref
объектvalue
Свойства выполняют логику, основанную на сборе и отправке уведомлений.
В Vue.js версии 3.2ref
В реализации о части коллекции зависимостей от оригиналаtrack
функция изменена наtrackRefValue
, чтобы увидеть его реализацию:
function trackRefValue(ref) {
if (isTracking()) {
ref = toRaw(ref)
if (!ref.dep) {
ref.dep = createDep()
}
if ((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep, {
target: ref,
type: "get" /* GET */,
key: 'value'
})
}
else {
trackEffects(ref.dep)
}
}
}
Вы можете увидеть здесь непосредственноref
Соответствующие зависимости сохраняются вdep
свойства, находясь вtrack
При реализации функции будет сохранена зависимость от глобальногоtargetMap
середина:
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每个 target 对应一个 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每个 key 对应一个 dep 集合
depsMap.set(key, (dep = createDep()))
}
Очевидно,track
Может потребоваться сделать несколько оценок и установить логику внутри функции, а также сохранить зависимости вref
объектdep
Эта серия суждений и настроек исключена из свойств для оптимизации производительности.
соответствующий,ref
Реализация части про отправку уведомлений, оригиналомtrigger
функция изменена наtriggerRefValue
, чтобы увидеть его реализацию:
function triggerRefValue(ref, newVal) {
ref = toRaw(ref)
if (ref.dep) {
if ((process.env.NODE_ENV !== 'production')) {
triggerEffects(ref.dep, {
target: ref,
type: "set" /* SET */,
key: 'value',
newValue: newVal
})
}
else {
triggerEffects(ref.dep)
}
}
}
function triggerEffects(dep, debuggerEventExtraInfo) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
if ((process.env.NODE_ENV !== 'production') && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
}
else {
effect.run()
}
}
}
}
потому что прямо изref
Получите все его зависимости в атрибуте и пройдите выполнение, нет необходимости выполнятьtrigger
Функция имеет дополнительную логику, поэтому производительность также повышается.
Дизайн trackOpBit
Осторожно, вы можете обнаружить, что эта отметка зависитtrackOpBit
, используя оператор сдвига влево в каждом вычисленииtrackOpBit = 1 << ++effectTrackDepth
; а при присваивании используется операция ИЛИ:
deps[i].w |= trackOpBit
dep.n |= trackOpBit
Так почему же он разработан таким образом? потому чтоeffect
Выполнение может быть рекурсивным, таким образом могут быть записаны флаги зависимости каждого уровня.
судитьdep
Был ли он использован при сборе зависимостейwasTracked
функция:
const wasTracked = (dep) => (dep.w & trackOpBit) > 0
Результат операции И больше, чем0
Судя по всему, для этого требуется, чтобы уровень вложенности зависимостей совпадал при их сборе. Например, предположим, чтоdep.w
Значение2
, что указывает на то, что он выполняется в первом слоеeffect
Функция создана, но в это время выполнен вложенный второй уровеньeffect
функция,trackOpBit
Сдвиг влево на две позиции становится4
,2 & 4
Значение0
,ТакwasTracked
Возвращаемое значение функции равноfalse
, указывая на то, что эту зависимость необходимо собрать. Очевидно, что это требование разумно.
можно увидеть, если нетtrackOpBit
С дизайном битовых операций вам сложно иметь дело с тегами зависимостей разного уровня вложенности.Такой дизайн также отражает очень солидные базовые компьютерные навыки basvanmeurs.
Суммировать
Как правило, в приложении Vue.js доступ к адаптивным данным и их изменение являются очень частыми операциями, поэтому оптимизация производительности этого процесса значительно повысит производительность всего приложения.
Большинство людей смотрят на адаптивную реализацию Vue.js, и наиболее вероятной целью является понимание принципа реализации, и редко обращают внимание на то, оптимальна ли реализация. А может начальник basvanmeurs предложить реализацию этой серии оптимизаций, и написать рукописнуюэталонный инструментЧтобы проверить нашу собственную оптимизацию, нам очень полезно учиться.
Надеюсь, вы дочитали эту статью.Помимо лайков, сборов и пересылок три раза подряд, вы также можете зайти и посмотреть.оригинальный пост, взгляните на их обсуждения, я думаю, вы получите больше.
Оптимизация производительности фронтенда — это всегда направление, в которое стоит копать глубоко, надеюсь, что в дальнейшей разработке, будь то написание фреймворка или бизнеса, можно будет чаще задумываться о возможных точках оптимизации.