[Список вопросов о Vue] Часто блуждая по vue, спросите еще несколько почему

Vue.js
[Список вопросов о Vue] Часто блуждая по vue, спросите еще несколько почему

Что касается чтения и резюме vue, это понимание vue после глубокого размышления. Учитесь по аналогии.

Предыстория (зачем изучать исходный код проектов с открытым исходным кодом)

Вот пример, который я недавно видел:vue-routerплагин, заимствованныйvue.minВозможен миксин жизненного цикла, где жизненный цикл миксина вызывается на данном этапе этого жизненного цикла для каждого компонента:

  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })

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

Расскажите о своем понимании MVVM

MVVM состоит из следующих трех компонентов

  • Вид: интерфейс
  • Модель: модель данных
  • ViewModel: как мост, отвечающий за связь View и Model.

В период JQuery, если вам нужно обновить пользовательский интерфейс, вам нужно получить соответствующий DOM, а затем обновить пользовательский интерфейс, чтобы данные и бизнес-логика были тесно связаны со страницей.

В MVVM ядром является двусторонняя привязка данных, такая как обнаружение грязных данных в Angluar и перехват данных в Vue.

MVVM 到底是什么?与其专注于说明 MVVM 的来历,不如让我们看一个典型的应用是如何构建的,并从那里了解 MVVM:

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

Хотя представления и контроллеры представлений технически являются отдельными компонентами, они почти всегда объединяются парами.

Можно попробовать их подключить:

В типичном приложении MVC большая часть логики размещается в контроллере представления. некоторые из них принадлежатView Controller, но больше так называется“表示逻辑(presentation logic)”.

В терминологии MVVM это те вещи, которые преобразуют данные модели во что-то, что может отображать представление, например.将一个 NSDate 转换为一个格式化过的 NSString.

На наших диаграммах чего-то не хватает, что позволяет нам поставить все表示逻辑放что-то, что входит. мы собираемся назвать это “View Model”- это между View/Controller и Model:

Эта диаграмма точно описывает, что такое MVVM: расширенная версия MVC, в которой мы формально связываем представление и контроллер и перемещаем логику представления из контроллера в новый объект, модель представления.

Разговор о v-if и v-show

v-показать и v-если

  1. v-if: Истинный условный рендеринг. ложный, не в дом.
  2. v-show: всегда находится в dom, просто переключается с помощью свойства отображения css (существует в структуре html, но не отображается с помощью css). существует в структуре dom
  3. display:noneне находится в дереве рендеринга.

видимость: скрытая и отображаемая: нет

display: noneТег : не отображается на странице (хотя вы все равно можете взаимодействовать с ним через dom). Никакие другие теги не выделят для него место.visibility:hiddenТег : появится на странице, но не будет виден. Другие теги будут выделять для него место.

Данные в компоненте должны быть объектом, возвращаемым функцией, а не просто объектом.

При желании можно получить глубокую копию исходного объекта данных, передав vm.$data в JSON.parse(JSON.stringify(...)) .

Расскажите о нескольких часто используемых компонентных коммуникациях.

props emit

От отца к сыну: реквизит Сын отцу: излучать

问题:多级嵌套组件

provide / inject

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

Кнопочный компонент element-ui, часть исходного кода

// Button 组件核心源码
export default {
    name: 'ElButton',
    // 通过 inject 获取 elForm 以及 elFormItem 这两个组件
    inject: {
        elForm: {
            default: ''
        },
        elFormItem: {
            default: ''
        }
    },
    // ...
    computed: {
        _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
        },
        buttonSize() {
            return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
        },
        //...
    },
    // ...
};

问题:不能够实现子组件向祖先组件传递数据

$attrs $listeners

вышеупомянутыйprovideа такжеinjectОн реализует передачу многоуровневых компонентных данных, но不能够实现子组件向祖先组件传递数据, если вы хотите реализовать дочернее наследование, вы можете использовать$ attrs和$ listeners

Подробное понимание см. в этой статьеНовая позиция передачи данных компонента Vue:attrsа такжеатрибуты иlisteners

EventBus

Для некоторых проектов, которым не нужно вводить vuex, рассмотрите

