Maison  >  Article  >  interface Web  >  Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

青灯夜游
青灯夜游avant
2021-10-11 09:55:472537parcourir

Comment optimiser Angular ? L'article suivant vous fera connaître l'optimisation des performances dans Angular, j'espère qu'il vous sera utile !

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

Cet article parlera de l'optimisation des performances d'Angular, et présentera principalement l'optimisation liée au runtime. Avant de parler de la façon d'optimiser, nous devons d'abord clarifier quels types de pages ont des problèmes de performances ? Quelles sont les mesures d’une bonne performance ? Quel est le principe de l’optimisation des performances ? Si ces questions vous intéressent, continuez à lire. [Recommandations du didacticiel associées : "Tutoriel angulaire"]

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

Mécanisme de détection des changements

Différent de l'optimisation de la transmission réseau, l'optimisation du temps d'exécution se concentre davantage sur le mécanisme de fonctionnement d'Angular et sur la manière de coder pour éviter efficacement les performances. Questions ( Meilleures pratiques). Pour comprendre le mécanisme de fonctionnement d'Angular, vous devez d'abord comprendre son mécanisme de détection des changements (également connu sous le nom de vérification sale) - comment restituer les changements d'état dans la vue. Comment refléter les changements d'état des composants dans la vue est également un problème que les trois frameworks frontaux doivent résoudre. Les solutions issues de différents frameworks ont des idées similaires mais ont également leurs propres caractéristiques.

Tout d'abord, Vue et React utilisent tous deux le DOM virtuel pour implémenter les mises à jour des vues, mais il existe encore des différences dans l'implémentation spécifique :

Pour React :

  • En utilisant setState ou forceUpdate pour déclencher la méthode render pour mettre à jour la vuesetState forceUpdate 来触发 render 方法更新视图

  • 父组件更新视图时,也会判断是否需要 re-render 子组件

对于 Vue:

  • Vue 会遍历 data 对象的所有属性,并使用 Object.defineProperty 把这些属性全部转为经过包装的 gettersetter

  • 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖

  • 当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而使它关联的组件得以更新

而 Angular 则是通过引入  Zone.js 对异步操作的 API 打补丁,监听其触发来进行变更检测。关于 Zone.js 的原理在之前的一篇文章中有详细的介绍。简单来说,Zone.js 通过 Monkey patch (猴补丁)的方式,暴力地将浏览器或 Node 中的所有异步 API 进行了封装替换。

比如浏览器中的 setTimeout

let originalSetTimeout = window.setTimeout;

window.setTimeout = function(callback, delay) {
  return originalSetTimeout(Zone.current.wrap(callback),  delay);
}

Zone.prototype.wrap = function(callback) {
  // 获取当前的 Zone
  let capturedZone = this;

  return function() {
    return capturedZone.runGuarded(callback, this, arguments);
  };
};

或者 Promise.then方法:

let originalPromiseThen = Promise.prototype.then;

// NOTE: 这里做了简化,实际上 then 可以接受更多参数
Promise.prototype.then = function(callback) {
  // 获取当前的 Zone
  let capturedZone = Zone.current;
  
  function wrappedCallback() {
    return capturedZone.run(callback, this, arguments);
  };
  
  // 触发原来的回调在 capturedZone 中
  return originalPromiseThen.call(this, [wrappedCallback]);
};

Zone.js 在加载时,对所有异步接口进行了封装。因此所有在 Zone.js 中执行的异步方法都会被当做为一个 Task 被其统一监管,并且提供了相应的钩子函数(hooks),用来在异步任务执行前后或某个阶段做一些额外的操作。因此通过 Zone.js 可以很方便地实现记录日志、监控性能、控制异步回调执行的时机等功能。

而这些钩子函数(hooks),可以通过Zone.fork()方法来进行设置,具体可以参考如下配置:

Zone.current.fork(zoneSpec) // zoneSpec 的类型是 ZoneSpec

