0

0

浅析Angular中的Change Detection机制

青灯夜游

青灯夜游

发布时间:2022-12-15 21:20:55

|

2657人浏览过

|

来源于掘金社区

转载

浅析Angular中的Change Detection机制

什么是 Change Detection ?

在应用的开发过程中,state 代表需要显示在应用上的数据。当 state 发生变化时,往往需要一种机制来检测变化的 state 并随之更新对应的界面。这个机制就叫做 Change Detection 机制。【相关教程推荐:《angular教程》】

在 WEB 开发中,更新应用界面其实就是对 DOM 树进行修改。由于 DOM 操作是昂贵的,所以一个效率低下的 Change Detection 会让应用的性能变得很差。因此,框架在实现 Change Detection 机制上的高效与否,很大程度上决定了其性能的好坏。

Change Detection 是如何实现的

Angular 可以检测组件数据何时更改,然后自动重新渲染视图以反映该更改。但是在像点击按钮这样的低级事件之后,它怎么能做到这一点呢?

通过 Zone , Angular 能够实现自动的触发 Change Detection 机制

Zone 是什么呢?简而言之,Zone 是一个执行上下文(execution context),可以理解为一个执行环境。与常见的浏览器执行环境不同,在这个环节中执行的所有异步任务都被称为 Task ,Zone 为这些 Task 提供了一堆的钩子(hook),使得开发者可以很轻松的「监控」环境中所有的异步任务。

题外话:由于 Angular 极力的推崇使用可观察对象(Observable),如果完全的基于 Observable 来开发应用,可以代替 Zone 来实现追踪调用栈的功能,且性能还比使用 Zone 会稍好一些。
  // Angular 在 v5.0.0-beta.8 起可以通过配置不使用 Zone 
  import { platformBrowser } from '@angular/platform-browser';
  platformBrowser().bootstrapModuleFactory(AppModuleNgFactory, { ngZone: 'noop' });

覆盖浏览器默认机制

Angular 在启动时会重写浏览器 low-level API,例如addEventListener,它是用于注册所有浏览器事件的浏览器函数,包括点击处理。Angular 将替换addEventListener为与此等效的新版本:

// this is the new version of addEventListener                                    
function addEventListener(eventName, callback) { 
    // call the real addEventListener                
    callRealAddEventListener(eventName, function() { 
        //first call the original callback              
        callback(...);
        // and then run Angular-specific functionality
        var changed = angular.runChangeDetection();
        if (changed) {
            angular.reRenderUIPart();
        }
    });
}

新的addEventListener为任何事件处理程序添加了更多功能:不仅调用了注册的回调,而且 Angular 有机会运行更改检测并更新 UI。

支持浏览器异步 API

修补了以下常用浏览器机制以支持更改检测:

  • 所有浏览器事件(单击、鼠标悬停、按键等)
  • setTimeout()setInterval()
  • Ajax HTTP 请求

事实上,Zone.js 修补了许多其他浏览器 API,以透明地触发 Angular 更改检测,例如 Websockets。

这种机制的一个限制是,如果由于某种原因 Zone.js 不支持的异步浏览器 API,则不会触发更改检测。例如,IndexedDB 回调就是这种情况。

默认的变更检测机制是如何工作的?

每个 Angular 组件都有一个关联的变更检测器,它是在应用程序启动时创建的。例如:

@Component({
    selector: 'todo-item',
    template: `<span class="todo noselect" 
       (click)="onToggle()">{{todo.owner.firstname}} - {{todo.description}}
       - completed: {{todo.completed}}</span>`
})
export class TodoItem {
    @Input()
    todo:Todo;

    @Output()
    toggle = new EventEmitter<Object>();

    onToggle() {
        this.toggle.emit(this.todo);
    }
}

该组件将接收一个 Todo 对象作为输入,并在 todo 状态被切换时发出一个事件。

export class Todo {
    constructor(public id: number, 
        public description: string, 
        public completed: boolean, 
        public owner: Owner) {
    }
}

