«Внешняя оптимизация» — оптимизация производительности проекта Vue.

интервью Vue.js
«Внешняя оптимизация» — оптимизация производительности проекта Vue.

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

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

1. Вступительные замечания

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

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

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

Во-вторых, оптимизация уровня кода

1. Используйте v-if и v-show, чтобы снизить нагрузку на производительность при инициализации рендеринга и переключении рендеринга.

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

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

Вы часто используете компонент всплывающего окна iview. Компонент всплывающего окна использует v-show для управления своим отображением и скрытием. Затем, когда страница загружается, компонент всплывающего окна (включая содержимое внутри) будет инициализирован и Если на странице есть только один компонент всплывающего окна, это не повлияет на производительность, но если на странице есть десятки компонентов всплывающего окна, повлияет ли это на производительность? Вы можете сделать щелчок правой кнопкой мыши меню, чтобы попробовать.

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

Для этого используется следующий код.

<template>
    <div>
        <Button type="primary" @click.native="add">添加</Button>
        <add v-model="add.show" v-bind="add"></add>
    </div>
</template>
<script>
export default{
    data(){
        return{
            add:{
                show:false,
                init:false
            }
        }
    },
    components:{
        add:() =>import('./add.vue')
    },
    methods:{
        add(){
            this.add.show=true;
            this.add.init=true
        }
    }
}
</script>
<template>
    <div v-if="init">
        <Modal v-model="show" title="添加" @on-cancel="handleClose"></Modal>
    </div>
</template>
<script>
export default{
    props:{
        value:{
            type:Boolean,
            default:false
        },
        init:{
            type:Boolean,
            default:false
        }
    },
    data(){
        return{
            show:false,
        }
    },
    watch:{
        value(val){
            if(val){
                this.show = val;
            }
        }  
    },
    methods:{
        handleClose(val) {
            this.$emit('input', val);
        },
    }
}
</script>

принцип:

v-ifКогда значение привязки равно false, при первоначальном рендерингеНе будуОтрисуйте его условный блок.

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

v-showНезависимо от того, является ли значение привязки истинным или ложным, при первоначальном отображении оно всегдавстречаОтрисуйте его условный блок.

v-showзначение привязки при переключении между истинным и ложным,Не будууничтожить и повторно визуализировать его условный блок, просто используйтеdisplay:nonestyle, чтобы контролировать его отображение и скрытие.

2. Расчеты, часы и методы различают сценарии использования

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

  • computed:
    • На данные влияют несколько данных.
    • Данные должны быть рассчитаны с высокой производительностью. Например, для их получения необходимо пройти через огромный массив и выполнить множество вычислений. В настоящее время их можно получить с помощьюcomputedФункция кэширования , только когда данные, на которые он опирается для своих расчетов, будут пересчитаны, в противном случае кэшированное значение будет возвращено напрямую.
  • watch:
    • Одни данные влияют на несколько данных.
    • При изменении данных необходимо выполнять асинхронные или дорогостоящие операции. Запросить интерфейс, если данные изменяются.
  • methods:
    • Есть надежда, что данные обновляются в режиме реального времени и не нуждаются в кэшировании.

3. Обработайте данные заранее, чтобы решить проблему, что v-if и v-for должны быть на одном уровне.

Потому что, когда Vue обрабатывает директивы,v-forСравниватьv-ifимеет более высокий приоритет, то естьv-ifбудет повторяться отдельно для каждогоv-forв цикле.

Можно рассчитать заранееv-forв данныхv-ifЭлементы данных фильтруются.

//userList.vue
<template>
    <div>
        <div v-for="item in userList" :key="item.id" v-if="item.age > 18">{{ item.name }}</div>
    </div>
</template>
//userList.vue
<template>
    <div>
        <div v-for="item in userComputedList" :key="item.id">{{ item.name }}</div>
    </div>
</template>
export default {
    computed:{
        userComputedList:function(){
            return this.userList.filter(function (item) {
                return item.age > 18
            })
        }
    }
}

Может быть, интервьюерv-forСравниватьv-ifс более высоким приоритетом? Этот вопрос уже затронул принципиальный уровень, если ответить и на этот вопрос, то это добавит много баллов интервью.

Выше сказано "v-ifбудет повторяться отдельно для каждогоv-forВ цикле» этот процесс доступен только при рендеринге страницы, и Vue, наконец, рендерит страницу с помощью функции рендеринга и сначала печатает рендеринг, сгенерированный компиляцией компонента.

//home.vue
<script>
import userList from './userList'
console.log(userList.render)
</script>

Печатное содержание выглядит следующим образом

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    _vm._l(_vm.userList, function(item) {
      return item.age > 18
        ? _c("div", { key: item.id }, [_vm._v(_vm._s(item.name))])
        : _vm._e()
    }),
    0
  )
}
var staticRenderFns = []
render._withStripped = true
export { render, staticRenderFns }

в_lпутьv-forинструкция прошлаgenForМетод renderList, сгенерированный функцией,item.age > 18? даv-ifинструкция прошлаgenIfкод тернарного оператора, сгенерированный функцией,_vЭтот метод представляет собой метод createTextVNode, используемый для создания текстовых узлов,_eСпособ заключается в том, что метод createEmptyVNode используется для создания пустого узла. Ясно ли теперь,v-ifработать на каждомv-forсередина.

В конечном счете, он все еще находится в процессе генерации функции рендеринга, что приводит кv-forСравниватьv-ifС более высоким приоритетом давайте посмотрим на процесс генерации функции рендеринга.

Vue предоставляет две версии: одна Runtime + Compiler, другая только Runtime, первая содержит скомпилированный код, вы можете выполнять процесс компиляции во время выполнения, последняя не содержит скомпилированного кода, вам нужно использовать шаблоны компиляции webpack vue-loader в функции рендеринга заранее.

Мы не изучаем здесь vue-loader, поэтому для изучения используем Runtime + Compiler, а также используем CDN для ознакомления с Vue.js.src/platforms/web/entry-runtime-with-compiler.jsсередина.

const vm = new Vue({
    render: h => h(App)
}).$mount('#app')

Экземпляры Vue создаются через$mountмонтируется на DOM. посмотри в файле ввода$mountметод, найти в его методеrenderполе, нашел следующий код