// 只有 name 是必选项,其他可选
interface ZoneSpec {
  name: string; // zone 的名称,一般用于调试 Zones 时使用
  properties?: { [key: string]: any; } ; // zone 可以附加的一些数据,通过 Zone.get('key') 可以获取 
  onFork: Function; // 当 zone 被 forked,触发该函数
  onIntercept?: Function; // 对所有回调进行拦截
  onInvoke?: Function; // 当回调被调用时,触发该函数
  onHandleError?: Function; // 对异常进行统一处理
  onScheduleTask?: Function; // 当任务进行调度时,触发该函数
  onInvokeTask?: Function; // 当触发任务执行时,触发该函数
  onCancelTask?: Function; // 当任务被取消时,触发该函数
  onHasTask?: Function; // 通知任务队列的状态改变
}

举一个onInvoke的简单列子:

let logZone = Zone.current.fork({ 
  name: 'logZone',
  onInvoke: function(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
    console.log(targetZone.name, 'enter');
    parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source)
    console.log(targetZone.name, 'leave'); }
});

logZone.run(function myApp() {
    console.log(Zone.current.name, 'queue promise');
    Promise.resolve('OK').then((value) => {console.log(Zone.current.name, 'Promise', value)
  });
});

最终执行结果:

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

理解了 Zone.js 的原理之后,通过走读 Angular 的源码,可以知道 Zone.js 在 Angular 被用来实现只要有异步方法或事件的调用,就会触发变更检测。大体如下:

首先,在 applicatoin_ref.ts 文件中,当 ApplicationRef 构建时就订阅了微任务队列为空的回调事件,其调用了 tick

Lorsque le composant parent met à jour la vue, il déterminera également s'il doit re- rendre le composant enfant2-Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances li>

Pour Vue : 🎜🎜🎜🎜Vue traversera toutes les propriétés de l'objet data et utilisera Object.defineProperty pour convertir toutes ces propriétés en packages. Chaque instance de composant de <code>getter et setter🎜🎜🎜 a une instance watcher correspondante objet, qui sera rendu lorsque le composant Pendant le processus, les propriétés sont enregistrées en tant que dépendances🎜🎜🎜Lorsque le setter de la dépendance est appelé, le watcher sera notifié de recalculer, afin que ses composants associés puissent être mis à jour 🎜🎜 Angular a introduit Zone.js pour patcher l'API pour les opérations asynchrones et écouter ses déclencheurs pour effectuer la détection des changements. À propos du principe de Zone.js dans le précédent Il y a une introduction détaillée dans un article 🎜. Pour faire simple, Zone.js encapsule et remplace violemment toutes les API asynchrones dans le navigateur ou Node via le patch Monkey. 🎜🎜Par exemple, setTimeout dans le navigateur : 🎜
import { Piep, PipeTransform } from &#39;@angular/core&#39;;