Автобус событий:EventBusЕго можно использовать для простой реализации связи между одноуровневыми и межуровневыми компонентами, но при неправильном использовании это также принесет много проблем (vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去); поэтому он подходит для небольших страниц с несложной логикой. Рекомендуется использовать vuex, когда логика сложная.

class EventBus{
    constructor(){
        // 一个map,用于存储事件与回调之间的对应关系
        this.event=Object.create(null);
    };
    //注册事件
    on(name,fn){
        if(!this.event[name]){
            //一个事件可能有多个监听者
            this.event[name]=[];
        };
        this.event[name].push(fn);
    };
    //触发事件
    emit(name,...args){
        //给回调函数传参
        this.event[name]&&this.event[name].forEach(fn => {
            fn(...args)
        });
    };
    //只被触发一次的事件
    once(name,fn){
        //在这里同时完成了对该事件的注册、对该事件的触发,并在最后取消该事件。
        const cb=(...args)=>{
            //触发
            fn(...args);
            //取消
            this.off(name,fn);
        };
        //监听
        this.on(name,cb);
    };
    //取消事件
    off(name,offcb){
        if(this.event[name]){
            let index=this.event[name].findIndex((fn)=>{
                return offcb===fn;
            })
            this.event[name].splice(index,1);
            if(!this.event[name].length){
                delete this.event[name];
            }
        }
    }
}

Vuex

Управление состоянием, рекомендуется использовать vuex, когда логика сложная.

Жизненный цикл Vue, что делается на каждом этапе

beforeCreate,created

beforeCreate,createdЖизненный цикл находится в момент инициализации, в_initвыполнить в

Конкретный код находится вvue/src/core/instance/init.jsсередина

Vue.prototype._init = function() {
      // expose real self
    //...
    vm._self = vm
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 初始化props,methods,data,computed等
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    // ...
}
  1. beforeCreate, Нельзя использовать реквизиты, методы, данные, вычисления и т. д.
  2. initState, Инициализируйте свойства, методы, данные, вычисляемые и т. д.
  3. created, На данный момент уже есть реквизиты, методы, данные, вычисленные и т. д. Вы можете вызвать его здесь, если хотите использовать атрибут данных.

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

beforeMount,mounted

Под монтированием понимается монтирование скомпилированного HTML-шаблона в соответствующий виртуальный дом.

Вызывается до начала монтирования: связанная функция рендеринга вызывается в первый раз.

该钩子在服务器端渲染期间不被调用。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      // ...
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      // ...
      vm._update(vnode, hydrating)
      // ...
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

в исполненииvm._render()Прежде чем функция визуализирует VNode, она выполняетbeforeMountХук-функция после выполненияvm._update()После исправления VNode к реальному DOM выполнитеmountedкрюк.

beforeUpdate,updated

beforeUpdateа такжеupdatedВремя выполнения функции ловушки должно быть, когда данные обновляются

  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode
    // ...
  }

Вот деталь_isMounted, указывая на то, что вmountedвыполнить послеbeforeUpdate

Что касаетсяupdatedозначает, что при вызове этого хука DOM компонента был обновлен, поэтому теперь вы можете выполнять операции, зависящие от DOM

beforeDestroy,destroyed

beforeDestroyа такжеdestroyedВремя выполнения функции ловушки находится в стадии уничтожения компонента

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

beforeDestroyВремя выполнения функции ловушки равно$destroyгде начинается выполнение функции, а затем выполняет ряд действий по уничтожению, в том числе начиная сparentиз$childrenудалить себя, удалитьwatcher, отображаемый в данный момент VNode выполняет функцию ловушки разрушения и т. д., а затем вызывает ее после выполненияdestroyфункция крючка.

существует$destroy, он будет выполнятьсяvm.__patch__(vm._vnode, null)Вызвать функцию уничтожения крючков подкомпонентов, такого слоя рекурсивного вызова, такdestroyПорядок выполнения функции ловушки сначала дочерний, затем родительский иmountedПроцесс такой же.

actived,deactivated

activatedа такжеdeactivatedФункция ловушки — это ловушка, специально настроенная для компонентов поддержки активности.

  1. activatedдаkeep-aliveВызывается при активации компонента.
  2. deactivatedдаkeep-aliveВызывается при уничтожении компонента.