const { render, staticRenderFns } = compileToFunctions(template, {
    outputSourceRange: process.env.NODE_ENV !== 'production',
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns

Объясните, что функция рендеринга генерируется методом compileToFunctions, а затем найдите, где находится метод compileToFunctions.

Метод compileToFunctions находится вsrc/platforms/web/compiler/index.jsопределено в.

const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }

Метод compileToFunctions генерируется методом createCompiler, и продолжаем искать метод createCompiler.

Метод createCompiler находится вsrc/compiler/index.jsопределено в.

export const createCompiler = createCompilerCreator(
    function baseCompile(template,options) {
        const ast = parse(template.trim(), options)
        if (options.optimize !== false) {
            optimize(ast, options)
        }
        const code = generate(ast, options)
        return {
            ast,
            render: code.render,
            staticRenderFns: code.staticRenderFns
        }
})

Как вы можете видеть в приведенном выше коде, рендеринг — это рендеринг в коде, а код генерируется методом generate.

Вот дополнительное упоминание о том, что такое ast, то есть книга по грамматике, сгенерированная tempalte, выполните следующую логику перед выполнением метода generate

  • Разобрать строку шаблона для генерации astconst ast = parse(template.trim(), options).
  • оптимизировать синтаксическое деревоoptimize(ast, options).
  • Сгенерировать код функции рендерингаconst code = generate(ast, options).

Продолжайте искать метод generate, который находится вsrc/compiler/codegen/index.jsопределено в.

export function generate (ast,options){
    const state = new CodegenState(options)
    const code = ast ? genElement(ast, state) : '_c("div")'
    return {
        render: `with(this){return ${code}}`,
        staticRenderFns: state.staticRenderFns
    }
}

Выясняется, что код генерируется методом genElement, а мы продолжаем искать метод genElement.На самом деле первопричина здесь решена, и приведены несколько строчек кода ключа.

export function genElement(el,state){
    if(){
    //...
    }else if (el.for && !el.forProcessed) {
        return genFor(el, state)
    } else if (el.if && !el.ifProcessed) {
        return genIf(el, state)
    } 
}

Как видно из приведенного выше кода, el.forv-for, эл.если естьv-if, el.for выполняется до решения el.if, поэтомуv-forСравниватьv-ifимеет более высокий приоритет.

Кроме того, в конце метода genFor будет продолжать вызываться метод genElement, формируя поуровневое выполнение.

return `${altHelper || '_l'}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
    `return ${(altGen || genElement)(el, state)}` +
'})'

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

4. Добавьте ключ к элементу цикла v-for, чтобы улучшить скорость расчета различий.

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

  • Почему добавление ключа увеличивает скорость расчета различий.

    После четырех перекрестных сравнений старой головы и новой головы, старого хвоста и нового хвоста, старой головы и нового хвоста, а также старого хвоста и новой головы нет ни одного узла, достойного сравнения. если новый узел имеет ключ. Нижний индекс старого узла, который стоит сравнить, можно получить непосредственно через карту.Если ключа нет, используется тот же метод Vnode, чтобы судить о том, стоит ли сравнивать новый узел и старый узел, путем зацикливания массива старых узлов и нижнего индекса. старого узла возвращается, если это того стоит. По-видимому, быстрее вычислять по карте, чем перебирать массив.

  • Что такое расчет разницы.

    Выполняется, когда срабатывает наблюдатель рендерингаvm._update(vm._render(), hydrating),существуетvm._undataметод будет вызыватьсяvm.__patch__vm.__patch__Указывая на метод patch, вычисление diff относится к началу вызова метода patch, и метод sameVnode используется для оценки того, стоит ли сравнивать узел, и если не стоит напрямую заменять старый узел новым узлом, Это конец. Стоит ввести метод patchVnode для сравнения и обработки нескольких случаев.Если старый и новый узлы имеют текстовые узлы, текстовые узлы под новым узлом напрямую заменяют текстовые узлы под старым узлом.Если новый узел имеет дочерние узлы и старый узел не имеет дочерних узлов, то Непосредственно проверьте новый узел на родителя старого узла.Если у нового узла нет дочерних узлов, а у старого узла есть дочерние узлы, то дочерние узлы под родителем старого узла удален. Если у старого и нового узлов есть дочерние узлы, введите метод updateChildren и сравните старую голову и новую голову, старый хвост и новый хвост, старую голову и новый хвост, а также старый хвост и новую голову четыре раз.Если стоит сравнивать, введите метод patchVnode, если не стоит Для сравнения есть ключ для использования карты для получения старого узла, который стоит сравнивать, и нет ключа для получения старого узла что стоит сравнить, прокачав старый узел. Когда все новые узлы сравниваются, а старые узлы не сравниваются, старые узлы, которые не сравнивались, будут удалены. Когда все старые узлы сравниваются, а новые узлы не сравниваются, новые узлы добавляются к последним сравниваемым новым узлам для завершения вычисления различий.

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

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

Во-первых, давайте представим, что такое вычисление различий. Вычисление различий заключается в сравнении старого и нового виртуального DOM (виртуальный DOM). Виртуальный DOM извлекает реальные данные DOM и имитирует древовидную структуру в виде объектов. Проще говоря, вычисление различий заключается в сравнении двух объектов для сравнения.

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

  • 1. В методе исправления используйте тот же Vnode, чтобы определить, стоит ли сравнивать старый и новый узлы.
  • 2. Если сравнивать не стоит, добавить новый узел непосредственно к родителю старого узла, затем удалить старый узел и выйти из сравнения.
  • 3. Если есть смысл сравнивать, вызываем метод patchVnode.
  • 4. Если старый и новый узлы полностью совпадают, если да, выходим из сравнения.
  • 5. Если нет, найти соответствующий реальный DOM, записать его как эл.
  • 6. Если старый и новый узлы имеют текстовые узлы и не равны, то установите текстовый узел el в качестве текстового узла нового узла и выйдите из сравнения.
  • 7. Если новый узел имеет дочерние узлы, а старый узел не имеет дочерних узлов, дочерние узлы нового узла будут добавлены в el после создания реального DOM, и сравнение будет завершено.
  • 8. Если у нового узла нет дочерних узлов, а у старого узла есть дочерние узлы, удалите дочерние узлы el и выйдите из сравнения.
  • 9. Если новый узел и старый узел имеют дочерние узлы, начните сравнивать их дочерние узлы, используя метод updateChildren.
  • 10. Запишите дочерние узлы старого узла какoldChпредставляет собой массив, голова которогоoldCh[oldStartIdx]записаться какoldStartVnode,oldStartIdxИзначально 0. его хвостoldCh[oldEndIdx]записаться какoldEndVnode,oldEndIdxИзначальноoldCh.length - 1.
  • 11. Запишите дочерние узлы старого узла какnewChпредставляет собой массив, голова которогоnewCh[newStartIdx]записаться какnewStartVnode,newStartIdxИзначально 0. его хвостnewCh[newEndIdx]записаться какnewEndVnode,newEndIdxИзначальноnewCh.length - 1.
  • 12. Используйте тот же Vnode, чтобы решить, стоит ли сравнивать головку старого дочернего узла и головку нового дочернего узла, называемого старой головкой и новой головкой.
  • 13. Если есть смысл сравнить, вызываем метод patchVnode и снова выполняем шаг 3. использовать одновременноoldCh[++oldStartIdx]Чтобы снова получить старый заголовок дочернего узла, используйтеnewCh[++newStartIdx]Обновите заголовок нового дочернего узла.
  • 14. Если сравнивать не стоит, используйте тот же Vnode, чтобы решить, стоит ли сравнивать хвост старого дочернего узла и хвост нового дочернего узла, называемые старым хвостом и новым хвостом.
  • 15. Если стоит сравнить, вызываем метод patchVnode и повторяем шаг 3. использовать одновременноoldCh[--oldEndIdx]Повторно получить хвост старого дочернего узла и повторно использовать его.newCh[--newEndIdx]Получите хвост нового дочернего узла.
  • 16. Если сравнивать не стоит, используйте тот же Vnode, чтобы решить, стоит ли сравнивать голову старого дочернего узла и хвост новой дочерней вершины, называемые старая голова и новый хвост.
  • 17. Если есть смысл сравнить, вызываем метод patchVnode и снова выполняем шаг 3. Заодно ставим голову старого дочернего узлаoldStartVnodeСоответствующий реальный DOM перемещается в конец старого дочернего узла.oldEndVnodeЗа соответствующим реальным DOM. использовать одновременноoldCh[++oldStartIdx]Чтобы снова получить старый заголовок дочернего узла, используйтеnewCh[--newEndIdx]Повторно выбирает хвост нового дочернего узла.
  • 18. Если сравнивать не стоит, используйте тот же Vnode, чтобы решить, заслуживают ли сравнения хвост старого дочернего узла и головка нового дочернего узла, называемые старым хвостом и новой головкой.
  • 19. Если стоит сравнить, вызываем метод patchVnode и повторяем шаг 3. Заодно ставим хвост старой дочерней нодыoldEndVnodeСоответствующий реальный DOM перемещается в голову старого дочернего узла.oldStartVnodeЗа соответствующим реальным DOM. использовать одновременноoldCh[--oldEndIdx]Чтобы снова получить хвост старого дочернего узла, используйтеnewCh[++newStartIdx]Обновите заголовок нового дочернего узла.
  • 20. Если сравнивать не стоит, если у старого дочернего узла есть ключ, можно с помощью метода createKeyToOldIdx получить структуру карты с ключом старого дочернего узла в качестве ключа и его индексом в качестве значения, помеченного какoldKeyToIdx.
  • 21. Если голова нового дочернего узлаnewStartVnodeЕсть ключевой атрибут, напрямую черезoldKeyToIdx[newStartVnode.key]Получить соответствующий индексidxInOld.
  • 22. Если голова нового дочернего узлаnewStartVnodeКлючевой атрибут отсутствует, используйте метод findIdxInOld, чтобы найти индекс, соответствующий старому дочернему узлу, достойному сравнения.idxInOld.
  • 23. После поиска. еслиidxInOldне существует. Затем вызовите метод createElm для генерирования напрямуюnewStartVnodeСоответствующая реальная вставка DOMoldStartVnodeСоответствует передней части реального DOM.
  • 24. ЕслиidxInOldсуществует, используйтеoldCh[idxInOld]Полученный Vnode записывается какvnodeToMoveиnewStartVnodeИспользуйте sameVnode, чтобы определить, стоит ли сравнивать.
  • 25. Если есть смысл сравнить, вызываем метод patchVnode и снова выполняем шаг 3. выполнять одновременноoldCh[idxInOld] = undefined, чтобы избежать повторных сравнений. В то же время будетvnodeToMoveСоответствующий реальный DOM перемещается в голову старого дочернего узла.oldStartVnodeСоответствующий реальный фронт DOM.
  • 26. Если сравнивать не стоит, вызовите метод createElm для прямой генерацииnewStartVnodeСоответствующая реальная вставка DOMoldStartVnodeСоответствует передней части реального DOM.
  • 27. ИспользованиеnewCh[++newStartIdx]Обновить заголовок нового дочернего узла
  • 28. Если устраиваетoldStartIdx <= oldEndIdx && newStartIdx <= newEndIdxПерейдите к шагу 9.
  • 29. ЕслиoldStartIdx > oldEndIdx, указывая на то, что все старые дочерние узлы были сравнены, а оставшиеся несопоставленные новые дочерние узлы вызывают метод createElm для создания соответствующего реального DOM и вставки его вnewCh[newEndIdx + 1]За соответствующим реальным DOM.
  • 30. ЕслиnewStartIdx > newEndIdx, что указывает на то, что все новые дочерние узлы были сравнены, тогда оставшиеся старые дочерние узлы удаляются.

5. Используйте v-once для обработки элементов или компонентов, которые будут отображаться только один раз.

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

Например, страница является типовым договором, большая часть контента в ней получена с сервера и фиксирована, изменится только название, товар, сумма и прочий контент. Тогда ты можешьv-onceДобавленные к тем элементам, которые оборачивают фиксированный контент, при создании нового контракта этот фиксированный контент можно пропустить, а повторно отобразить можно только имя, продукт, сумму и т. д.

иv-ifПри совместном использованииv-onceнедействителен. существуетv-forПри использовании на элементах или компонентах внутри цикла необходимо добавить ключ.

Говоря об этой оптимизации, чтобы интервьюер не спрашивал васv-onceКак визуализировать элемент или компонент только один раз?

Когда дело доходит до рендеринга, он должен быть таким же, какrenderфункция, то вам нужно сгенерироватьrenderфункция поиска ответа.

существуетsrc/compiler/codegen/index.js, найдите метод genElement

else if (el.once && !el.onceProcessed) {若设置v-once,则调用genOnce()函数
    return genOnce(el, state)
 } 

Посмотрите еще раз на метод genOnce

function genOnce(el, state){
    el.onceProcessed = true
    if (el.if && !el.ifProcessed) {//如果有定义了v-if指令
        //...
    } else if (el.staticInFor) {//如果是在v-for下面的元素或组件上
        //...
        return `_o(${genElement(el, state)},${state.onceId++},${key})`
    } else {
        return genStatic(el, state)
    }
}

если определеноv-ifинструкция, еслиv-ifЗначение инструкции не существует, и, наконец, будет вызван метод genStatic. Посмотрите еще раз на метод genStatic

function genStatic(el, state) {
	//...
    state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
    return `_m(${state.staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`
}

где метод _msrc\core\instance\render-helpers\render-static.jsМетод renderStatic в , этот методv-onceКлючом к достижению только одного рендеринга элемента или компонента является.

function renderStatic(index,isInFor){
    const cached = this._staticTrees || (this._staticTrees = [])
    let tree = cached[index]
    if (tree && !isInFor) {
        return tree
    }
    tree = cached[index] = this.$options.staticRenderFns[index].call(this._renderProxy,null,this)
 	return tree
}

вcachedременьv-onceКэш виртуального DOM-узла, сгенерированный рендерингом элемента или компонента, если кеш виртуального DOM-узла существует, а виртуальный DOM-узел не находится вv-forнапрямую вернуть кеш узла виртуального DOM, если узел виртуального DOM не кэширован, вызовитеgenStaticсуществует в методеstaticRenderFnsФункция рендеринга в массиве, которая рендерит виртуальный узел DOM и существуетcached, так что виртуальный узел DOM будет возвращен напрямую без повторного рендеринга в следующий раз, и вызов в то же времяmarkOnceМетод добавляет виртуальный узел DOMisOnceфлаг, значениеtrue.

если определеноv-for, который в конечном итоге вызовет_o(${genElement(el, state)},${state.onceId++},${key})_oПуть этоsrc\core\instance\render-helpers\render-static.jsМетод markOnce в , его роль заключается в добавлении к сгенерированному виртуальному узлу DOMisOnceФлаг, если правда, то это означает, что виртуальный DOM-узел является статическим узлом, при патчинге он будет судитьvnode.isOnceЭтоtrue,заtrue, старый узел возвращается напрямую без сравнения, что эквивалентно однократному рендерингу.

6. Используйте Object.freeze(), чтобы заморозить данные, которые не требуют оперативных изменений.

Vue初始化过程中,会把data传入observe函数中进行数据劫持,把data中的数据都转换成响应式的。

Вызовите функцию defineReactive внутри функции наблюдения, чтобы обработать данные, настроить свойства геттера/сеттера и преобразовать их в адаптивные, если вы используетеObject.freeze()Заморозить некоторые данные в data, то есть установить его настраиваемое свойство (configurable) в false.

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

export function defineReactive(obj,key,val,customSetter,shallow){
    //...
    const property = Object.getOwnPropertyDescriptor(obj, key)//获取obj[key]的属性
    if (property && property.configurable === false) {
        return
    }
    //...
}

Если вы столкнулись с данными, которые не требуют оперативных изменений в проекте, вы можете использоватьObject.freeze()Если данные заморожены, шаг перехвата данных при инициализации можно пропустить, что значительно повышает начальную скорость рендеринга.

7. Заранее отфильтруйте ненужные данные и оптимизируйте структуру данных в опции данных

Когда Vue инициализируется, данные опции будут переданы в функцию наблюдения для перехвата данных.

initData(vm){
    let data = vm.$options.data
    //...
    observe(data, true)
}

В функции наблюдения будет вызываться

observe(value,asRootData){
   //...
   ob = new Observer(value);
}

Функция defineReactive в прототипе Observer обрабатывает данные, настраивает свойства геттера/сеттера и преобразует их в реактивные.

walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i])
    }
}

В функции defineReactive значение данных будет снова передано в функцию наблюдения.

export function defineReactive(obj,key,val,customSetter,shallow){
    //...
    if (arguments.length === 2) {
        val = obj[key]
    }
    let childOb = observe(val);
    //...
}

В функции наблюдения есть фрагмент кода, который передает данные в класс Observer.

export function observe(value,asRootData){
  //...
  ob = new Observer(value)
  //...
  return ob
}

Вышеприведенное представляет собой рекурсивный вызов.

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

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

8. Избегайте чтения данных типа массива в данных в цикле v-for

export function defineReactive(obj,key,val,customSetter,shallow){
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)
    const getter = property && property.get;
    const setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key]
    }
    let childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
            dep.depend()
            if (childOb) {
                childOb.dep.depend()
                if (Array.isArray(value)) {
                    dependArray(value)
                }
            }
        }
        return value
    }
    })
}
function dependArray (value: Array<any>) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i]
        e && e.__ob__ && e.__ob__.dep.depend()
        if (Array.isArray(e)) {
            dependArray(e)
        }
    }
}
export function observe (value, asRootData){
  	if (!isObject(value) || value instanceof VNode) {
    	return
  	}
  	//...
}

Почему следует избегать чтения данных типа массива в data в цикле v-for, ведь в захвате данных будет вызываться функция defineReactive. Поскольку геттеры являются функциями и относятся кdep,childOb, образуя замыкание, поэтомуdep,childObТакже существует в памяти (функция GETTER для каждых данных),depЭто зависимый контейнер сбора для всех данных.childObданные после реактивной обработки.

В процессе рендеринга представлений, использования наблюдения и использования вычисляемых свойств чтение данных будетDep.targetНазначьте значение Watcher (зависимость), например, при чтении данных во время рендеринга представления,Dep.targetдля рендервотчера.

Тогда позвониdep.depend()Собрать зависимости для себя, если val (значение самого себя) не является объектом, тоchildObявляется ложным. Если val (значение самого себя) является объектом, используйтеchildOb.dep.depend()Соберите зависимости, если значение (значение самого себя) является массивом, используйтеdependArray(value)Рекурсивно каждый элемент для сбора зависимостей.

Причина, по которой вам следует избегать чтения данных типа массива в data в цикле v-for, заключается в том, чтоЕсли значение (собственное значение) является массивом, используйтеdependArray(value)Рекурсия каждого элемента для сбора зависимостей

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

<template>
    <div class="g-table-content">
        <el-table :data="tableData">
            <el-table-column prop="carno" label="车牌号"></el-table-column>
            <el-table-column prop="cartype" label="车型"></el-table-column>
            <el-table-column label="驾驶员">
                <template slot-scope="{row,column,$index}">
                    <el-input v-model="driverList[$index].name"></el-input>
                </template>
            </el-table-column>
            <el-table-column label="电话">
                <template slot-scope="{row,column,$index}">
                    <el-input v-model="driverList[$index].phone"></el-input>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

Предположим, что в таблице содержится 500 фрагментов данных, затем прочитайте список драйверов в общей сложности 500 раз, каждый раз, когда читается список драйверов, он войдет вdependArray(value), всего требуется 500 * 500 = 250 000 циклов.Если есть пейджинг, каждый раз, когда переключается номер страницы, он будет циклически повторяться не менее 250 000 раз.

Если мы выполним следующую предварительную обработку после получения данных от службы, назначим ееthis.tableData, как бы это было?

res.data.forEach(item =>{
    item.name='';
    item.phone='';
})

Шаблон делает это

<template>
    <div class="g-table-content">
        <el-table :data="tableData">
            <el-table-column prop="carno" label="车牌号"></el-table-column>
            <el-table-column prop="cartype" label="车型"></el-table-column>
            <el-table-column label="驾驶员">
                <template slot-scope="{row}">
                    <el-input v-model="row.name"></el-input>
                </template>
            </el-table-column>
            <el-table-column label="电话">
                <template slot-scope="{row,column,$index}">
                    <el-input v-model="row.phone"></el-input>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

Также можно реализовать требования, и они не будут входить в оценку в процессе рендеринга.dependArray(value), это не вызовет 250 000 ненужных циклов. Значительно улучшена производительность.

9. Защита от сотрясения и дросселирования

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

  • Anti-shake: событие будет выполнено только один раз в течение указанного времени после запуска события. Проще говоря, он предназначен для предотвращения дрожания рук, и он использовался много раз за короткий промежуток времени.
  • Регулирование: событие выполняется только один раз в течение указанного времени.
  • Сценарий приложения: дросселирование Независимо от того, срабатывает ли событие или срабатывает часто, в течение заданного времени будет выполняться только одно событие, в то время как анти-встряска означает, что событие срабатывает в течение заданного времени, а событие выполняется только тогда, когда оно срабатывает в последний раз. Если событие необходимо выполнять периодически, но другие операции также вызывают его выполнение, в этом сценарии можно использовать регулирование. Если событие не нужно выполнять регулярно, его нужно инициировать для выполнения и нельзя выполнять несколько раз за короткий период времени, в этом сценарии можно использовать защиту от сотрясений.

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

import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
export default{
	methods:{
    	a: debounce(function (){
        	//...
        },200,{
            'leading': false,
            'trailing': true
        }),
        b: throttle(function (){
        	//...
        },200,{
            'leading': false,
            'trailing': true
        })
    }
}
  • debounce(func, [wait=0], [options={}])Создайте функцию устранения дребезга, которая вызывает метод func после задержки ожидания в миллисекундах с момента последнего вызова. Возвращает функцию устранения дребезга debounceFn,debounce.cancelОтмените анти-встряску,debounce.flushФункция вызывается немедленно.

    • options.leadingПри значении true функция func вызывается до начала задержки.
    • options.trailingПри значении true функция func вызывается после начала и окончания задержки.
    • options.maxWaitУстанавливает максимальное значение задержки функции.
  • throttle(func, [wait=0], [options={}])Создайте функцию дросселя, которая выполняет func не более одного раза в секундах ожидания. Возвращает функцию дроссельной заслонкиtrolFn,throttleFn.cancelотменить дросселирование,throttleFn.flushФункция вызывается немедленно.

    • options.leadingЕсли установлено значение true, функция func вызывается перед началом регулирования.
    • options.trailingПри значении true функция func вызывается после завершения регулирования.
    • leadingиtrailingОба верны, функция func вызывается несколько раз во время ожидания.

10. Оптимизация размера изображения и отложенная загрузка

Что касается оптимизации размера изображения, вы можете использовать image-webpack-loader для сжатия изображений и настройки их в плагине webpack.Подробности см. в этом пункте этой статьи.

Что касается отложенной загрузки изображений, ее можно реализовать с помощью плагина vue-lazyload.

Выполнение заказаnpm install vue-lazyload --saveУстановите плагин vue-lazyload. Введите конфигурацию в main.js

import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {
  preLoad: 1.3,//预载高度比例
  error: 'dist/error.png',//加载失败显示图片
  loading: 'dist/loading.gif',//加载过程中显示图片
  attempt: 1,//尝试次数
})

использовать в проекте

<img v-lazy="/static/img/1.png">

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

import Vue from 'vue'
import App from './App.vue'
new Vue({
    render: h => h(App)
}).$mount('#app')

Если функция рендеринга в опции Vue существует, конструктор Vue не будет компилировать функцию рендеринга из шаблона HTML, извлеченного из опции шаблона, или элемента монтирования, указанного в опции el.

То есть при рендеринге он будет заменен непосредственно содержимым, визуализируемым с помощью рендеринга.<div id="app"></div>.

У проекта Vue есть недостаток: при первом рендеринге какое-то время будет белый экран. Причина в том, что во время первого рендеринга необходимо загрузить кучу ресурсов, таких как js, css и изображения. Множество стратегий оптимизации, конечной целью является повышение скорости загрузки этих ресурсов. Однако, если сеть работает медленно, независимо от того, максимально ли она оптимизирована или для загрузки требуется определенное время, появится белый экран.

Первая загрузка — это страница index.html, на которой нет контента, и появится белый экран. если<div id="app"></div>Если в нем есть контент, белого экрана не будет. так что мы можем<div id="app"></div>Добавьте статическую страницу вверху сгиба. Когда загрузится реальный первый экран, он поместит<div id="app"></div>Эта структура заменяется, что дает визуальную ошибку, и белый экран не создается.

11. Внедрение библиотек компонентов по запросу

Метод введения библиотеки компонентов по запросу будет представлен в общих документах.

Например, библиотеку элементов пользовательского интерфейса, используйте подключаемый модуль babel-plugin-component для импорта по запросу.

Выполнение заказаnpm install babel-plugin-component --save-dev, установите плагин.

Настройте файл .babelrc.js в корневом каталоге следующим образом.

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

вlibraryNameимя библиотеки компонентов,styleLibraryNameИмя папки, в которой хранятся стили после упаковки библиотеки компонентов. Его можно импортировать по запросу в main.js.

import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)

Фактически, подключаемый модуль babel-plugin-component представляет собой конкретное использование пользовательского интерфейса элемента после преобразования элемента с помощью подключаемого модуля babel-plugin-import. Общая библиотека компонентов по-прежнему является плагином babel-plugin-import для реализации импорта по требованию.

Выполнение заказаnpm install babel-plugin-import --save-dev, установите плагин.

Настройте файл .babelrc.js в корневом каталоге следующим образом.

{
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}

вlibraryNameимя библиотеки компонентов,libraryDirectoryУказывает имя папки, в которой находится основной файл записи или файл записи модуля package.json подчиненной библиотеки, в противном случае по умолчанию используется lib.

Представляемstyleперед настройкой опции. Давайте взглянем на структуру и содержимое сгенерированных файлов после упаковки библиотеки компонентов Vant.

Содержимое файла index.js выглядит следующим образом.

Содержимое файла less.js выглядит следующим образом.

styleЕсли задано значение true, index.js в соответствующем файле стиля будет импортирован в проект по мере необходимости.

styleКогда это css, less.js в соответствующем файле стиля будет внедряться в проект по мере необходимости.

styleДля функции babel-plugin-import автоматически импортирует файл, путь к файлу которого совпадает с возвращаемым значением функции. Это полезно для разработчиков библиотек компонентов. Вы можете посмотреть мою другую статьюVue CLI3 создает библиотеку компонентов и реализует введение реальных боевых действий по запросу..

3. Оптимизация упаковки проекта

Прежде чем говорить об этом, давайте уточним, что такое упаковка. Вообще говоря, это упаковать проект в ресурсы, такие как файлы js, файлы css и т. д., и, наконец, представить его в файле index.html Вы можете посмотреть index.html в папке dist проекта.

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

  • js и css коды импортируются по мере необходимости.
  • js, извлечение общего кода кода css.
  • Минимальное сжатие кода js и css.
  • Сжатие ресурсов изображений.

Теперь проект упакован с помощью Webpack и его можно оптимизировать, настроив Webpack.

Если ваш проект Vue создан с помощью Vue Cli3, вы можете создать новый в корневом каталоге.vue.config.jsфайл, в котором Webpack настроен для оптимизации этих ресурсов.

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

Естественно сравнивать до и после оптимизации.Сначала установите плагин webpack-bundle-analyzer, который поможет визуально проанализировать размер каждого запакованного ресурса.

npm install webpack-bundle-analyzer --save-dev

существуетvue.config.jsПредставьте этот плагин

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
    configureWebpack:config =>{
        return {
            plugins:[
                new BundleAnalyzerPlugin()
            ]
        }
    }
}

Выполнение заказаnpm run build, в браузере откроется график анализа пакетов, как показано на следующем рисунке.

1. Используйте import() для асинхронного импорта компонентов для импорта по требованию.

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

Просто поищите в интернете, ленивую загрузку можно сделать и черезresolve =>require(['需要加载组件的地址'],resolve)реализовать.

component: () =>import('views/home.vue'),
component: resolve =>require(['views/home.vue'],resolve)

но сresolve =>require(['需要加载组件的地址'],resolve)Для асинхронного внедрения компонентов после упаковки через Webpack4 обнаружено, что код всех компонентов запакован в js файл, что не соответствует ожиданиям Ожидается, что код каждого компонента должен быть запакован в соответствующий js файл файл, который будет загружаться при загрузке компонента.js файл, это ленивая загрузка.

использоватьimport()После того, как компонент будет введен асинхронно, выполните командуnpm run buildПосле этого взгляните на диаграмму анализа упаковки.

После сравнения было обнаружено, что исходный js-файл размером 1,42 МБ отсутствует и разбит на множество небольших js-файлов, таких как 32,55 КБ и 31,69 КБ. Эти небольшие файлы js будут загружаться только при загрузке соответствующего компонента, что является ленивой загрузкой.

Возможно, вы почувствуете замешательство, глядя на эти имена файлов js, и не сможете сопоставить компоненты в проекте один за другим.Сейчас я научу вас небольшому трюку.webpackChunkName: Имя файла фрагмента. [запрос] представляет фактическое имя проанализированного файла.

function load(component) {
    return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}

Выполнение заказаnpm run build, взгляните на график анализа упаковки.

Файл js в красном поле на рисунке упакован компонентом views/flow_card_manage/flow_card_list/index.vue.

Откройте проект в браузере, используйте F12, чтобы захватить пакет, и найдите его.flow_card_manage-flow_card_list.67de1ef8.jsэтот файл. Когда он находится на домашней странице, он еще не загружен в этот компонент маршрутизации. Этот js-файл загружен, только что предварительно выбран, и содержимое не возвращается. Цель состоит в том, чтобы указать браузеру загружать этот js-файл для меня, когда он не используется. Пока компонент маршрутизации не будет фактически загружен, файл js загружается снова. Если браузер уже загружен, содержимое будет возвращено напрямую. Если браузер не загрузился, он отправится на сервер, чтобы запросить файл js и вернуть содержание. Это ленивая загрузка, загрузка по требованию. принцип:Посмотрите мою другую статью🚩Четырехлетний фронтенд позволяет понять принцип ленивой загрузки роутинга

2. Используйте внешние ресурсы для извлечения сторонних зависимостей и внедрения их с помощью CDN.

Из диаграммы анализа упаковки видно, что два файла chunk-vendors.js и chunk-80f6b79a.js по-прежнему очень велики. Эти два файла содержат сторонние зависимые ресурсы, такие как element-ui, jquery и xlsx.

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

Подробности смотрите в другой моей статьеПодробное объяснение внешнего использования Webpack.

Выполнение заказаnpm run build, взгляните на график анализа упаковки.Размеры файлов chunk-vendors.js и chunk-80f6b79a.js были значительно уменьшены по сравнению с предыдущими версиями.

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

3. Используйте плагин SplitChunks для извлечения общедоступного кода js и разделения кода js.

После упаковки с помощью Webpack остается много ресурсов, которые повторно упаковываются в каждый файл js, которые можно дополнительно оптимизировать с помощью плагина SplitChunks, чтобы уменьшить общий размер упакованных файлов.

Кроме того, CDN является третьей стороной, которая работает нестабильно, если вдруг CDN зависнет, то система рухнет, и есть определенный риск. Также может быть реализовано с помощью плагина SplitChunks.externalsЭффект конфигурации, сторонние зависимости по-прежнему находятся на их собственных серверах, что снижает риски.

Подробности смотрите в другой моей статьеПодробное использование плагина Webpack SplitChunks

4. Используйте плагин MiniCssExtractPlugin для извлечения стилей css.

В проекте Vue, созданном с помощью Vue Cli3, используетсяcss.extractчтобы контролировать, включен ли плагин MiniCssExtractPlugin, хотя и в производственной среде,css.extractПо умолчаниюtrue, то есть подключаемый модуль MiniCssExtractPlugin включен. Но вам все равно нужно быть знакомым с использованием плагина MiniCssExtractPlugin, если интервьюер спросит подробно.

Подробности смотрите в другой моей статьеПодробное использование плагина Webpack MiniCssExtractPlugin.

5. Используйте плагин OptimizeCssnanoPlugin для сжатия и дедупликации файлов стилей css.

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

Вот как использовать этот плагин. Сначала установите плагин OptimizeCssnanoPlugin.

cnpm install --save-dev @intervolga/optimize-cssnano-plugin

существуетvue.config.jsнастроено так

const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
module.exports={
    configureWebpack:config =>{
        return {
            plugins:[
                new OptimizeCssnanoPlugin({
                    sourceMap: false,
                    cssnanoOptions: {
                        preset: [
                            'default',
                            {
                              mergeLonghand: false,
                              cssDeclarationSorter: false
                            }
                        ]
                    },
                }),
            ]
        }
    }
}

Среди них можно увидеть конфигурацию cssnanoOptions.здесь.

mergeLonghand:false, Указывает, что закрывающие свойства, такие как поля, отступы и границы, объединены, как стиль CSS.

.box {
    margin-top: 10px;
    margin-right: 20px;
    margin-bottom: 10px;
    margin-left: 20px;
}
//压缩后
.box {
    margin: 10px 20px;
}

cssDeclarationSorter:false, что означает отключение сортировки CSS по имени свойства.

body {
   animation: none;
   color: #C55;
   border: 0;
}
//压缩后
body {
   animation: none;
   border: 0;
   color: #C55;
}

6. Откройте файлOptimize.minimize, чтобы сжать код js.

optimization.minimizeопция имеет два значенияtrueиfalse,заtrueОткройте сжатый код js, дляfalseОтключите сжатый код js.

По умолчанию в производствеtrue, который в среде разработки по умолчанию равенfalse.

Если вам не нужно отлаживать код в среде разработки, вы также можете установить его вtrueДля сжатия кода js и повышения скорости загрузки страницы.

существуетvue.config.jsнастроено так

module.exports={
    configureWebpack:config =>{
        return {
            optimization:{
                minimize: true
            }
        }
    }
}

В Vue Cli3 для сжатия js-кода по умолчанию используется плагин TerserPlugin, и конфигурация уже оптимальна.

Если вы хотите использовать другие плагины для сжатия кода js, вы можетеoptimization.minimizeroption, значением которого является массив.

Для добавления используйте chainWebpack, где WebpackPlugin — имя плагина, а args — параметр плагина.

const WebpackPlugin = require(插件名称)
module.exports = {
    chainWebpack: config =>{
        config.optimization
            .minimizer(name)
            .use(WebpackPlugin, args)
    },
}

7. Используйте image-webpack-loader для сжатия изображений

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

Оптимизируйте использование image-webpack-loader для сжатия изображений, а затем обрабатывайте их в url-loader и file-loader.

Загрузчик для обработки изображений был настроен в Vue Cli3, чтобы изменить его, смотрите мою другую статью для деталей.Подробное объяснение конфигурации загрузчика Webpack.

Сначала установите image-webpack-loader

cnpm install image-webpack-loader --save-dev

затем вvue.config.jsнастроено так

module.exports = {
    chainWebpack: config =>{
        config.module
            .rule('images')
            .use('imageWebpackLoader')
            .loader('image-webpack-loader')
    },
}

Перед добавлением image-webpack-loader изображение homeBg.png после упаковки выглядит следующим образом

После добавления image-webpack-loader упакованное изображение homeBg.png выглядит следующим образом:

Видно, что размер изображения уменьшился с 251 КБ до 110 КБ, и эффект оптимизации очевиден.

image-webpack-loaderОн поддерживает сжатие изображений PNG, JPEG, GIF, SVG и WEBP Ниже приведены наиболее часто используемые параметры.

  • bypassOnDebug ture/false, По умолчаниюfalse,заtrueОтключить сжатые изображения, когдаwebpack@1.xиспользуется в.

  • disable ture/false, По умолчаниюfalse,заtrueОтключить сжатые изображения, когдаwebpack@2.xи более поздние версии. Сжатые изображения можно отключить в среде разработки, чтобы ускорить их компиляцию.

    module.exports = {
        chainWebpack: config =>{
            config.module
                .rule('images')
                .use('imageWebpackLoader')
                .loader('image-webpack-loader')
                .options({
                    disable: process.env.NODE_ENV === 'development',
                })
        },
    }
    
  • mozjpeg: управляет конфигурацией сжатых изображений JPEG, включено по умолчанию. Значение параметра является объектом, а часто используемые подпараметры:

    • качество Качество сжатия в диапазоне от 0 (наихудшее) до 100 (наилучшее).
  • optipng: управляет конфигурацией сжатых изображений PNG, включено по умолчанию. Значение параметра является объектом, а часто используемые подпараметры:

    • Уровень оптимизации OptimizationLevel, выберите уровень оптимизации от 0 до 7, чем выше значение, тем лучше качество сжатия, но ниже скорость, по умолчанию 3.
  • pngquant: управляет конфигурацией сжатых изображений PNG, включено по умолчанию. Значение параметра является объектом, а часто используемые подпараметры:

    • скорость сжатия, от 1 до 11, чем выше значение, тем выше скорость сжатия, значение по умолчанию равно 4. Значение 10 снижает качество на 5%, но в 8 раз быстрее, чем по умолчанию.
    • качество качества сжатия, значение представляет собой массив, например[0 , 1], минимальное значение – 0 (наихудшее), а максимальное – 1 (идеальное).
  • GIFSicle: управляет конфигурацией сжатого изображения GIF, включено по умолчанию. Значением параметра является объект, а общими подпараметрами являются: -Уровень оптимизации OptimizationLevel, выберите оптимизированный уровень от 1 до 3, оптимизированный уровень определения степени оптимизации; более высокие уровни занимают больше времени, но могут быть лучшие результаты.

  • WebP: Сжатие изображений JPG и PNG - WebP, не включена по умолчанию, после включения необходимости настроить. При включении изображения JPG и PNG могут быть сжатыми меньшими размерами вывода изображений, но по сравнению с Mozjpeg, Optipng, сжатие PNGQuant - это больше времени, повлияет на скорость пакета компилятора, чтобы быть их собственным выбором.

    Значение параметра — это объект, а часто используемые подпараметры — это

    • качество Коэффициент качества, устанавливаемый между 0 и 100, по умолчанию 75, чем выше значение, тем лучше качество.
    • Независимо от того, является ли lossless сжатием без потерь, значение по умолчанию равно false, если true, сжатие без потерь включено.
    • NearLossless использует дополнительный шаг предварительной обработки с потерями для кодирования без потерь с коэффициентом качества от 0 (максимальная предварительная обработка) до 100 (равно без потерь).
module.exports = {
    chainWebpack: config =>{
        config.module
            .rule('images')
            .use('imageWebpackLoader')
            .loader('image-webpack-loader')
            .options({
                disable: process.env.NODE_ENV === 'development',
                mozjpeg:{
                    quality:75
                },
                optipng:{
                    OptimizationLevel:3
                },
                pngquant:{
                    speed:4,
                    quality:[0.2,0.5]
                },
                gifsicle:{
                    OptimizationLevel:1
                },
                webp:{
                    quality:75,
                    lossless:true,
                    nearLossless:75
                }
            })
    },
}

В-четвертых, оптимизация развертывания проекта

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

1. Определите, включено ли сжатие gzip

Это очень просто, просто посмотрите на заголовки ответов (Response headers), чтобы увидеть, есть ли какие-либоContent-Encoding: gzipЭтого атрибута достаточно, есть представитель, у которого включено сжатие gzip.

2. Включите сжатие gzip на Nginx.

Настраивается в nginx/conf/nginx.conf

http {
    gzip  on;
    gzip_min_length 1k;
    gzip_comp_level 5;
    gzip_types application/javascript image/png image/gif image/jpeg text/css text/plain;
    gzip_buffers 4 4k;
    gzip_http_version 1.1;
    gzip_vary on;
}
  • gzip: on | off , по умолчанию выключено, on — включить gzip, off — отключить gzip.
  • gzip_min_length: число, начальная точка сжатия, размер файла больше, чем размер до сжатия, единица измерения по умолчанию — байты, а k также может использоваться для представления килобайтов.
  • gzip_comp_level: Уровень сжатия, от 1 до 9, чем больше число, тем меньше размер сжатого файла, тем больше загружен ЦП и тем больше времени это занимает.
  • gzip_types: Тип файла для сжатия. Тип Перейдите к заголовкам ответа, чтобы увидеть свойство Content-Type.
  • gzip_buffers: числовой размер, задайте системе получение нескольких единиц кэша для хранения потока данных результата сжатия gzip.

Например, 4 4 КБ означает, что единица измерения равна 4 КБ, а память применяется в 4 раза больше единицы 4 КБ в соответствии с исходным размером данных. Если исходный размер данных составляет 17 КБ, подайте заявку на (17/4)*4 = 17 КБ памяти.

  • gzip_http_version: Установите версию протокола HTTP или более позднюю для сжатия gzip.
  • gzip_vary: on | off, добавлять ли Vary:Accept-Encoding в заголовок http, on значит добавлять. Vary:Accept-Encoding указывает прокси-серверу кэшировать две версии ресурса: сжатую и несжатую, чтобы избежать браузера, который не поддерживает сжатые ресурсы, но сначала запрашивает сервер, сервер кэширует несжатые ресурсы, а затем браузер поддерживает сжатые ресурсы , а затем запросите сервер, результатом будет несжатый ресурс, но чтобы снова его распаковать, результатом будет ошибка. Поэтому рекомендуется включить его.

Перед включением сжатия gzipПосле включения сжатия gzip

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

3. Включите сжатие gzip в Webpack.

Используйте плагин CompressionWebpack для сжатия gzip.

Сначала установите плагин CompressionWebpack.

npm install compression-webpack-plugin --save-dev

затем вvue.config.jsнастроено так

const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
    configureWebpack: config =>{
        return {
            plugins: [
                new CompressionPlugin()
            ],
        }
    }
}

воплощать в жизньnpm run buildПосле команды открытьdistфайл, вы найдете много других файлов с тем же именем, но один из файлов имеет суффикс.gz, который представляет собой файл, сжатый с помощью gzip.

4. Разница между сжатием Nginx и Webpack

  • Независимо от сжатия Nginx или Webpack, в Nginx должно быть включено сжатие gzip, иначе браузер все равно будет загружать несжатые ресурсы.

    Вы также можете добавить его в Nginxgzip_static on;Конфигурация.gzip_staticПосле включения, когда браузер запрашивает ресурс, Nginx сначала проверяет, существует ли имя ресурса и есть ли суффикс..gzЕсли есть какой-либо файл, он напрямую вернет содержимое gz-файла, что может помешать Nginx выполнять сжатие gzip для ресурса и тратить ресурсы ЦП сервера.

  • Сжатие с помощью Nginx будет занимать ЦП сервера.Каждый раз, когда браузер запрашивает ресурс, Nginx будет сжимать ресурс в режиме реального времени и возвращать ресурс после завершения сжатия.Слишком долго, что приведет к плохому взаимодействию с пользователем.

  • Использование Webpack увеличит время упаковки. Однако при сжатии плагином CompressionPlugin будет кеш, что может относительно сократить время упаковки.

  • Рекомендуется включить сжатие как для Nginx, так и для сжатия Webpack и добавить его в Nginx.gzip_static on;Конфигурация сервера может уменьшить использование центрального процессора сервера.Конечно, ее следует выбирать в соответствии с реальной ситуацией проекта.

5. Подробное объяснение параметров плагина CompressionPlugin

  • test: String|RegExp|Array,название ресурсаТолько те, которые удовлетворяют условиям, будут сжаты, а значение по умолчанию не определено, то есть все удовлетворяют, например, до тех пор, пока файл js сжат
    plugins: [
        new CompressionPlugin({
            test: /\.js(\?.*)?$/i,
        })
    ],
    
  • include: String|RegExp|Array,название ресурсаТолько те, которые соответствуют условиям, будут сжаты, значение по умолчанию не определено, что находится вtestПараметры фильтруются в пределах диапазона, удовлетворяющегоtestпараметрические условия и удовлетворятьincludeРесурс условия параметра будет сжат.
  • exclude: String|RegExp|Array, исключено из сжатияназвание ресурсаПодходящие ресурсы, значение по умолчанию не определено, находятся вtestИсключено в рамках параметра, удовлетворяющегоtestУсловие параметра не выполняетсяexcludeРесурс условия параметра будет сжат.
  • algorithm: Алгоритм/функция сжатия, по умолчанию gzip, в основном не меняются.
  • compressionOptions,правильноalgorithmНастройка параметра функции сжатия, выбранная параметром, обычно используется для установки уровня сжатия, от 1 до 9, чем больше число, тем меньше размер сжатия, тем больше занят ЦП и тем больше времени это занимает.
    plugins: [
        new CompressionPlugin({
            compressionOptions: { level: 1 },
        })
    ],
    
  • threshold: Число, установите минимальный размер сжатого ресурса в байтах. По умолчанию 0.
  • minRatio: Число, установите степень сжатия, степень сжатия = размер сжатого ресурса/сжатого ресурса, и только ресурс меньше степени сжатия будет сжат. иthresholdПараметр представляет собой отношение «и».
  • filename: Тип: String|Function, задайте имя сжатого ресурса, значение по умолчанию: [путь].gz[запрос], [file] заменяется исходным именем файла актива. [путь] заменяется путем к исходному ресурсу. [dir] заменяется каталогом исходного актива. [имя] заменяется именем файла исходного актива. [ext] заменяется расширением исходного актива. [запрос] заменяется запросом.

Следующие функции используются для вывода значений каждого типа.

 new CompressionPlugin({
    filename(info) {
        console.log(info)
        return `${info.path}.gz${info.query}`;
    },
})

  • deleteOriginalAssets: логическое значение, по умолчаниюfalse,заtrueудалить исходный файл ресурсов. Установка не рекомендуется.
  • cache: Boolean|String, по умолчаниюtrue,заtrue, кэширование файлов включено. Путь по умолчанию к каталогу кеша:node_modules/.cache/compression-webpack-plugin. Когда значение равно String. Включите кеширование файлов и укажите путь к каталогу кеша.
new CompressionPlugin({
      cache: 'path/to/cache',
}),