@Pipe({
  name: &#39;gender&#39;,
  pure,
})
export class GenderPiep implements PipeTransform {
  transform(value: string): string {
    if (value === &#39;M&#39;) return &#39;男&#39;;
    if (value === &#39;W&#39;) return &#39;女&#39;;
    return &#39;&#39;;
  }
}
🎜 ou méthode Promise.then : 🎜
@Component({
  ...
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class XXXComponent {
	....
}
🎜Zone.js encapsule toutes les interfaces asynchrones lors du chargement. Par conséquent, toutes les méthodes asynchrones exécutées dans Zone.js seront traitées comme une tâche et uniformément supervisées par celle-ci, et les fonctions de hook correspondantes (hooks) sont fournies pour effectuer certaines opérations supplémentaires avant et après l'exécution de la tâche asynchrone ou à un certain stade. . Par conséquent, Zone.js peut facilement implémenter des fonctions telles que la journalisation, la surveillance des performances et le contrôle du calendrier d'exécution des rappels asynchrones. 🎜🎜Ces fonctions de hook (hooks) peuvent être définies via la méthode Zone.fork() Pour plus de détails, veuillez vous référer à la configuration suivante : 🎜rrreee🎜Donnez un exemple simple de onInvoke<.> Exemple : 🎜rrreee🎜Résultat final de l'exécution : 🎜🎜<img src="https://img.php.cn/upload/image/862/659/987/163391656587125Comment%20Angular%20optimise-t-il%20?%20Une%20br%C3%A8ve%20analyse%20des%20solutions%20d%E2%80%99optimisation%20des%20performances" title="163391656587125Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances" alt=" Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances">🎜🎜Après avoir compris les principes de Zone.js, en lisant le code source d'Angular, vous pouvez savoir que Zone.js est utilisé dans Angular pour implémenter la détection de changement tant qu'il y a un appel à une méthode ou un événement asynchrone. C'est à peu près le suivant : 🎜🎜Tout d'abord, dans _ref .ts%23L522" target="_blank" rel="nofollow noopener noreferrer" title="https://github.com/angular/angular/blob/fc6f48185c3a546b130296d3d5dce86fdf334115/ packages/core/src/application_ref.ts#L522" ref= "nofollow noopener noreferrer">applicatoin_ref.ts🎜 fichier, lorsque <code>ApplicationRef a été construit, il s'est abonné à l'événement de rappel indiquant que la file d'attente des microtâches était vide , et appelé la méthode tick (c'est-à-dire détection de changement) : 🎜🎜🎜🎜

Deuxièmement, dans la méthode checkStable, il sera déterminé que l'événement onMicrotaskEmpty sera déclenché lorsque la file d'attente des microtâches est vidée (en combinaison, cela équivaut à déclencher la détection de changement) : onMicrotaskEmpty 事件(结合上来看,等价于会触发变更检测):

2-Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

最后,能够触发  checkStable 方法的调用的地方分别在 Zone.js 的三个钩子函数中,分别是 onInvokeonInvokeTaskonHasTask

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

比如 onHasTask —— 检测到有或无 ZoneTask 时触发的钩子:

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

另外 Zone.js 中对于异步任务总共分为三类:

Micro Task(微任务):Promise 等创建, nativePromise 是在当前事件循环结束前就要执行的,而打过补丁的 Promise 也会在事件循环结束前执行。

Macro Task (宏任务):setTimeout 等创建,nativesetTimeout 会在将来某个时间被处理。

Event Task :addEventListener 等创建,这些 task


Enfin, vous pouvez déclencher checkStable est appelée dans trois fonctions hook de Zone.js, à savoir onInvoke, onInvokeTask et onHasTask
Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

Par exemple, onHasTask - un hook déclenché lorsque la présence ou l'absence de ZoneTask est détectée :

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

De plus, les tâches asynchrones dans Zone.js sont divisées en trois catégories : Micro Tâche : Créée par Promise etc. La Promesse de natif est exécutée avant la fin de l'événement en cours loop , et la Promise corrigée sera également exécutée avant la fin de la boucle d'événement.

Tâche Macro :

Créée par setTimeout etc. Le setTimeout de natif sera traité à un moment donné dans le futur. Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

Tâche événementielle :

Créées par addEventListener et ainsi de suite. Ces tâches peuvent être déclenchées plusieurs fois ou ne jamais être déclenchées.

En fait, du point de vue du navigateur,

Event Task

peut en fait être considérée comme une macro-tâche. En d'autres termes, tous les événements ou API asynchrones peuvent être compris comme l'une des macro-tâches ou des micro-tâches, et leur exécution. La séquence est analysée en détail dans un

article

précédent. En termes simples :

(1) Une fois le thread principal exécuté, il vérifiera d'abord s'il y a des tâches dans la file d'attente des microtâches qui doivent être exécutées

(2) La première fois, une fois l'interrogation terminée, il vérifiera s'il y a encore des tâches à exécuter dans la file d'attente des tâches macro. Après l'exécution, il vérifiera s'il y a encore des tâches à exécuter dans la liste des micro-tâches. Ce processus sera répété. après

Principe d'optimisation des performances

La qualité des performances de la page , le jugement le plus intuitif est de voir si la réponse de la page est fluide et rapide. La réponse de page est essentiellement le processus de rendu des modifications d'état de la page sur la page. D'un point de vue relativement macro, la détection des modifications d'Angular n'est en réalité qu'une partie de l'ensemble du cycle de réponse aux événements. Toutes les interactions entre les utilisateurs et la page sont déclenchées par des événements, et l'ensemble du processus de réponse est à peu près le suivant : 🎜🎜🎜🎜🎜Si vous envisagez d'optimiser la vitesse de réponse de la page, vous pouvez commencer à partir de chaque étape : 🎜🎜 (1) Pour l'étape de déclenchement d'événements, vous pouvez réduire le déclenchement d'événements pour réduire le nombre total de détections de modifications et de nouveau rendu🎜🎜(2) Pour l'étape logique d'exécution du gestionnaire d'événements, le temps d'exécution peut être réduit en optimisant la logique de code complexe🎜🎜 (3) Pour la détection des modifications, détectez la liaison des données et lors de l'étape de mise à jour du DOM, le nombre de détections de modifications et de calculs de données de modèle peut être réduit pour réduire le temps de rendu🎜🎜(4) Pour l'étape de rendu du navigateur, vous devrez peut-être envisager d'utiliser un navigateur différent ou une amélioration de la configuration matérielle🎜🎜Pour la seconde, l'optimisation pertinente des quatre phases ne sera pas trop discutée ici en combinaison avec la classification Angular des tâches asynchrones mentionnée ci-dessus, les méthodes d'optimisation pour les première et troisième phases. peut être clarifié davantage : 🎜🎜 (1) Pour les demandes de fusion de tâches de macro, essayez de minimiser le nombre de ticks 🎜🎜 (2) Fusionner les ticks pour les micro-tâches 🎜🎜 (3) Réduire le déclenchement d'événements et les événements d'enregistrement pour les tâches d'événement 🎜🎜 ( 4) La coche est divisée en deux phases : vérification et rendu, réduisant ainsi le calcul et le non-respect de la phase de vérification. Rendu nécessaire 🎜

前面有提到,大多数情况通过观察页面是否流畅可以判断页面的是否存在性能问题。虽然这种方式简单、直观,但也相对主观,并非是通过精确的数字反映页面的性能到底如何。换言之,我们需要用一个更加有效、精确的指标来衡量什么样的页面才是具备良好性能的。而 Angular 官方也提供了相应的方案,可以通过开启 Angular 的调试工具,来实现对变更检测循环(完成的 tick)的时长监控。

首先,需要使用 Angular 提供的 enableDebugTools 方法,如下:

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

之后只需要在浏览器的控制台中输入 ng.profiler.timeChangeDetection() ,即可看到当前页面的平均变更检测时间:

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

从上面可以看出,执行了 692 次变更检测循环(完整的事件响应周期)的平均时间为 0.72 毫秒。如果多运行几次,你会发现每次运行的总次数是不一样、随机的。

官方提供了这样一个判断标准:理想情况下,分析器打印出的时长(单次变更检测循环的时间)应该远低于单个动画帧的时间(16 毫秒)。一般这个时长保持在 3 毫秒下,则说明当前页面的变更检测循环的性能是比较好的。如果超过了这个时长,则就可以结合 Angular 的变更检测机制分析一下是否存在重复的模板计算和变更检测。

性能优化方案

在理解 Angular 优化原理的基础上,我们就可以更有针对性地去进行相应的性能优化:

(1)针对异步任务 ——减少变更检测的次数

  • 使用 NgZone 的 runOutsideAngular 方法执行异步接口
  • 手动触发 Angular 的变更检测

(2)针对 Event Task —— 减少变更检测的次数

  • 将 input 之类的事件换成触发频率更低的事件
  • 对 input valueChanges 事件做的防抖动处理,并不能减少变更检测的次数

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

如上图,防抖动处理只是保证了代码逻辑不会重复运行,但是 valueChanges 的事件却随着 value 的改变而触发(改变几次,就触发几次),而只要有事件触发就会相应触发变更检测。

(3)使用 Pipe ——减少变更检测中的计算次数

  • 将 pipe 定义为 pure pipe(@Pipe 默认是 pure pipe,因此也可以不用显示地设置 pure: true

    import { Piep, PipeTransform } from &#39;@angular/core&#39;;
    
    @Pipe({
      name: &#39;gender&#39;,
      pure,
    })
    export class GenderPiep implements PipeTransform {
      transform(value: string): string {
        if (value === &#39;M&#39;) return &#39;男&#39;;
        if (value === &#39;W&#39;) return &#39;女&#39;;
        return &#39;&#39;;
      }
    }

关于  Pure/ImPure Pipe:

  • Pure Pipe: 如果传入 Pipe 的参数没有改变,则会直接返回之前一次的计算结果

  • ImPure Pipe: 每一次变更检测都会重新运行 Pipe 内部的逻辑并返回结果。(简单来说, ImPure Pipe 就等价于普通的 formattedFunction,如果一个页面触发了多次的变更检测,那么 ImPure Pipe 的逻辑就会执行多次)

(4)针对组件 ——减少不必要的变更检测

  • 组件使用 onPush 模式
    • 只有输入属性发生变化时,该组件才会检测
    • 只有该组件或者其子组件中的 DOM 事件触发时,才会触发检测
    • 非 DOM 事件的其他异步事件,只能手动触发检测
    • 声明了 onPush 的子组件,如果输入属性未变化,就不会去做计算和更新
@Component({
  ...
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class XXXComponent {
	....
}

在 Angular 中 显示的设置 @ComponentchangeDetection 为  ChangeDetectionStrategy.OnPush 即开启 onPush 模式(默认不开启),用 OnPush 可以跳过某个组件或者某个父组件以及它下面所有子组件的变化检测,如下所示:

Comment Angular optimise-t-il ? Une brève analyse des solutions d’optimisation des performances

(5)针对模板 ——减少不必要的计算和渲染

  • Utilisez trackBy pour le rendu en boucle de la liste
  • Essayez d'utiliser des valeurs mises en cache et évitez d'utiliser des appels de méthode et d'obtenir des appels d'attributs
  • S'il y a un endroit dans le modèle qui a vraiment besoin d'appeler une fonction, et qu'il y en a plusieurs appels, vous pouvez utiliser le cache de modèles
  • ngIf control L'affichage des composants doit être contrôlé là où le composant est appelé

(6) Autres suggestions d'optimisation du codage

  • N'utilisez pas try/catch pour le contrôle des processus, ce qui entraînera beaucoup de consommation de temps (enregistrement d'une grande quantité d'informations sur la pile, etc.)
  • Des animations excessives entraîneront un décalage de chargement des pages
  • Vous pouvez utiliser le défilement virtuel pour les longues listes
  • Retardez le chargement autant que possible pour le préchargement module, car le nombre simultané de threads de requête http du navigateur est limité Une fois la limite dépassée, les requêtes suivantes seront bloquées et suspendues
  • et ainsi de suite

Résumé

(1) Brève explication de l'utilisation d'Angular. Zone.js pour implémenter la détection des changements

(2) Après avoir compris Angular Sur la base de la détection des changements, les principes d'optimisation des performances angulaires et les critères permettant de juger si une page a de bonnes performances sont clarifiés davantage

(3) Certains runtimes ciblés des solutions d'optimisation des performances sont fournies

Pour plus de connaissances sur la programmation, veuillez visiter : Introduction à la programmation ! !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer