热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Angular2ChangeDetection1

阅读Angular6RxJS最新教程,请访问前端修仙之路ChangeDetection(变化检测)是Angular2中最重要的一个特性。当组件中的数据发生变化的时候,Angular

阅读 Angular 6/RxJS 最新教程,请访问
前端修仙之路

Change Detection (变化检测) 是 Angular 2 中最重要的一个特性。当组件中的数据发生变化的时候,Angular 2 能检测到数据变化并自动刷新视图反映出相应的变化。

在介绍变化检测之前,我们要先介绍一下浏览器中渲染的概念,渲染是将模型映射到视图的过程。模型的值可以是 Javascript 中的原始数据类型、对象、数组或其他数据对象。然而视图可以是页面中的段落、表单、按钮等其他元素,这些页面元素内部使用 DOM (Document Object Model) 来表示。

《Angular 2 Change Detection - 1》

为了更好地理解,我们来看一个具体的示例:


这个例子很简单,因为模型不会变化,所以页面只会渲染一次。如果数据模型在运行时会不断变化,那么整个过程将变得复杂。因此为了保证数据与视图的同步,页面将会进行多次渲染。接下来我们来考虑一下以下几个问题:

  • 什么时候模型会发生变化
  • 模型产生了什么变化
  • 变化后需要更新的视图区域在哪里
  • 怎么更新对应视图区域

而变化检测的基本目的就是解决上述问题。在 Angular 2 中当组件内的模型发生变化的时候,组件内的变化检测器就会检测到更新,然后通知视图刷新。因此变化检测器有两个主要的任务:

  • 检测模型的变化
  • 通知视图刷新

接下来我们来分析一下什么是变化,变化是怎么产生的。

变化和事件

变化是旧模型与新模型之间的区别,换句话说变化产生了一个新的模型。让我们来看一下下面的代码:

import { Component } from '@angular/core';
@Component({
selector: 'exe-counter',
template: `

当前值:{{ counter }}


`
})
export class CounterComponent {
counter = 0;
countUp() {
this.counter++;
}
}

页面首次渲染完后,计数器的当前值为0。当我们点击 + 按钮时,计数器的 counter 值将会自动加1,之后页面中当前值也会被更新。在这个例子中,点击事件引起了 counter 属性值的变化。

我们继续看下一个例子:

import { Component, OnInit } from '@angular/core';
@Component({
selector: 'exe-counter',
template: `

当前值:{{ counter }}


`
})
export class CounterComponent implements OnInit {
counter = 0;
ngOnInit() {
setInterval(() => {
this.counter++;
}, 1000);
}
}

该组件通过 setInterval 定时器,实现每秒钟 counter 值自动加1。在这种情况下,它是定时器事件引起了属性值的变化。最后我们再来看个例子:

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'exe-counter',
template: `

当前值:{{ counter }}


`
})
export class CounterComponent implements OnInit {
counter = 0;
constructor(private http: Http) {}
ngOnInit() {
this.http.get('/counter-data.json')
.map(res => res.json())
.subscribe(data => {
this.counter = data.value;
});
}
}

该组件在进行初始化的时候,会发送一个 HTTP 请求去获取初始值。当请求成功返回的时候,组件的 counter 属性的值会被更新。在这种情况下,它是由 XHR 回调引起了属性值的变化。

现在我们来总结一下,引起模型变化的三类事件源:

  • Events:click, mouseover, keyup …
  • Timers:setInterval、setTimeout
  • XHRs:Ajax(GET、POST …)

这些事件源有一个共同的特性,即它们都是异步操作。那我们可以这样认为,所有的异步操作都有可能会引起模型的变化。

非常好,你已经了解了引起模型变化的事件源和触发变化的时机点。但是你还不知道,是由谁来负责通知相应的变化给视图。接下来,我们将讨论一种允许 Angular 随时检测到变化的机制,它被称为 Zone

Zones

Zone 是下一个 ECMAScript 规范的建议之一。Angular 团队实现了 Javascript 版本的 zone.js ,它是用于拦截和跟踪异步工作的机制。