我们可以看到 Todo 有一个属性owner,它本身就是一个具有两个属性的对象:firstnamelastname

变更检测器是什么样的?

我们实际上可以在运行时看到变化检测器的样子!要查看它,只需在 Todo 类中添加一些代码以在访问某个属性时触发断点。

当断点命中时,我们可以遍历堆栈跟踪并查看变化检测:

image.png

这个方法一开始可能看起来很奇怪,所有变量都奇怪命名。但是通过深入研究,我们注意到它在做一些非常简单的事情:对于模板中使用的每个表达式,它会将表达式中使用的属性的当前值与该属性的先前值进行比较。

如果前后的属性值不同,就会设置isChanged 为true,就这样!差不多,它是通过使用一个名为looseNotIdentical() 的方法来比较值。

那么嵌套对象owner呢?

我们可以在更改检测器代码中看到 owner 嵌套对象的属性也正在检查差异。但只比较 firstname 属性,而不是 lastname 属性。这是因为组件template中没有使用lastname!同样,Todo 的顶级 id 属性也没有出于相同的原因进行比较。

有了这个,我们可以有把握地说:

默认情况下,Angular Change Detection 通过检查模板表达式的值是否已更改来工作。

我们还可以得出结论:

默认情况下,Angular 不做深度对象比较来检测变化,它只考虑模板使用的属性

为什么默认情况下更改检测会这样工作?

Angular 的主要目标之一是更加透明和易于使用,因此框架用户不必费尽心思调试框架并了解内部机制即可有效地使用它。

AI Web Designer
AI Web Designer

AI网页设计师,快速生成个性化的网站设计

下载

如果 Angular 默认更改检测机制基于组件输入的参考比较而不是默认机制,那会是什么情况?即使是像 TODO 应用程序这样简单的东西也很难构建:开发人员必须非常小心地创建一个新的 Todo,而不是简单地更新属性。

OnPush 变化检测策略

如果你觉得默认模式影响了性能,我们也可以自定义 Angular 更改检测。将组件更改检测策略更新为OnPush

@Component({
    selector: 'todo-list',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: ...
})
export class TodoList {
    ...
}

现在让我们在应用程序中添加几个按钮:一个是通过直接改变列表的第一项来切换列表的第一项,另一个是向整个列表添加一个 Todo。代码如下所示:

@Component({
    selector: 'app',
    template: `<div>
                    <todo-list [todos]="todos"></todo-list>
               </div>
               <button (click)="toggleFirst()">Toggle First Item</button>
               <button (click)="addTodo()">Add Todo to List</button>`
})
export class App {
    todos:Array = initialData;

    constructor() {
    }

    toggleFirst() {
        this.todos[0].completed = ! this.todos[0].completed;
    }

    addTodo() {
        let newTodos = this.todos.slice(0);
        newTodos.push( new Todo(1, "TODO 4", 
            false, new Owner("John", "Doe")));
        this.todos = newTodos;
    }
}

现在让我们看看这两个新按钮的行为:

  • 第一个按钮“切换第一项”不起作用!这是因为该toggleFirst()方法直接改变了列表中的一个元素。
    TodoList无法检测到这一点,因为它的输入参考todos没有改变
  • 第二个按钮确实有效!请注意,该方法addTodo()创建了 todo 列表的副本,然后将项目添加到副本中,最后将 todos 成员变量替换为复制的列表。这会触发更改检测,因为组件检测到其输入中的参考更改:它收到了一个新列表!
  • 在第二个按钮中,直接改变 todos 列表是行不通的!我们真的需要一个新的清单。

OnPush只是通过引用比较输入吗?

情况并非如此。当使用 OnPush 检测器时,框架将在 OnPush 组件的任何输入属性更改、触发事件或 Observable 触发事件时检查

尽管允许更好的性能,但OnPush如果与可变对象一起使用,则使用会带来很高的复杂性成本。它可能会引入难以推理和重现的错误。但是有一种方法可以使使用OnPush可行。

使用 Immutable.js 简化 Angular 应用程序的构建

如果我们只使用不可变对象和不可变列表来构建我们的应用程序,则可以OnPush透明地在任何地方使用,而不会遇到更改检测错误的风险。这是因为对于不可变对象,修改数据的唯一方法是创建一个新的不可变对象并替换之前的对象。使用不可变对象,我们可以保证:

  • 新的不可变对象将始终触发OnPush更改检测
  • 我们不会因为忘记创建对象的新副本而意外创建错误,因为修改数据的唯一方法是创建新对象

实现不可变的一个不错的选择是使用Immutable.js库。该库为构建应用程序提供了不可变原语,例如不可变对象(映射)和不可变列表。

避免变更检测循环:生产与开发模式

Angular 更改检测的重要属性之一是,与 AngularJs 不同,它强制执行单向数据流:当我们的控制器类上的数据更新时,更改检测运行并更新视图。

如何在 Angular 中触发变更检测循环?

一种方法是如果我们使用生命周期回调。例如,在TodoList组件中,我们可以触发对另一个组件的回调来更改其中一个绑定:

ngAfterViewChecked() {
    if (this.callback && this.clicked) {
        console.log("changing status ...");
        this.callback(Math.random());
    }
}

控制台中将显示一条错误消息:

EXCEPTION: Expression '{{message}} in App@3:20' has changed after it was checked

仅当我们在开发模式下运行 Angular 时才会抛出此错误消息。如果我们启用生产模式会发生什么? 在生产模式下,错误不会被抛出,问题也不会被发现。

在开发阶段始终使用开发模式会更好,因为这样可以避免问题。这种保证是以 Angular 总是运行两次变更检测为代价的,第二次检测这种情况。在生产模式下,变更检测只运行一次。

打开/关闭变化检测,并手动触发它

在某些特殊情况下,我们确实想要关闭更改检测。想象一下这样一种情况,大量数据通过 websocket 从后端到达。我们可能只想每 5 秒更新一次 UI 的某个部分。为此,我们首先将更改检测器注入到组件中:

constructor(private ref: ChangeDetectorRef) {
    ref.detach();
    setInterval(() => {
      this.ref.detectChanges();
    }, 5000);
  }

正如我们所看到的,我们只是分离了变化检测器,这有效地关闭了变化检测。然后我们只需每 5 秒通过调用手动触发它detectChanges()

现在让我们快速总结一下我们需要了解的关于 Angular 变更检测的所有内容:它是什么,它是如何工作的以及可用的主要变更检测类型是什么。

概括

Angular 更改检测是一个内置的框架功能,可确保组件数据与其 HTML 模板视图之间的自动同步。

更改检测的工作原理是检测常见的浏览器事件,如鼠标点击、HTTP 请求和其他类型的事件,并确定每个组件的视图是否需要更新。

变更检测有两种类型:

  • 默认更改检测:Angular 通过比较事件发生前后的所有模板表达式值来决定是否需要更新视图,用于组件树的所有组件
  • OnPush 更改检测:这通过检测是否已通过组件输入或使用异步管道订阅的 Observable 将某些新数据显式推送到组件中来工作

Angular默认更改检测机制实际上与 AngularJs 非常相似:它比较浏览器事件之前和之后模板表达式的值,以查看是否有更改。它对所有组件都这样做。但也有一些重要的区别:

一方面,没有变化检测循环,也没有 AngularJs 中命名的摘要循环。这允许仅通过查看其模板和控制器来推理每个组件。

另一个区别是,由于变化检测器的构建方式,检测组件变化的机制要快得多。

最后,与 AngularJs 不同的是,变化检测机制是可定制的。

更多编程相关知识,请访问:编程教学!!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 7.1万人学习

Vue 教程
Vue 教程

共42课时 | 9.5万人学习

React 教程
React 教程

共58课时 | 6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号