热门标签 | 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 组件中的变化检测器。


推荐阅读
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 先看看ElementUI里关于el-table的template数据结构:<template><el-table:datatableData><e ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文详细解析了JavaScript中相称性推断的知识点,包括严厉相称和宽松相称的区别,以及范例转换的规则。针对不同类型的范例值,如差别范例值、统一类的原始范例值和统一类的复合范例值,都给出了具体的比较方法。对于宽松相称的情况,也解释了原始范例值和对象之间的比较规则。通过本文的学习,读者可以更好地理解JavaScript中相称性推断的概念和应用。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • MPLS VP恩 后门链路shamlink实验及配置步骤
    本文介绍了MPLS VP恩 后门链路shamlink的实验步骤及配置过程,包括拓扑、CE1、PE1、P1、P2、PE2和CE2的配置。详细讲解了shamlink实验的目的和操作步骤,帮助读者理解和实践该技术。 ... [详细]
  • 本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]
  • 本文介绍了如何使用elementui分页组件进行分页功能的改写,只需一行代码即可调用。通过封装分页组件,避免在每个页面都写跳转请求的重复代码。详细的代码示例和使用方法在正文中给出。 ... [详细]
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社区 版权所有