Zone 是一个全局的对象,用来配置有关如何拦截和跟踪异步回调的规则。Zone 有以下能力:

  • 拦截异步任务调度
  • 提供了将数据附加到 zones 的方法
  • 为异常处理函数提供正确的上下文
  • 拦截阻塞的方法,如 alert、confirm 方法

我们来看一个简单的示例:

Zone.current.fork({}).run(function () {
Zone.current.inTheZOne= true;
setTimeout(function () {
console.log('in the zone: ' + !!Zone.current.inTheZone);
}, 0);
});
console.log('in the zone: ' + !!Zone.current.inTheZone);

以上代码运行后的结果是:

in the zone: false
in the zone: true

是不是感觉很神奇!在Angular 2 中,有一个 NgZone,它是专门为 Angular 2 定制的 zone。在正式介绍它之前,我们先来看一下 Angular 1.x 中的一个例子:










Hello {{ name }}





以上代码运行后的输出结果:

《Angular 2 Change Detection - 1》

用过 Angular 1.x 的同学,应该很清楚可以通过 Angular 1.x 中的 $timeout 服务或手动调用 $scope.$digest() 方法来通知视图刷新。这对初学者来说,是很麻烦的一件事情。你们应该还记得前面计数器组件的例子,我们通过 setInterval 定时器,实现每秒钟 counter 值自动加1,页面就自动刷新了。不需要再使用 Angular 1.x 中的 $timeout 服务或手动调用 $scope.$digest() 方法来刷新视图。

为什么我们都是使用定时器,而在 Angular 2 中模型发生变化后,却能自动通知视图进行刷新呢 ?我们来分析一下,首先在浏览器中新开一个 Tab 页,在控制台输入:

window.setTimeout.toString()
"function setTimeout() { [native code] }"

然后再打开一个 Angular 2 应用的页面,在控制台同样输入:

window.setTimeout.toString()
"function setTimeout(){return f(this, arguments)}"

我们发现在 Angular 2 中,setTimeout 方法已经被重写了,最简单的实现方式如下:

var originSetTimeout = window.setTimeout;
window.setTimeout = function(fn, delay) {
console.log('setTimeout has been called');
originSetTimeout(fn, delay);
}

其实在 Angular 2 应用程序启动之前,Zone 采用猴子补丁 (Monkey-patched) 的方式,将 Javascript 中的异步任务都进行了包装,这使得这些异步任务都能运行在 Zone 的执行上下文中,每个异步任务在 Zone 中都是一个任务,除了提供了一些供开发者使用的钩子外,默认情况下 Zone 重写了以下方法:

  • setInterval、clearInterval、setTimeout、clearTimeout
  • alert、prompt、confirm
  • requestAnimationFrame、cancelAnimationFrame
  • addEventListener、removeEventListener

Zone 内部源码片段:

var set = 'set';
var clear = 'clear';
var blockingMethods = ['alert', 'prompt', 'confirm'];
var _global = typeof window === 'object' && window ||
typeof self === 'object' && self || global;
patchTimer(_global, set, clear, 'Timeout');
patchTimer(_global, set, clear, 'Interval');
patchTimer(_global, set, clear, 'Immediate');
patchTimer(_global, 'request', 'cancel', 'AnimationFrame');
patchTimer(_global, 'mozRequest', 'mozCancel', 'AnimationFrame');
patchTimer(_global, 'webkitRequest', 'webkitCancel', 'AnimationFrame');

NgZone

NgZone 是基于 Zone 实现的,它是Zone派生出来的一个子Zone,在 Angular 环境内注册的异步事件都运行在这个子 Zone 内 (因为NgZone拥有整个运行环境的执行上下文),它扩展了自有的一些 API 并添加了一些功能性的方法到它的执行上下文中。

在 Angular 源码中,有一个 ApplicationRef_ 类,其作用是用来监听 NgZone 中的 onMicrotaskEmpty 事件,无论何时只要触发这个事件,那么将会执行一个 tick 方法用来告诉 Angular 去执行变化检测,简化版的代码如下:

class ApplicationRef {
private _views: InternalViewRef[] = [];
constructor(private zone: NgZone) {
this.zone.onMicrotaskEmpty.subscribe(() => {
this.zone.run(() => {
this.tick();
});
});
}
tick() {
if (this._runningTick) {
throw new Error('ApplicationRef.tick is called recursively');
}
this._views.forEach((view) => view.detectChanges());
}
}

现在我们先来总结一下前面所讲的内容:

  • 异步操作被安排为任务
  • Zones 跟踪任务的执行
  • Angular 处理由执行异步操作引起的事件
  • Angular 对所有组件执行变化检测,若发生变化则更新视图

要完全理解 Zone 的工作原理是比较困难的,对我们大部分的人来说,只要知道 Angular 内部是通过它来跟踪异步任务,然后执行变化检测任务就可以了。

我有话说

1.在 Angular 2 项目中怎么访问 Zone 打补丁前的方法,如 setTimeout、clearTimeout 等

因为 Zone 内部通过内建的 __symbol__ 函数来模拟 Symbol :

function __symbol__(name) {
return '__zone_symbol__' + name;
}

因此我们可以在浏览器的控制台中运行:

Object.keys(window).forEach((key) => {
if(key.indexOf('zone_symbol') > 0) {
console.log(key);
}
});

运行后控制台的输出结果如下:

《Angular 2 Change Detection - 1》

2.前面介绍 Zone 使用的示例,为什么控制台会输出那样的结果 ?

// 加载Zone.js给浏览器中的一些异步操作打上补丁
// 创建Root Zone
// 调用Zone.current对象上的fork方法创建新的zone,我们称之为childZone
Zone.current.fork({}).run(function () {
// 运行run方法,Zone.current被设置为函数被执行时所属的Zone,即childZone
Zone.current.inTheZOne= true;
// 这里注册了一个定时器。由于被打过了猴子补丁,这里调用的并不是
// 浏览器"默认"的setTimeout方法。因此,这里实际上是在配置代理。这里
// 要重点指出的是这个代理会保留一个指向创建时所属Zone的引用即childZone,
// 稍后会用到这个引用。
setTimeout(function () {
// 定时时间到,此时的Zone.current的值会被重置为childZone
console.log('in the zone: ' + !!Zone.current.inTheZone);
}, 0);
// 代码执行完 Zone.current属性被重置为Root Zone
// Zone的生命周期里的钩子函数会被触发
});
console.log('in the zone: ' + !!Zone.current.inTheZone);

如果还是不好理解的话,我们可以想象一下同步的过程:

const rootZOne= Zone.current;
// 创建一个新的Zone
const childZOne= Zone.current.fork({});
// 设置当前的zone
Zone.current = zone;
// 为当前的zone添加inTheZone属性
Zone.current.inTheZOne= true;
console.log('in the zone: ' + !!Zone.current.inTheZone);
// 退出当前的zone
Zone.current = rootZone;
console.log('in the zone: ' + !!Zone.current.inTheZone);

总结

这篇文章我们先介绍了浏览器中渲染的概念,然后通过三个示例引出了引起模型变化的事件源并总结了它们之间的共性,此外我们还介绍了 Angular 1.x 项目中初学者容易遇到的问题,并基于该问题引入了 Zone 和 NgZone 的概念,最后我们简单介绍了 Zone.js 的内部工作原理。下一篇文章我们将详细介绍 Angular 2 组件中的变化检测器。


推荐阅读
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 版本控制工具——Git常用操作(下)
    本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复 ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 本文详细介绍了如何在Kendo UI for jQuery的数据管理组件中,将行标题字段呈现为锚点(即可点击链接),帮助开发人员更高效地实现这一功能。通过具体的代码示例和解释,即使是新手也能轻松掌握。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 深入解析ESFramework中的AgileTcp组件
    本文详细介绍了ESFramework框架中AgileTcp组件的设计与实现。AgileTcp是ESFramework提供的ITcp接口的高效实现,旨在优化TCP通信的性能和结构清晰度。 ... [详细]
  • 本文介绍了如何在React和React Native项目中使用JavaScript进行日期格式化,提供了获取近7天、近半年及近一年日期的具体实现方法。 ... [详细]
  • 主调|大侠_重温C++ ... [详细]
author-avatar
捕风的丶阿兹猫-在北京
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有