目录
什么是 Change Detection ?
Change Detection 是如何实现的
覆盖浏览器默认机制
支持浏览器异步 API
默认的变更检测机制是如何工作的?
变更检测器是什么样的?
那么嵌套对象owner呢?
为什么默认情况下更改检测会这样工作?
OnPush 变化检测策略
OnPush只是通过引用比较输入吗?
使用 Immutable.js 简化 Angular 应用程序的构建
避免变更检测循环:生产与开发模式
如何在 Angular 中触发变更检测循环?
打开/关闭变化检测,并手动触发它
概括
首页 web前端 js教程 浅析Angular中的Change Detection机制

浅析Angular中的Change Detection机制

Dec 15, 2022 pm 09:20 PM
前端 angular.js change detection

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

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

OnPush 变化检测策略

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

@Component({
    selector: &#39;todo-list&#39;,
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: ...
})
export class TodoList {
    ...
}
登录后复制

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

@Component({
    selector: &#39;app&#39;,
    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 &#39;{{message}} in App@3:20&#39; 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 不同的是,变化检测机制是可定制的。

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

以上是浅析Angular中的Change Detection机制的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

一文聊聊Node中的内存控制 一文聊聊Node中的内存控制 Apr 26, 2023 pm 05:37 PM

基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。在海量请求的前提下,就需要考虑“内存控制”的相关问题了。 1. V8的垃圾回收机制与内存限制 Js由垃圾回收机

探讨如何在Vue3中编写单元测试 探讨如何在Vue3中编写单元测试 Apr 25, 2023 pm 07:41 PM

当今前端开发中,Vue.js 已经成为了一个非常流行的框架。随着 Vue.js 的不断发展,单元测试变得越来越重要。今天,我们将探讨如何在 Vue.js 3 中编写单元测试,并提供一些最佳实践和常见的问题及解决方案。

深入聊聊Node中的File模块 深入聊聊Node中的File模块 Apr 24, 2023 pm 05:49 PM

文件模块是对底层文件操作的封装,例如文件读写/打开关闭/删除添加等等 文件模块最大的特点就是所有的方法都提供的**同步**和**异步**两个版本,具有 sync 后缀的方法都是同步方法,没有的都是异

如何解决跨域?常见解决方案浅析 如何解决跨域?常见解决方案浅析 Apr 25, 2023 pm 07:57 PM

跨域是开发中经常会遇到的一个场景,也是面试中经常会讨论的一个问题。掌握常见的跨域解决方案及其背后的原理,不仅可以提高我们的开发效率,还能在面试中表现的更加

PHP与Vue:完美搭档的前端开发利器 PHP与Vue:完美搭档的前端开发利器 Mar 16, 2024 pm 12:09 PM

PHP与Vue:完美搭档的前端开发利器在当今互联网高速发展的时代,前端开发变得愈发重要。随着用户对网站和应用的体验要求越来越高,前端开发人员需要使用更加高效和灵活的工具来创建响应式和交互式的界面。PHP和Vue.js作为前端开发领域的两个重要技术,搭配起来可以称得上是完美的利器。本文将探讨PHP和Vue的结合,以及详细的代码示例,帮助读者更好地理解和应用这两

深入了解Node中的Buffer 深入了解Node中的Buffer Apr 25, 2023 pm 07:49 PM

最开始的时候 JS 只在浏览器端运行,对于 Unicode 编码的字符串容易处理,但是对于二进制和非 Unicode 编码的字符串处理困难。并且二进制是计算机最底层的数据格式,视频/音频/程序/网络包

如何使用 Go 语言进行前端开发? 如何使用 Go 语言进行前端开发? Jun 10, 2023 pm 05:00 PM

随着互联网技术的发展,前端开发变得日益重要。尤其是移动端设备的普及,更需要高效、稳定、安全又易维护的前端开发技术。而作为一门快速发展的编程语言,Go语言已经被越来越多的开发者所使用。那么,使用Go语言进行前端开发行得通吗?接下来,本文将为你详细说明如何使用Go语言进行前端开发。先来看看为什么使用Go语言进行前端开发。很多人认为Go语言是一门

C#开发经验分享:前端与后端协同开发技巧 C#开发经验分享:前端与后端协同开发技巧 Nov 23, 2023 am 10:13 AM

作为一名C#开发者,我们的开发工作通常包括前端和后端的开发,而随着技术的发展和项目的复杂性提高,前端与后端协同开发也变得越来越重要和复杂。本文将分享一些前端与后端协同开发的技巧,以帮助C#开发者更高效地完成开发工作。确定好接口规范前后端的协同开发离不开API接口的交互。要保证前后端协同开发顺利进行,最重要的是定义好接口规范。接口规范涉及到接口的命

See all articles