Angular를 최적화하는 방법은 무엇입니까? 다음 글에서는 Angular의 성능 최적화에 대해 알려드리겠습니다. 도움이 되길 바랍니다!
이 글에서는 Angular의 성능 최적화에 대해 이야기할 것이며 주로 런타임과 관련된 최적화에 대해 소개하겠습니다. 최적화 방법에 대해 이야기하기 전에 먼저 어떤 종류의 페이지에 성능 문제가 있는지 명확히 해야 합니까? 좋은 성과의 척도는 무엇입니까? 성능 최적화의 원리는 무엇입니까? 이러한 질문에 관심이 있으시면 계속 읽어보시기 바랍니다. [관련 튜토리얼 권장 사항: "angular Tutorial"]
변경 감지 메커니즘
네트워크 전송 최적화와 달리 런타임 최적화는 Angular의 운영 메커니즘과 성능을 효과적으로 방지하기 위한 코딩 방법에 더 중점을 둡니다. 모범 사례). Angular의 작동 메커니즘을 이해하려면 먼저 변경 감지 메커니즘(더티 검사라고도 함), 즉 상태 변경 사항을 뷰에 다시 렌더링하는 방법을 이해해야 합니다. 컴포넌트 상태의 변경 사항을 뷰에 반영하는 방법도 세 가지 프런트엔드 프레임워크 모두가 해결해야 할 문제입니다. 다양한 프레임워크의 솔루션은 비슷한 아이디어를 가지고 있지만 고유한 특성도 있습니다.
우선 Vue와 React는 모두 가상 DOM을 사용하여 뷰 업데이트를 구현하지만 구체적인 구현에는 여전히 차이점이 있습니다.
React의 경우:
setState
또는 forceUpdate
는 render
메서드를 트리거하여 뷰를 업데이트합니다setState
或 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
상위 구성 요소가 뷰를 업데이트할 때 다시 렌더링해야 하는지 여부도 결정합니다. 렌더링
하위 구성 요소 li>
data
객체의 모든 속성을 탐색하고 Object.defineProperty<를 사용합니다. /code>를 사용하여 이러한 모든 속성을 패키지로 변환 <code>getter
및 setter
🎜🎜🎜의 각 구성 요소 인스턴스에는 해당 watcher
인스턴스가 있습니다. 프로세스 중에 속성은 종속성으로 기록됩니다🎜🎜🎜종속성의 setter
가 호출되면 watcher</code > 관련 구성요소가 업데이트될 수 있도록 재계산하라는 알림이 전송됩니다. 🎜</li></ul>🎜 Angular는 비동기 작업 API를 패치하고 트리거를 수신하여 변경 사항을 감지하기 위해 Zone.js를 도입했습니다. 이전 <a href="https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2Fforcheng%2Fp%2F13472326.html" target="에서 Zone.js의 원리에 대해 _blank " rel="nofollow noopener noreferrer" title="https://www.cnblogs.com/forcheng/p/13472326.html" ref="nofollow noopener noreferrer">기사에 자세한 소개가 있습니다🎜. 간단히 말해서 Zone.js는 Monkey 패치를 통해 브라우저나 노드의 모든 비동기 API를 강력하게 캡슐화하고 대체합니다. 🎜🎜예를 들어 브라우저의 <code>setTimeout
: 🎜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()
메서드를 통해 설정할 수 있습니다. 자세한 내용은 다음 구성을 참조하세요. 🎜rrreee🎜onInvoke<의 간단한 예를 들어주세요. /code> 예: 🎜rrreee🎜최종 실행 결과: 🎜🎜<img src="https://img.php.cn/upload/image/862/659/987/163391656587125Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석" title="163391656587125Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석" alt=" Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석"/>🎜🎜 Zone.js의 원리를 이해한 후 Angular의 소스 코드를 읽으면 Zone.js가 호출이 있는 한 변경 감지를 구현하기 위해 Angular에서 사용된다는 것을 알 수 있습니다. 비동기 메서드 또는 이벤트에. 대략 다음과 같습니다. 🎜🎜우선 _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🎜 파일, <code>ApplicationRef
가 빌드될 때 마이크로태스크 대기열이 비어 있다는 콜백 이벤트를 구독했습니다. , 그리고 tick
메서드를 호출했습니다(예: 변경 감지): 🎜🎜🎜🎜두 번째로, checkStable 메서드에서는 마이크로태스크 대기열이 비워질 때 onMicrotaskEmpty
이벤트가 트리거되도록 결정됩니다(조합하여 변경 감지를 트리거하는 것과 동일합니다). 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
마지막으로 checkStable 메소드는 Zone.js의 세 가지 후크 함수, 즉 onInvoke
, onInvokeTask
및 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/163391673594784Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석" title="163391673594784Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석" alt="Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석 " /><br/></p><p>예: <code>onHasTask
- ZoneTask
의 유무가 감지될 때 트리거되는 후크:
또한 Zone.js의 비동기 작업은 나누어집니다. 마이크로 작업: Promise
등에 의해 생성됩니다. native
의 Promise
는 현재 이벤트가 끝나기 전에 실행됩니다. loop 이며, 패치된 Promise
도 이벤트 루프가 끝나기 전에 실행됩니다.
setTimeout
등에 의해 생성됩니다. native
의 setTimeout
은 나중에 처리될 예정입니다.
이벤트 작업:
addEventListener
등에 의해 생성됩니다. 이러한 작업
은 여러 번 트리거될 수도 있고 전혀 트리거되지 않을 수도 있습니다. 실제로 브라우저의 관점에서 보면 Event Task
는 실제로 매크로 작업으로 간주될 수 있습니다. 즉, 모든 이벤트나 비동기 API는 매크로 작업 또는 마이크로 작업 중 하나로 이해될 수 있으며, 그 실행은 다음과 같습니다. 시퀀스는 이전기사
에서 자세히 분석되었습니다. 간단히 말해서: (1) 메인 스레드가 실행된 후 먼저 마이크로태스크 대기열에 실행해야 할 작업이 있는지 확인합니다(2 ) 처음으로 폴링이 완료된 후 매크로 작업 대기열에 실행할 작업이 아직 있는지 확인합니다. 실행 후 마이크로 작업 목록에 실행할 작업이 아직 있는지 확인합니다. 이후 반복성능 최적화의 원리페이지 성능의 품질, 가장 직관적인 판단은 페이지 응답이 부드럽고 빠른지 확인하는 것입니다. 페이지 응답은 본질적으로 페이지 상태 변경 사항을 페이지에 다시 렌더링하는 프로세스입니다. 상대적으로 거시적인 관점에서 볼 때 Angular의 변경 감지는 실제로 전체 이벤트 응답 주기의 한 부분일 뿐입니다. 사용자와 페이지 간의 모든 상호 작용은 이벤트를 통해 시작되며 전체 응답 프로세스는 대략 다음과 같습니다. 🎜🎜🎜🎜🎜페이지 응답 속도 최적화를 고려하는 경우 각 단계부터 시작할 수 있습니다. 🎜🎜 (1) 트리거 이벤트 단계에서는 이벤트 트리거를 줄여 전체적인 변경 감지 및 재렌더링 횟수를 줄일 수 있습니다🎜🎜(2) 이벤트 핸들러 실행 로직 단계에서는 복잡한 코드 로직을 최적화하여 실행 시간을 줄일 수 있습니다🎜🎜 (3) 변경 감지의 경우 데이터 바인딩 감지 및 DOM 업데이트 단계에서 변경 감지 횟수와 템플릿 데이터 계산을 줄여 렌더링 시간을 줄일 수 있습니다🎜🎜(4) 브라우저 렌더링 단계에서는 다음을 사용하는 것을 고려해야 할 수도 있습니다. 다른 브라우저 또는 하드웨어 구성 개선🎜🎜두 번째로, 4단계의 관련 최적화는 위에서 언급한 Angular의 비동기 작업 분류와 결합하여 첫 번째 및 세 번째 단계의 최적화 방법에 대해 너무 많이 논의하지 않습니다. 더 자세히 설명할 수 있습니다. 🎜🎜 (1) 매크로 작업 병합 요청의 경우 틱 수를 최소화하려고 노력하세요. 🎜🎜 (2) 마이크로 작업의 틱 병합 🎜🎜 (3) 이벤트 작업에 대한 이벤트 트리거 및 등록 이벤트 줄이기 🎜🎜 ( 4) 틱은 검사와 렌더링의 두 단계로 나누어 계산 및 검사 단계의 비준수를 줄입니다. 렌더링이 필요합니다 🎜前面有提到,大多数情况通过观察页面是否流畅可以判断页面的是否存在性能问题。虽然这种方式简单、直观,但也相对主观,并非是通过精确的数字反映页面的性能到底如何。换言之,我们需要用一个更加有效、精确的指标来衡量什么样的页面才是具备良好性能的。而 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) 기타 코딩 최적화 제안
요약
(1) Angular 방법을 간략하게 설명했습니다. Zone.js를 사용하여 변경 감지 구현
(2) Angular를 이해한 후 변경 감지를 기반으로 Angular 성능 최적화의 원리와 페이지 성능이 좋은지 판단하는 기준이 더 명확해졌습니다
(3) 일부 대상 런타임 성능 최적화 솔루션이 제공됩니다
더 많은 프로그래밍 관련 지식을 보려면 프로그래밍 소개를 방문하세요! !
위 내용은 Angular는 어떻게 최적화하나요? 성능 최적화 솔루션에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!