Текст на 10 000 слов! Обобщить методы и принципы оптимизации производительности Vue.

Vue.js

предисловие

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

использоватьv-slot:slotName, вместоslot="slotName"

v-slotЭто новый синтаксис версии 2.6. Подробности см. здесь:Vue2.6, 2.6 вышла почти два года назад, но многие до сих пор её используютslot="slotName"этот синтаксис. Хотя эти две грамматики могут достичь одного и того же эффекта, внутренняя логика действительно отличается.Давайте посмотрим на различия между двумя методами.

Давайте сначала посмотрим, во что компилируются эти два синтаксиса:

Используя новую нотацию, для следующего шаблона в родительском компоненте:

<child>
  <template v-slot:name>{{name}}</template>
</child>

будет скомпилирован в:

function render() {
  with (this) {
    return _c('child', {
      scopedSlots: _u([
        {
          key: 'name',
          fn: function () {
            return [_v(_s(name))]
          },
          proxy: true
        }
      ])
    })
  }
}

Используя старую нотацию, для следующих шаблонов:

<child>
  <template slot="name">{{name}}</template>
</child>

будет скомпилирован в:

function render() {
  with (this) {
    return _c(
      'child',
      [
        _c(
          'template',
          {
            slot: 'name'
          },
          [_v(_s(name))]
        )
      ],
      2
    )
  }
}

Из скомпилированного кода можно узнать, чтоСтарый способ написания состоит в том, чтобы визуализировать содержимое слота как дочерний элемент, который будет создан в функции рендеринга родительского компонента, а зависимости содержимого слота будут собраны родительским компонентом (отношение имени собирается к наблюдатель рендеринга родительского компонента), а новый метод записи Поместить содержимое слота в scopedSlots, он будет вызываться в функции рендеринга субкомпонента, а зависимости контента слота будут собираться субкомпонентом (деп имени собирается наблюдателю рендеринга подкомпонента), окончательный результат таков: когда мы изменяем атрибут name, старый способ написания состоит в том, чтобы вызывать обновление родительского компонента (вызывать наблюдатель за отрисовкой родительского компонента), а затем вызывать обновление дочернего компонента во время обновления. родительского компонента (prePatch => updateChildComponent), а новый метод записи заключается в непосредственном вызове обновления дочернего компонента (вызов наблюдателя за отрисовкой дочернего компонента).

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

Использование вычисляемых свойств

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

<template>
  <div>{{superCount}}</div>
</template>
<script>
  export default {
    data() {
      return {
        count: 1
      }
    },
    computed: {
      superCount() {
        let superCount = this.count
        // 假设这里有个复杂的计算
        for (let i = 0; i < 10000; i++) {
          superCount++
        }
        return superCount
      }
    }
  }
</script>

В этом примере доступ к свойству superCount осуществляется при создании, подключении и шаблоне. Из этих трех посещений только первоеcreatedSuperCount будет оцениваться только тогда, когда атрибут count не изменился. Два других раза возвращаются непосредственно к кэшированному значению. Более подробное знакомство с вычисляемым атрибутом см. в статье, которую я написал ранее:Как реализованы вычисления Vue?.

Используйте функциональные компоненты

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

Мы можем написать простую демонстрацию, чтобы проверить эту оптимизацию:

// UserProfile.vue
<template>
  <div class="user-profile">{{ name }}</div>
</template>

<script>
  export default {
    props: ['name'],
    data() {
      return {}
    },
    methods: {}
  }
</script>
<style scoped></style>

// App.vue
<template>
  <div id="app">
    <UserProfile v-for="item in list" :key="item" :name="item" />
  </div>
</template>

<script>
  import UserProfile from './components/UserProfile'

  export default {
    name: 'App',
    components: { UserProfile },
    data() {
      return {
        list: Array(500)
          .fill(null)
          .map((_, idx) => 'Test' + idx)
      }
    },
    beforeMount() {
      this.start = Date.now()
    },
    mounted() {
      console.log('用时:', Date.now() - this.start)
    }
  }
</script>

<style></style>

Компонент UserProfile отображает только имя реквизита, а затем вызывает его 500 раз в App.vue и считает время от beforeMount до монтирования, то есть время, необходимое для инициализации 500 дочерних компонентов (UserProfile).

