Comment optimiser Angular ? L'article suivant vous fera connaître l'optimisation des performances dans Angular, j'espère qu'il vous sera utile !
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"]
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
把这些属性全部转为经过包装的 getter
和 setter
每个组件实例都有相应的 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) }); });
最终执行结果:
理解了 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 enfant li>
data
et utilisera Object.defineProperty< /code> 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</code > sera notifié de recalculer, afin que ses composants associés puissent être mis à jour 🎜</li></ul>🎜 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 <a href="https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2Fforcheng%2Fp%2F13472326.html" target=" _blank " rel="nofollow noopener noreferrer" title="https://www.cnblogs.com/forcheng/p/13472326.html" ref="nofollow noopener noreferrer">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, <code>setTimeout
dans le navigateur : 🎜import { Piep, PipeTransform } from '@angular/core'; @Pipe({ name: 'gender', pure, }) export class GenderPiep implements PipeTransform { transform(value: string): string { if (value === 'M') return '男'; if (value === 'W') return '女'; return ''; } }
Promise.then
: 🎜@Component({ ... changeDetection: ChangeDetectionStrategy.OnPush, }) export class XXXComponent { .... }
Zone.fork()
Pour plus de détails, veuillez vous référer à la configuration suivante : 🎜rrreee🎜Donnez un exemple simple de onInvoke<. /code> Exemple : 🎜rrreee🎜Résultat final de l'exécution : 🎜🎜<img src="https://img.php.cn/upload/image/862/659/987/163391656587125Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation des performances" title="163391656587125Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation des performances" alt=" Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation 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
事件(结合上来看,等价于会触发变更检测):
最后,能够触发 checkStable 方法的调用的地方分别在 Zone.js 的三个钩子函数中,分别是 onInvoke
、 onInvokeTask
和 onHasTask
:
比如 onHasTask
—— 检测到有或无 ZoneTask
时触发的钩子:
另外 Zone.js 中对于异步任务总共分为三类:
Micro Task(微任务): 由 Promise
等创建, native
的 Promise
是在当前事件循环结束前就要执行的,而打过补丁的 Promise
也会在事件循环结束前执行。
Macro Task (宏任务): 由 setTimeout
等创建,native
的 setTimeout
会在将来某个时间被处理。
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</code > :<br/><a href="https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2Fforcheng%2Fp%2F12746392.html" target="_blank" rel="nofollow noopener noreferrer" title="https://www.cnblogs.com/forcheng/p/12746392.html" ref="nofollow noopener noreferrer"></a><img src="https://img.php.cn/upload/image/119/939/373/163391673594784Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation des performances" title="163391673594784Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation des performances" alt="Comment Angular optimise-t-il ? Une brève analyse des solutions doptimisation des performances " /><br/></p><p>Par exemple, <code>onHasTask
- un hook déclenché lorsque la présence ou l'absence de ZoneTask
est détectée :
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.
Créée par setTimeout
etc. Le setTimeout
de natif
sera traité à un moment donné dans le futur.
Tâche événementielle :
Créées paraddEventListener
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 unarticle
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èsPrincipe d'optimisation des performancesLa 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
方法,如下:
之后只需要在浏览器的控制台中输入 ng.profiler.timeChangeDetection()
,即可看到当前页面的平均变更检测时间:
从上面可以看出,执行了 692 次变更检测循环(完整的事件响应周期)的平均时间为 0.72 毫秒。如果多运行几次,你会发现每次运行的总次数是不一样、随机的。
官方提供了这样一个判断标准:理想情况下,分析器打印出的时长(单次变更检测循环的时间)应该远低于单个动画帧的时间(16 毫秒)。一般这个时长保持在 3 毫秒下,则说明当前页面的变更检测循环的性能是比较好的。如果超过了这个时长,则就可以结合 Angular 的变更检测机制分析一下是否存在重复的模板计算和变更检测。
性能优化方案
在理解 Angular 优化原理的基础上,我们就可以更有针对性地去进行相应的性能优化:
(1)针对异步任务 ——减少变更检测的次数
(2)针对 Event Task —— 减少变更检测的次数
如上图,防抖动处理只是保证了代码逻辑不会重复运行,但是 valueChanges 的事件却随着 value 的改变而触发(改变几次,就触发几次),而只要有事件触发就会相应触发变更检测。
(3)使用 Pipe ——减少变更检测中的计算次数
将 pipe 定义为 pure pipe(@Pipe
默认是 pure pipe,因此也可以不用显示地设置 pure: true
)
import { Piep, PipeTransform } from '@angular/core'; @Pipe({ name: 'gender', pure, }) export class GenderPiep implements PipeTransform { transform(value: string): string { if (value === 'M') return '男'; if (value === 'W') return '女'; return ''; } }
关于 Pure/ImPure Pipe:
Pure Pipe: 如果传入 Pipe 的参数没有改变,则会直接返回之前一次的计算结果
ImPure Pipe: 每一次变更检测都会重新运行 Pipe 内部的逻辑并返回结果。(简单来说, ImPure Pipe 就等价于普通的 formattedFunction,如果一个页面触发了多次的变更检测,那么 ImPure Pipe 的逻辑就会执行多次)
(4)针对组件 ——减少不必要的变更检测
@Component({ ... changeDetection: ChangeDetectionStrategy.OnPush, }) export class XXXComponent { .... }
在 Angular 中 显示的设置 @Component
的 changeDetection
为 ChangeDetectionStrategy.OnPush
即开启 onPush 模式(默认不开启),用 OnPush 可以跳过某个组件或者某个父组件以及它下面所有子组件的变化检测,如下所示:
(5)针对模板 ——减少不必要的计算和渲染
(6) Autres suggestions d'optimisation du codage
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!