errorCaptured

Вызывается при обнаружении ошибки от компонента-потомка. Этот хук получает три параметра: объект ошибки, экземпляр компонента, в котором произошла ошибка, и строку, содержащую информацию об источнике ошибки. Этот хук может возвращать false, чтобы остановить распространение ошибки.

Что происходит, когда новый Vue

  1. Вызовите _init для слияния конфигурации, инициализации жизненного цикла, инициализации центра событий, инициализации рендеринга, инициализации данных, свойств, вычислений, наблюдателя и т. д.
  2. пройти черезObject.definePropertyнастраиватьsetterа такжеgetterФункции для отзывчивости и сбора зависимостей

Расскажите о процессе отзывчивого принципа

При создании экземпляра Vue Vue будет проходить через свойства параметра данных, использовать Object.defineProperty для добавления геттеров и сеттеров к свойствам, чтобы перехватить чтение данных (геттеры используются для сбора зависимостей, сеттеры используются для отправки обновлений), и внутренне отслеживать зависимости, чтобы уведомлять об изменениях при доступе к свойствам и их изменении.

每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 逻辑会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

Резюме: Vue.js использует перехват данных в сочетании с режимом публикации-подписки, захватывает установщики и получатели каждого свойства через Object.defineproperty, публикует сообщения подписчикам при изменении данных и запускает обратный вызов прослушивателя ответа.

основная роль

  • Наблюдатель: добавьте геттеры и сеттеры к объектам для сбора зависимостей и отправки обновлений. Не только прослушиватель данных, но и издатель;
  • Наблюдатель (подписчик): Наблюдатель пересылает данные настоящему подписчику — объекту-наблюдателю. После того, как наблюдатель получит новые данные, он обновит представление. Экземпляр наблюдателя делится на наблюдатель рендеринга (наблюдатель рендеринга), вычисляемый наблюдатель и наблюдатель пользователя-слушателя. Поддерживается массив экземпляров deps (используемых для сбора зависимостей). Во время вторичного сбора зависимостей cleanupDeps будет удалять зависимости старой подписки каждый раз, когда добавляется новая подписка;
  • compile (компилятор): уникальная роль фреймворка MVVM, отвечающая за сканирование и разбор инструкций каждого элемента узла, инициализация данных инструкций, а также создание подписчиков, также находится под его контролем;
  • Dep: используется для сбора зависимостей текущего реагирующего объекта, каждый реагирующий объект имеет экземпляр Dep (подпрограммы — это массив экземпляров Watcher), когда изменения данных запускают логику установки, передайтеdep.notify()(пройдите подпрограммы, вызовите метод update() каждого наблюдателя) уведомите каждого наблюдателя

核心角色的关系如下:

основной код

实现observer

// 遍历对象
function observer(target) {
  // target是对象,则遍历
  if (target && typeof target === 'object') {
    Object.keys(target).forEach(key => {
      defineReactive(target, key, target[key])
    })
  }
}

// 用defineProperty监听当前属性
function defineReactive(target, key, val) {
  const dep = new Dep()
  // 递归
  observer(val)
  Object.defineProperty(target, key, {
    // 可枚举
    enumerable: true,
    // 不可配置
    configurable: false,
    get: function() {
      return val
    },
    set: function(value) {
      console.log(val, value)
      val = value
    }
  })
}

实现depподписчик

class Dep {
  constructor() {
    // 初始化订阅队列
    this.subs = []
  }
  // 增加订阅
  addSub(sub) {
    this.subs.push(sub)
  }
  // 通知订阅者
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

订阅者Dep里的subs数组是Watcher实例

实现Watcher类

class Watcher {
  constructor() {}
  update() {
    // 更新视图
  }
}

Перепишите метод установки в defineReactive, чтобы уведомить подписчиков в прослушивателе:

// 用defineProperty监听当前属性
function defineReactive(target, key, val) {
  const dep = new Dep()
  // 递归
  observer(val)
  Object.defineProperty(target, key, {
    // 可枚举
    enumerable: true,
    // 不可配置
    configurable: false,
    get: function() {
      return val
    },
    set: function(value) {
      console.log(val, value)
      dep.notify()
    }
  })
}

2. Отношения между Наблюдателем и Депом

Наблюдатель создает экземпляр dep и добавляет подписчиков в dep.subs, dep проходит через dep.subs через уведомление, чтобы уведомлять о каждом обновлении наблюдателя.

3. вычисляем и смотрим

по сути, это лениво вычисляемый наблюдатель.

Computed внутренне реализует отложенный наблюдатель, то есть вычисляемый наблюдатель. Вычисляемый наблюдатель не будет выполнять немедленную оценку и одновременно удерживать экземпляр dep. через его внутреннююthis.dirty 属性标记计算属性是否需要重新求值.

При изменении состояния зависимости вычисляемого ленивый наблюдатель будет уведомлен, Вычисляемый наблюдатель оценивает, есть ли подписчики через this.dep.subs.length, Если есть, то он будет пересчитан, а затем будут сравнены новые и старые значения, и если они изменились, то будут перерендерены. (Vue хочет убедиться, что изменяется не только значение зависимости вычисляемого свойства, но и чтобы повторная визуализация наблюдателя рендеринга срабатывала при изменении окончательного вычисленного значения вычисляемого свойства, что, по сути, является оптимизацией.)

Если нет, просто установите this.dirty = true. (Когда вычисляемое свойство зависит от других данных, это свойство не будет пересчитано немедленно. Оно будет вычислено только тогда, когда свойство нужно будет прочитать в другом месте позже, то есть оно имеет функцию ленивых (ленивых вычислений).)

разница

вычисляемое вычисляемое свойство: зависит от значений других свойств и computed 的值有缓存, вычисленное значение будет пересчитано только тогда, когда значение атрибута, от которого оно зависит, изменится, при следующем получении вычисленного значения.

наблюдать за слушателем: больше роли «наблюдателя»,无缓存性, подобно обратному вызову мониторинга некоторых данных, обратный вызов будет выполняться для последующих операций при каждом изменении отслеживаемых данных.

4. Сбор зависимостей

  1. В initState при инициализации вычисляемого свойства запускается коллекция зависимостей вычисляемого наблюдателя.
  2. В initState при инициализации свойства watch запускается коллекция зависимостей пользователя Watcher.
  3. Процесс render() запускает сбор зависимостей наблюдателя рендеринга.
  4. При повторном рендеринге снова выполняется vm.render(), что удалит подписку сабов и переназначит

5. Распространяйте обновления

  1. В компоненте реагирующие данные изменяются, чтобы вызвать логику установщика.
  2. вызов отд.notity()
  3. Пройдите dep.subs (экземпляр Watcher) и вызовите update() каждого Watcehr
  4. Процесс update() использует очередь для дальнейшей оптимизации, выполняет запуск всех наблюдателей после nextTick и, наконец, выполняет их функции обратного вызова.

Измените данные, от которых зависит текущий компонент в цикле for, и измените его 10000 раз, какой будет эффект (принцип nextTick)

Если я изменю данные, от которых зависит текущий компонент, в цикле for и изменю их 10 000 раз, каков будет эффект? (涉及批量更新和 nextTick 原理) Общий процесс:在这里插入图片描述

JS-операционный механизм

Выполнение JS является однопоточным и основано на цикле событий. Цикл событий примерно разделен на следующие этапы:

  1. Все задачи синхронизации выполняются в основном потоке, образуя стек контекста выполнения.
  2. Помимо основного потока, существует еще «очередь задач» (task queue). Как только асинхронная задача имеет запущенный результат, событие помещается в «очередь задач».
  3. Как только все задачи синхронизации в «стеке выполнения» будут выполнены, система прочитает «очередь задач», чтобы увидеть, какие события в ней находятся. Затем соответствующие асинхронные задачи завершают состояние ожидания, входят в стек выполнения и начинают выполнение.
  4. Основной поток продолжает повторять третий шаг выше.

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

for (macroTask of macroTaskQueue) {  
  // 1. Handle current MACRO-TASK  
  handleMacroTask();  
  // 2. Handle all MICRO-TASK  
  for (microTask of microTaskQueue) {    
    handleMicroTask(microTask);  
}}

В среде браузера:

Общие задачи макросов включают setTimeout, MessageChannel, postMessage, setImmediate.

Общие микрозадачи включают MutationObsever и Promise.then

Асинхронная очередь обновлений

Пример решения проблемы: число会被不停地进行++操作,不断地触发它对应的Dep中的Watcher对象的update方法。然后最终queue中因为对相同idWatcher对象进行了筛选(过滤),从而queue中实际上只会存在一个number对应的Watcher对象。在下一个 tick 的时候(此时number已经变成了 1000),触发Watcher对象的run方法来更新视图,将视图上的число` идет напрямую от 0 до 1000.

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

Vue внутренне пытается использовать собственные Promise.then, MutationObserver и setImmediate для асинхронных очередей, если среда выполнения не поддерживает это, вместо этого будет использоваться setTimeout(fn, 0).

В исходном коде vue2.5 схема понижения макрозадач такова: setImmediate, MessageChannel, setTimeout

Принцип реализации метода nextTick vue:

  1. Vue использует асинхронную очередь для управления обновлением DOM и обратным вызовом nextTick для последовательного выполнения.
  2. Благодаря высокому приоритету микрозадачи могут гарантировать, что микрозадачи в очереди будут выполнены до цикла обработки событий.
  3. Учитывая проблемы совместимости, Vue разработал план перехода с микрозадачи на макрозадачу.

Как обрабатываются массивы реактивных пар Vue

  1. Переопределите все методы в массиве, которые могут изменить сам массив, такие как push, pop и т. д.
  2. Затем вручную вызовите уведомление, уведомите средство наблюдения за рендерингом и выполните обновление.

Почему вычисляемые свойства могут изменяться при изменении зависимостей?

Computed и Watch имеют общий класс Watcher, а в случае Computed есть deps. Vue использует cleanupDeps при сборе зависимостей во второй раз, после добавления новой подписки старая подписка будет удалена.

Зачем использовать прокси в Vue3.0 и отказаться от Object.defineProperty

  1. Сам Object.defineProperty имеет определенную возможность отслеживать изменения индексов массива, но в Vue, учитывая экономическую эффективность производительности/опыта, эта функция устарела (почему Vue не может обнаруживать изменения массива).

Чтобы решить эту проблему, выполните в массиве все методы, которые могут изменить сам массив, такие как push, pop и т. д.重写. Затем вручную вызовите уведомление, уведомите средство наблюдения за рендерингом и выполните обновление.

push();
pop();
shift();
unshift();
splice();
sort();
reverse();
  1. Object.defineProperty может только захватить свойства объекта, поэтому我们需要对每个对象的每个属性进行遍历. Если значение свойства также является объектом, то需要深度遍历, очевидно, если能劫持一个完整的对象Да лучший выбор.

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

Для чего используются ключи в Vue

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

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

更准确: поскольку ключ не используется повторно на месте, в функции того же узла сравнение a.key === b.key позволяет избежать повторного использования на месте. Поэтому будет точнее, если ключ не добавить, то сохранится состояние предыдущей ноды, что вызовет ряд багов.

更快速: Уникальность ключа может быть полностью использована структурой данных Map.По сравнению с временной сложностью обходного поиска O(n), временная сложность Map составляет всего O(1)

Расскажите о процессе рендеринга Vue.

  1. Вызовите функцию компиляции, чтобы сгенерировать строку функции рендеринга.Процесс компиляции выглядит следующим образом:

    • Функция parse анализирует шаблон и генерирует ast (абстрактное синтаксическое дерево).
    • Функция оптимизации оптимизирует статические узлы (отмечая контент, который не нужно обновлять каждый раз, алгоритм diff будет напрямую пропускать статические узлы, тем самым сокращая процесс сравнения и оптимизируя производительность патча)
    • Функция генерации генерирует строку функции рендеринга
  2. Вызов новой функции Watcher для отслеживания изменений данных.При изменении данных функция Render выполняется для создания объекта vnode.

  3. Вызовите метод patch, сравните старые и новые объекты vnode и добавьте, измените и удалите реальные элементы DOM с помощью алгоритма DOM diff.

Расскажите о принципе реализации и стратегии кэширования keep-alive.

export default {
  name: "keep-alive",
  abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中
  props: {
    include: patternTypes, // 被缓存组件
    exclude: patternTypes, // 不被缓存组件
    max: [String, Number] // 指定缓存大小
  },

  created() {
    this.cache = Object.create(null); // 缓存
    this.keys = []; // 缓存的VNode的键
  },

  destroyed() {
    for (const key in this.cache) {
      // 删除所有缓存
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    // 监听缓存/不缓存组件
    this.$watch("include", val => {
      pruneCache(this, name => matches(val, name));
    });
    this.$watch("exclude", val => {
      pruneCache(this, name => !matches(val, name));
    });
  },

  render() {
    // 获取第一个子元素的 vnode
    const slot = this.$slots.default;
    const vnode: VNode = getFirstComponentChild(slot);
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // name不在inlcude中或者在exlude中 直接返回vnode
      // check pattern
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }

      const { cache, keys } = this;
      // 获取键,优先获取组件的name字段,否则是组件的tag
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      // 不命中缓存,把 vnode 设置进缓存
      else {
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // keepAlive标记位
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};

принцип

  1. Получите первый объект дочернего компонента, обернутый keep-alive, и имя его компонента

  2. В соответствии с набором include/exclude (если есть) выполняется условное сопоставление, чтобы решить, нужно ли кэшировать. Не соответствует, напрямую возвращает экземпляр компонента

  3. Создайте ключ кэша на основе идентификатора компонента и тега и проверьте, был ли экземпляр компонента кэширован в объекте кэша. Если он существует, напрямую извлеките кэшированное значение и обновите позицию ключа в this.keys (обновление позиции ключа является ключом к реализации стратегии замены LRU).

  4. Сохраните экземпляр компонента в объекте this.cache и сохраните значение ключа, а затем проверьте, превышает ли количество кэшированных экземпляров установленное значение max, и если оно превышает, удалите последний неиспользованный экземпляр (то есть тот, у которого индекс 0) в соответствии с ключом политики замены LRU)

  5. Наконец, свойству keepAlive экземпляра компонента присваивается значение true, которое будет использоваться при рендеринге и выполнении функции ловушки обернутого компонента.Я не буду здесь вдаваться в подробности.

стратегия кэширования

Стратегия кэширования LRU (наименее недавно использованная): поиск в памяти самых длинных неиспользуемых данных для замены новых данных.

Основная идея заключается в следующем: «Если к данным обращались недавно, у них больше шансов получить доступ в будущем.

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

  1. Вставить новые данные в заголовок связанного списка;
  2. Всякий раз, когда происходит попадание в кеш (т. е. к данным кеша обращаются), данные перемещаются в начало связанного списка;
  3. Когда связанный список заполнен, данные в конце связанного списка отбрасываются;

Какова цель введения виртуального DOM в Vue2.0

Почему Vue использует виртуальный DOM (Virtual DOM)

  1. Он может принимать синтаксический анализ и преобразование Parser, абстрагируя исходный процесс рендеринга.
  2. Кроссплатформенность. Рендеринг на платформах, отличных от DOM, и реализация расширенных функций, таких как SSR и изоморфный рендеринг.
  3. О виртуальный дом быстрее, чем реальный дом. Преимущество виртуального DOM проявляется в больших и частых изменениях DOM. Но таких случаев не много.

Можете ли вы рассказать о соображениях оптимизации производительности при внедрении Vue?

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

  1. cache函数,利用闭包实现缓存

  2. Когда вторичная зависимость собрана,cleanupDeps, 剔除上一次存在但本次渲染不存在的依赖

  3. traverse,处理深度监听数据,解除循环引用

  4. этап оптимизации компиляции,optimizeОсновная роль заключается в标记 staticстатический узел

  5. использование компонента поддержки активностиlRU缓存淘汰алгоритм

  6. Асинхронный компонент, отрендеренный дважды

Коротко о сути процесса Диффа

Обработать

  1. Сначала сравните братьев и сестер, а затем сравните дочерние узлы.

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

  3. Давайте сравним ситуацию, когда есть детские узлы. Вот ядро ​​разных. Во-первых, он определит, будут ли два узла того же узла, судя, определяются ли ключ, тег, реамент и данные двух узлов, определены или не определены одновременно и когда тип метки вводится. Заменить Старый узел.

  4. Если это тот же узел, он войдет в фазу PatchVNode. На данном этапе ядро ​​представляет собой алгоритм с двумя указателями, при сравнении с обоих концов нового старого узла, в котором статические теги шаблона компилируются во время с ключом, чтобы пропустить сравнение статических узлов, если нет, другое другое сравнение.

Например:

// old arr
["a", "b", "c", "d", "e", "f", "g", "h"]
// new arr
["a", "b", "d", "f", "c", "e", "x", "y", "g", "h"]
  1. Начать сравнение с начала до конца, [a,b] — это тот же Vnode, ввести патч и остановиться на [c];

  2. Начать сравнение от начала до конца, [h,g] — тот же Vnode, ввести патч и остановиться на [f];

  3. Судя по тому, сравнивались ли старые данные, избыточное описание новое и его нужно смонтировать (не в этом примере)

  4. Судя по тому, сравнивались ли новые данные, лишнее описание удаляется и его нужно размонтировать (не в этом примере)

  5. Этап patchVNode. На этом этапе ядром является использование алгоритма двойного указателя и одновременное сравнение старых и новых узлов.В этом процессе статический тег и ключ при компиляции шаблона будут использоваться для пропуска сравнения статический узел. Если нет, выполните другие действия. Сравнение.

Disadvantage: Because the same-level comparison is used, if the nodes of this level are found to be different, the old nodes will be replaced with the new nodes, and the child nodes under it will not be compared to see if they are the такой же.

Сравнение vue2, vue3 и реакции

Вью2, Вью3

Vue3.x использует алгоритм ivi и алгоритм inferno.

Он определяет тип VNode при его создании и использует битовую операцию для определения типа VNode в процессе монтирования/исправления.На этой основе он взаимодействует с основным алгоритмом Diff, чтобы повысить производительность по сравнению с Vue2.x. .повысить

смотреть и реагировать

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

Разговор о vue-маршрутизаторе

Реализация различных режимов в VueRouter примерно такая:

  1. Во-первых, в соответствии с режимом определяется выбранный режим.Если текущая среда не поддерживает режим истории, она будет принудительно переключена в режим хеширования;

  2. Если текущая среда не является средой браузера, она переключится в абстрактный режим. Затем генерируются разные объекты операций с историей в соответствии с разными режимами.

новый процесс маршрутизатора

  1. Переменная app в методе init — это сохраненное this текущего экземпляра vue.
  2. Сохраните приложение в массиве apps. Используйте this.app, чтобы определить, был ли экземпляр инициализирован.
  3. Определить действие переключения разных маршрутов через history.transitionTo.
  4. Используйте history.listen для регистрации обратных вызовов ответа на изменение маршрута.

Разница между хешем и историей

  1. Наиболее очевидным является то, что на дисплее URL-адрес режима хеширования будет смешан со знаком #, а история — нет.
  2. Базовый Vue реализует их по-разному. Режим хеширования основан на событии onhashchange (прослушивание изменений в location.hash), а режим истории (popstate) в основном опирается на два новых метода в истории HTML5: pushState() может изменить URL-адрес без отправки запроса, replaceState ( ) может читать стек истории, а также изменять записи браузера.
  3. Когда действительно необходимо отправить HTTP-запрос на серверную часть через URL-адрес, например, обычный пользователь вручную вводит URL-адрес, а затем нажимает Enter или обновляет (перезапускает) браузер, режим истории нуждается в поддержке серверной части. Поскольку в режиме истории внешний URL-адрес должен совпадать с URL-адресом, который фактически отправляет запрос на серверную часть. Например, если есть URL-адрес с путем (например, www.lindaidai.wang/blogs/ id), если серверная часть не поддерживает этот путь. При обработке будет возвращена ошибка 404. Следовательно, необходимо добавить ресурс-кандидат, охватывающий все ситуации в бэкэнде, который обычно соответствует странице 404, предоставленной фронтендом.

Справочная статья

PS: Добро пожаловать на обмен и изучение, пожалуйста, укажите на недостатки.