После многих попыток я обнаружил, что время занимает около 30 мс, поэтому теперь давайте изменим его на UserProfile и изменим его на функциональный компонент:

<template functional>
  <div class="user-profile">{{ props.name }}</div>
</template>

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

Использование v-show и v-if со сценами

Вот два шаблона, которые используют v-show и v-if

<template>
  <div>
    <UserProfile :user="user1" v-if="visible" />
    <button @click="visible = !visible">toggle</button>
  </div>
</template>
<template>
  <div>
    <UserProfile :user="user1" v-show="visible" />
    <button @click="visible = !visible">toggle</button>
  </div>
</template>

Оба используются для управления отображением/скрытием некоторых компонентов или DOM.Прежде чем обсуждать их различия в производительности, давайте проанализируем, чем они отличаются. Среди них шаблон v-if будет скомпилирован в:

function render() {
  with (this) {
    return _c(
      'div',
      [
        visible
          ? _c('UserProfile', {
              attrs: {
                user: user1
              }
            })
          : _e(),
        _c(
          'button',
          {
            on: {
              click: function ($event) {
                visible = !visible
              }
            }
          },
          [_v('toggle')]
        )
      ],
      1
    )
  }
}

Видно, что часть v-if преобразуется в троичное выражение.Когда visible равно true, создается vnode UserProfile, в противном случае создается пустой vnode.При исправлении старые и новые узлы различаются и будут удалены . Старый узел или создайте новый узел, напримерUserProfileСоздание/уничтожение будет следовать. еслиUserProfileЕсли в компоненте много DOM или нужно выполнить много логики инициализации/уничтожения, то с видимым переключением много производительности будет потрачено впустую. В настоящее время для оптимизации можно использовать v-show.Давайте посмотрим на скомпилированный код v-show:

function render() {
  with (this) {
    return _c(
      'div',
      [
        _c('UserProfile', {
          directives: [
            {
              name: 'show',
              rawName: 'v-show',
              value: visible,
              expression: 'visible'
            }
          ],
          attrs: {
            user: user1
          }
        }),
        _c(
          'button',
          {
            on: {
              click: function ($event) {
                visible = !visible
              }
            }
          },
          [_v('toggle')]
        )
      ],
      1
    )
  }
}

v-showкомпилируется вdirectives, по сути, v-show — это директива внутри Vue, в коде которой в основном выполняется следующая логика:

el.style.display = value ? el.__vOriginalDisplay : 'none'

На самом деле это управляется переключением свойства отображения элемента.По сравнению с v-if, нет необходимости создавать/удалять узлы на этапе исправления, просто в соответствии сv-showЗначение, связанное с управлением элементом DOMstyle.displayсвойства, которые могут значительно снизить производительность в сценариях с частым переключением.

Но не сказатьv-showможно заменить в любом случаеv-if, если начальное значениеfalseчас,v-ifне создает скрытые узлы, ноv-showбудет создан и установленstyle.display='none'чтобы скрыть, хотя это выглядит так, как будто этот DOM скрыт, ноv-showПроцесс создания был полностью завершен, что привело к потере производительности.

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

Используйте поддержку активности

В случае динамических компонентов:

<template>
  <div>
    <component :is="currentComponent" />
  </div>
</template>

В это время несколько компонентов переключаются туда и обратно,currentComponentКаждый раз, когда он изменяется, связанные компоненты будут уничтожены/созданы один раз.Если эти компоненты сложные, это вызовет определенное давление на производительность.На самом деле, мы можем использовать keep-alive для кэширования этих компонентов:

<template>
  <div>
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
  </div>
</template>

keep-aliveФункция состоит в том, чтобы кешировать компоненты, которые он обертывает после первого рендеринга, и брать их прямо из кеша, когда это потребуется в следующий раз, избегая ненужных потерь производительности.Обсуждая предыдущий вопрос, я сказал, чтоv-showВ начале нагрузка на производительность высока, потому что нужно создать все компоненты, на самом деле вы можете использоватьkeep-aliveПод оптимизацией:

<template>
  <div>
    <keep-alive>
      <UserProfileA v-if="visible" />
      <UserProfileB v-else />
    </keep-alive>
  </div>
</template>

В этом случае он не будет отображаться во время инициализации.UserProfileBкомпонент при переключенииvisibleбудет отображаться, когдаUserProfileBкомпоненты, будучиkeep-aliveКогда он кешируется и часто переключается, это сэкономит много производительности, потому что он берется непосредственно из кеша, поэтому этот метод имеет лучшую производительность во время инициализации и обновления.

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

Избегайте использования v-for и v-if вместе

Это четко указано в официальном руководстве по стилю Vue:Руководство по стилю Vue

Например, следующий шаблон:

<ul>
  <li v-for="user in users" v-if="user.isActive" :key="user.id">
    {{ user.name }}
  </li>
</ul>

будет скомпилирован в:

// 简化版
function render() {
  return _c(
    'ul',
    this.users.map((user) => {
      return user.isActive
        ? _c(
            'li',
            {
              key: user.id
            },
            [_v(_s(user.name))]
          )
        : _e()
    }),
    0
  )
}

Как видите, здесь сначала обход (v-for), а затем суждение (v-if) Здесь есть проблема: если у вас есть 10 000 кусков данных, только 100 из нихisActiveВ состоянии вы хотите отобразить только эти 100 фрагментов данных, но при фактическом рендеринге каждый раз при рендеринге эти 10 000 фрагментов данных будут проходиться один раз. Например, когда вы изменяете какие-то отзывчивые данные где-либо еще в этом компоненте, он инициирует повторную визуализацию, вызывает функцию визуализации, а когда вызывается функция визуализации, выполняется приведенный выше код, таким образом проходя 10 000 фрагментов данных, даже если вашusersНичего не изменилось.

Чтобы избежать этой проблемы, вы можете использовать вместо этого вычисляемое свойство в этом сценарии:

<template>
  <div>
    <ul>
      <li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script>
  export default {
    // ...
    computed: {
      activeUsers() {
        return this.users.filter((user) => user.isActive)
      }
    }
  }
</script>

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

Всегда добавляйте ключ к v-for и не используйте индекс в качестве ключа

ЭтоРуководство по стилю VueНа это четко указано в статье, и этот момент часто задают во время интервью.Многие люди привыкли использовать индекс в качестве ключа, что на самом деле не очень хорошо.Когда индекс используется в качестве ключа, алгоритм сравнения будет принимать неверные решения, что приведет к некоторым проблемам с производительностью, вы можете посмотреть наsshСтатья большого человека, при глубоком анализе,Почему бы не использовать индекс в качестве ключа в Vue. Здесь я также использую пример, чтобы кратко показать, как влияет на производительность использование индекса в качестве ключа.

Взгляните на этот пример:

const Item = {
  name: 'Item',
  props: ['message', 'color'],
  render(h) {
    debugger
    console.log('执行了Item的render')
    return h('div', { style: { color: this.color } }, [this.message])
  }
}

new Vue({
  name: 'Parent',
  template: `
  <div @click="reverse" class="list">
    <Item
      v-for="(item,index) in list"
      :key="item.id"
      :message="item.message"
      :color="item.color"
    />
  </div>`,
  components: { Item },
  data() {
    return {
      list: [
        { id: 'a', color: '#f00', message: 'a' },
        { id: 'b', color: '#0f0', message: 'b' }
      ]
    }
  },
  methods: {
    reverse() {
      this.list.reverse()
    }
  }
}).$mount('#app')

Вот список, который будет отображатьсяa b, который будет выполняться при нажатииreverseЭтот метод меняет порядок в этом списке. Вы можете скопировать этот пример и увидеть результат на своем компьютере.

Давайте сначала проанализируемidПри использовании в качестве ключа, что происходит при нажатии,

Активировано, потому что список изменилсяParentПеререндерить компонент, получить новыйvnode, и старыйvnodeвыполнитьpatch, наша главная заботаpatchв процессеupdateChildrenлогика,updateChildrenтолько для старых и новыхchildrenвоплощать в жизньdiffАлгоритм максимально возможного повторного использования узлов, для нашего примера, в это время旧的childrenДа:

;[
  {
    tag: 'Item',
    key: 'a',
    propsData: {
      color: '#f00',
      message: '红色'
    }
  },
  {
    tag: 'Item',
    key: 'b',
    propsData: {
      color: '#0f0',
      message: '绿色'
    }
  }
]

воплощать в жизньreverseПосле新的childrenДа:

;[
  {
    tag: 'Item',
    key: 'b',
    propsData: {
      color: '#0f0',
      message: '绿色'
    }
  },
  {
    tag: 'Item',
    key: 'a',
    propsData: {
      color: '#f00',
      message: '红色'
    }
  }
]

выполнить в это времяupdateChildren,updateChildrenЦиклы старой и новой групп дочерних узлов будут сравниваться:

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
  } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx]
  } else if (sameVnode(oldStartVnode, newStartVnode)) {
    // 对新旧节点执行patchVnode
    // 移动指针
  } else if (sameVnode(oldEndVnode, newEndVnode)) {
    // 对新旧节点执行patchVnode
    // 移动指针
  } else if (sameVnode(oldStartVnode, newEndVnode)) {
    // 对新旧节点执行patchVnode
    // 移动oldStartVnode节点
    // 移动指针
  } else if (sameVnode(oldEndVnode, newStartVnode)) {
    // 对新旧节点执行patchVnode
    // 移动oldEndVnode节点
    // 移动指针
  } else {
    //...
  }
}

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

function sameVnode(a, b) {
  return (
    a.key === b.key &&
    ((a.tag === b.tag &&
      a.isComment === b.isComment &&
      isDef(a.data) === isDef(b.data) &&
      sameInputType(a, b)) ||
      (isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)))
  )
}

sameVnodeВ основном судят по тональности.Поскольку мы поменяли порядок списка, в первом раунде сравнения:sameVnode(oldStartVnode, newEndVnode)установлен, то есть старый головной узел и новый хвостовой узел являются одним и тем же узлом и будут выполняться в это время.patchVnodeлогика,patchVnodeбудет казненprePatch,prePatchреквизиты будут обновлены в , в это время наши два узла имеютpropsDataодинаковы, как для{color: '#0f0',message: '绿色'},В этом случаеItemРеквизит компонента не будет обновляться,ItemТакже не перерисовывается. назад сноваupdateChildren, будет продолжать выполняться"移动oldStartVnode节点"Операция, которая преобразует элемент DOM. Переместитесь в правильное положение, и тот же процесс будет использован для сравнения других узлов.

Можно обнаружить, что в течение всего процессаПросто переместил узел, не вызвал повторную визуализацию компонента ItemЭто обеспечивает повторное использование узлов.

Давайте посмотрим на использованиеindexВ качестве ключевого случая используйтеindexчас,旧的childrenДа:

;[
  {
    tag: 'Item',
    key: 0,
    propsData: {
      color: '#f00',
      message: '红色'
    }
  },
  {
    tag: 'Item',
    key: 1,
    propsData: {
      color: '#0f0',
      message: '绿色'
    }
  }
]

воплощать в жизньreverseПосле新的childrenДа:

;[
  {
    tag: 'Item',
    key: 0,
    propsData: {
      color: '#0f0',
      message: '绿色'
    }
  },
  {
    tag: 'Item',
    key: 1,
    propsData: {
      color: '#f00',
      message: '红色'
    }
  }
]

здесь иidУзлы, используемые в качестве ключей, различны.Хотя мы изменили порядок списка, порядок ключей не изменился.updateChildrenВремяsameVnode(oldStartVnode, newStartVnode)будет установлено, то есть старый головной узел и новый головной узел совпадают, а затем выполнитьpatchVnode -> prePatch -> 更新props, на этот раз старый propsData{color: '#f00',message: '红色'}, новые propsData{color: '#0f0',message: '绿色'}, после обновления реквизит Item изменится,вызовет повторную визуализацию компонента Item.

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

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

отложенный рендеринг

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

<template>
  <div>
    <!-- Heavy组件初始化时需要执行很复杂的逻辑,执行大量计算 -->
    <Heavy1 />
    <Heavy2 />
    <Heavy3 />
    <Heavy4 />
  </div>
</template>

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

Ссылаться наХуан ИучительДемистификация девяти советов по оптимизации производительности для Vue.jsкод в:

<template>
  <div>
    <Heavy v-if="defer(1)" />
    <Heavy v-if="defer(2)" />
    <Heavy v-if="defer(3)" />
    <Heavy v-if="defer(4)" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      displayPriority: 0
    }
  },
  mounted() {
    this.runDisplayPriority()
  },
  methods: {
    runDisplayPriority() {
      const step = () => {
        requestAnimationFrame(() => {
          this.displayPriority++
          if (this.displayPriority < 10) {
            step()
          }
        })
      }
      step()
    },
    defer(priority) {
      return this.displayPriority >= priority
    }
  }
}
</script>

На самом деле, очень простой принцип, в основном техническое обслуживаниеdisplayPriorityпеременная, черезrequestAnimationFrameУвеличивайте рендер каждого кадра, затем мы можем передать компонентv-if="defer(n)"СделатьdisplayPriorityУвеличьте до определенного значения, а затем визуализируйте, чтобы избежать проблемы с зависанием, вызванной слишком длительным временем выполнения js.

Работа с неотзывчивыми данными

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

<template>
  <div>
    <ul>
      <li v-for="item in heavyData" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
// 一万条数据
const heavyData = Array(10000)
  .fill(null)
  .map((_, idx) => ({ name: 'test', message: 'test', id: idx }))

export default {
  data() {
    return {
      heavyData: heavyData
    }
  },
  beforeCreate() {
    this.start = Date.now()
  },
  created() {
    console.log(Date.now() - this.start)
  }
}
</script>

heavyDataЕсть 10 000 единиц данных, вот статистика изbeforeCreateприбытьcreatedПрошедшее время, которое для этого примера в основном является временем для инициализации данных.

Я несколько раз проверял на своем персональном компьютере, и на этот раз40-50ms, то проходимObject.freeze()метод, будетheavyDataПерестаньте отвечать и повторите попытку:

//...
data() {
  return {
    heavyData: Object.freeze(heavyData)
  }
}
//...

После изменения попробуйте еще раз, время инициализации данных становится0-1ms, вскоре40ms,Этот40msрекурсивный обходheavyDataвоплощать в жизньObject.definePropertyвремя.

Так почемуObject.freeze()Будет ли такой эффект? использовать на объектеObject.freeze()После этого вы не сможете добавлять к этому объекту новые свойства, удалять существующие свойства и изменять перечисляемость, конфигурируемость и возможность записи существующих свойств объекта, а также вы не сможете изменять значения существующих свойств.

И у Vue есть мнение, прежде чем преобразовывать данные в отзывчивые:

export function observe(value, asRootData) {
  // ...省略其他逻辑
  if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  // ...省略其他逻辑
}

Одно из этих условий сужденияObject.isExtensible(value), этот метод должен определить, является ли объект расширяемым, потому что мы используемObject.freeze(), он обязательно вернется сюдаfalse, поэтому следующий процесс пропускается, что естественно экономит много времени.

На самом деле это важно не только при инициализации данных, вы можете использовать приведенный выше пример для подсчета отcreatedприбытьmountedПроведенное время, не использованное на моем компьютереObject.freeze(), это время60-70ms,использоватьObject.freeze()вплоть до40-50ms, это потому, что в функции рендеринга читаетсяheavyDataКогда данные вObject.definePropertyОпределенныйgetterметод, Vue выполняет некоторую обработку сбора зависимостей здесь, что определенно займет некоторое время.Object.freeze()Полученные данные не реагируют, и без процесса сбора зависимостей производительность естественно сохраняется.

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

Но это не без проблем, это приведет кheavyDataДанные ниже не являются ответными данными, вы используетеcomputed,watchи т. д. не будет иметь никакого эффекта, но, вообще говоря, этот большой объем данных используется для отображения.Если у вас есть особые потребности, вы можете использовать только определенный слой этих данных.Object.freeze(), а использование отложенного рендеринга, функциональных компонентов и т. д. выше может значительно повысить производительность.

Функции компиляции и рендеринга шаблонов, различия в производительности в JSX

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

На самом деле, в компиляции шаблона Vue2 не так много оптимизаций производительности.В Vue3 их много.В Vue3 значительно улучшена производительность за счет объединения компиляции и времени выполнения.Однако, поскольку эта статья посвящена оптимизации производительности Vue2, а у Vue2 еще много людей используют его, поэтому я выберу точку в компиляции шаблона Vue2.

статический узел

Следующий шаблон:

<div>你好! <span>Hello</span></div>

будет скомпилирован в:

function render() {
  with (this) {
    return _m(0)
  }
}

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

Компиляция Vue пройдетoptimizeВо время этого процесса будут отмечены статические узлы.Подробности см. в этом документе, написанном г-ном Хуан И:Компиляция Vue2 - оптимизация пометки статического узла.

существуетcodegenЭтап оценивает, что метка статического узла перейдет кgenStaticотделение:

function genStatic(el, state) {
  el.staticProcessed = true
  const originalPreState = state.pre
  if (el.pre) {
    state.pre = el.pre
  }
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
  state.pre = originalPreState
  return `_m(${state.staticRenderFns.length - 1}${
    el.staticInFor ? ',true' : ''
  })`
}

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

это_mФактическиrenderStaticаббревиатура:

export 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
  )
  markStatic(tree, `__static__${index}`, false)
  return tree
}

function markStatic(tree, key) {
  if (Array.isArray(tree)) {
    for (let i = 0; i < tree.length; i++) {
      if (tree[i] && typeof tree[i] !== 'string') {
        markStaticNode(tree[i], `${key}_${i}`, isOnce)
      }
    }
  } else {
    markStaticNode(tree, key, isOnce)
  }
}

function markStaticNode(node, key, isOnce) {
  node.isStatic = true
  node.key = key
  node.isOnce = isOnce
}

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

После получения узла он пройдетmarkStaticотметить узелisStaticЗнак равенства, отмеченный знакомisStaticУзлы будут пропущены напрямуюpatchVnodeэтап, потому что статический узел не изменится, поэтому нет необходимости в исправлении, пропуск исправления может снизить производительность.

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

В дополнение к статическим узлам оптимизация компиляции Vue2 также включает слоты, createElement и т. д.

Оптимизация компиляции шаблона Vue3

По сравнению с Vue2 оптимизация компиляции шаблонов в Vue3 более заметна, а производительность улучшена больше.Поскольку это требует многого, эта статья не может быть написана.Если вам интересно, вы можете прочитать эти статьи:Детали оптимизации компилятора Vue3, как писать высокопроизводительные функции рендеринга,Расскажите об оптимизации компиляции шаблонов Vue.js 3.0., и видео с интерпретацией You Yuxi:Ю Юйси, отец Vue, глубоко интерпретирует идеи разработки Vue3.0., я также напишу в будущем несколько статей отдельно, чтобы проанализировать оптимизацию компиляции шаблонов Vue3.

Суммировать

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

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

Наконец-то эта статья писалась долго и сил было потрачено много.Если вы считаете, что она вам полезна, ставьте лайк ⭐, поддержите, спасибо!

связанное предложение

Ниже приведены ссылки или связанные статьи в этой статье:

  1. Все еще читаете эти старомодные статьи по оптимизации производительности? Узнайте об этих последних показателях эффективности
  2. Демистификация девяти советов по оптимизации производительности для Vue.js
  3. Руководство по оптимизации производительности приложений Vue
  4. Почему бы не использовать индекс в качестве ключа в Vue? (Подробное объяснение алгоритма diff)
  5. Компиляция Vue2 - оптимизация пометки статического узла
  6. Детали оптимизации компилятора Vue3, как писать высокопроизводительные функции рендеринга
  7. Оптимизация производительности Vue2.6 для слотов
  8. Расскажите об оптимизации компиляции шаблонов Vue.js 3.0.
  9. Высокопроизводительный внешний рендеринг 100 000 фрагментов данных (временной разрез)
  10. Ю Юйси, отец Vue, глубоко интерпретирует идеи разработки Vue3.0.

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

  1. Vue2 Template Explorer
  2. Vue3 Template Explorer

Наконец, попросите лайк ⭐, спасибо~