ZoneJS 的原理与应用
目录
- 序言
- Zone 是什么
- ZoneJS 的原理
- ZoneJS 的应用场景
- 参考
1. 序言
ZoneJS 是 Angular 团队受到 Dart 的 Zone 的启发,为 Angular v2 及其以上版本设计的核心模块。Angular 通过引入 ZoneJS 使得其变更检测机制更加简单与可靠。
2. Zone 是什么
在 ZoneJS 中有一个核心概念:Zone(域)。一个 Zone 表示一个 JavaScript 执行过程的上下文,其可以在异步任务之间进行持久性传递。
先看一个示例:
import 'zone.js'
const rootZone = Zone.current;
const zoneA = rootZone.fork({name: 'A'});
expect(Zone.current).toBe(rootZone);
setTimeout(function timeoutCb1() {
// 此回调在 rootZone 中执行
expect(Zone.current).toEqual(rootZone);
}, 0);
// 执行 run 方法,将切换 Zone.current 所保存的 Zone
zoneA.run(function run1() {
expect(Zone.current).toEqual(zoneA);
// setTimeout 在 zoneA 中被调用
setTimeout(function timeoutCb2() {
// 此回调在 zoneA 中执行
expect(Zone.current).toEqual(zoneA);
}, 0);
});
// 退出 zoneA.run 后,将切换回之前的 Zone
expect(Zone.current).toBe(rootZone);
在上述代码中:
Zone.current
是Zone
上的一个静态属性,用来保存全局此刻正在使用的Zone
。Zone.run()
方法将切换Zone.current
所保存的Zone
。- Zones 之间的关系:
最初的 rootZone
是 ZoneJS 默认创建的一个 Zone 实例。而通过 Zone.fork()
方法,可以再创建子Zone
(这也是一个 Zone 实例,因此可以继续调用 fork()
方法创建子 Zone
,而其parent
属性将关联创建其的父 Zone),这些 Zones
最终可以形成一个树形结构。
const rootZone = Zone.current;
const zoneA = rootZone.fork({name: 'A'});
const zoneB = rootZone.fork({name: 'B'});
const zoneC = zoneA.fork({name: 'C'});
上述代码中的 Zones 之间的关系如下图所示:
从上图中也可以看出,这些 Zones 形成的树形结构是一颗有唯一根节点的树。
3. ZoneJS 的原理
ZoneJS 通过 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]);
};
简单来说,ZoneJS 在加载时,对所有异步接口进行了封装,因此所有在 Zone 中执行的异步方法都会被当做为一个 Task 被其统一监管,并且提供了相应的钩子函数(hooks),用来在异步任务执行前后或某个阶段做一些额外的操作,因此可以实现:记录日志、监控性能、附加数据到异步执行上下文中等。
而这些钩子函数(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)
});
});
最终执行结果:
4. ZoneJS 的应用场景
ZoneJS 的应用场景有很多,例如:
- 可以用于开发调试、错误记录、分析和测试
- 可以让框架知道什么时候可以重新渲染(在 Angular 中的应用)
- 可以实现异步 Task 跟踪以及自动释放和清理资源
- 等等
这里举几个比较实用的例子:
1. 在测试中的应用:不允许异步代码
const syncZoneSpec = {
name: 'SyncZone',
onScheduleTask: function() {
throw new Error('No Async work is allowed in test.'); // 如果存在异步任务调度,将抛出异常
}
}
function sync(fn) {
return function(...args) {
Zone.current.fork(syncZoneSpec).run(fn, args, this);
}
}
it('should fail when doing async', sync(() => {
Promise.resolve('value');
}));
上述实现可以用来保证测试的代码中没有异步方法被调用。
2. 用于性能分析:监听异步方法的执行时间
const executeTimeZoneSpec = {
name: 'executeTimeZone',
onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {
console.time('scheduleTask')
return parentZoneDelegate.scheduleTask(targetZone, task);
},
onInvokeTask: function (parentzone, currentZone, targetZone, task, applyThis, applyArgs) {
console.time('callback')
parentzone.invokeTask(targetZone, task, applyThis, applyArgs);
console.timeEnd('callback')
console.timeEnd('scheduleTask')
}
}
Zone.current.fork(executeTimeZoneSpec).run(() => {
setTimeout(function () {
console.log('start callback...')
for (let i = 0; i < 100; i++) {
console.log(i)
}
}, 1000);
});
// start callback...
// 0
// ...
// 100
// callback: 12.2890625ms
// scheduleTask: 1015.6650390625ms
在 JavaScript 中类似 setTimeout
这种异步调用,其回调执行的时机很难确定,想要直接监控其执行时间一般来说是比较苦难的,而通过引入 ZoneJS 则可以很容易实现这点。
3. 在框架中的应用:实现自动重新渲染
class VMTurnZoneSpec {
constructor(vmTurnDone) {
this.name = 'VMTurnZone';
this.vmTurnDone = vmTurnDone;
this.hasAsyncTask = false
}
onHasTask(delegate, current, target, hasTaskState) {
const { microTask, macroTask, eventTask } = hasTaskState
this.hasAsyncTask = microTask || macroTask || eventTask;
if (!this.hasAsyncTask) {
this.vmTurnDone();
}
}
onInvokeTask(parent, current, target, task, applyThis, applyArgs) {
try {
return parent.invokeTask(target, task, applyThis, applyArgs);
} finally {
if (!this.hasAsyncTask) {
this.vmTurnDone();
}
}
}
}
上述代码中的ZoneSpec
可以用来检查异步任务是否执行完毕,然后触发对应的回调方法。而像 Angular 这种框架,正是需要知道什么时候所有的任务执行完毕以此来执行 DOM 更新(变更检测)。
5. 参考
ZoneJS 的原理与应用的更多相关文章
- Angular 的性能优化
目录 序言 变更检查机制 性能优化原理 性能优化方案 小结 参考 序言 本文将谈一谈 Angular 的性能优化,并且主要介绍与运行时相关的优化.在谈如何优化之前,首先我们需要明确什么样的页面是存在性 ...
- Angular ZoneJS 原理
Zone.js到底是如何工作的? 原文链接: blog.kwintenp.com 如果你阅读过关于Angular 2变化检测的资料,那么你很可能听说过zone.Zone是一个从Dart中引入的特性并被 ...
- 奇异值分解(SVD)原理与在降维中的应用
奇异值分解(Singular Value Decomposition,以下简称SVD)是在机器学习领域广泛应用的算法,它不光可以用于降维算法中的特征分解,还可以用于推荐系统,以及自然语言处理等领域.是 ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
- 线性判别分析LDA原理总结
在主成分分析(PCA)原理总结中,我们对降维算法PCA做了总结.这里我们就对另外一种经典的降维方法线性判别分析(Linear Discriminant Analysis, 以下简称LDA)做一个总结. ...
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- H5单页面手势滑屏切换原理
H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...
- .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理
.NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...
- python自动化测试(2)-自动化基本技术原理
python自动化测试(2) 自动化基本技术原理 1 概述 在之前的文章里面提到过:做自动化的首要本领就是要会 透过现象看本质 ,落实到实际的IT工作中就是 透过界面看数据. 掌握上面的这样的本领 ...
随机推荐
- P1136 迎接仪式 题解
题目描述 LHX教主要来X市指导OI学习工作了.为了迎接教主,在一条道路旁,一群Orz教主er穿着文化衫站在道路两旁迎接教主,每件文化衫上都印着大字.一旁的Orzer依次摆出"欢迎欢迎欢迎欢 ...
- QSignalMapper的使用和使用场景
目录 QSignalMapper的使用和使用场景 常见场景 下面是参考.可看可不看 这篇写的不错,搬运为Markdown了 可以看一下 参考 QSignalMapper的使用和使用场景 QSignal ...
- Cyber Security - Palo Alto Security Policies(1)
Security policies: Enforcing network traffic by configuring rules of what is allowed or denied to co ...
- 抽象工厂模式(c++实现)
抽象工厂模式 目录 抽象工厂模式 模式定义 模式动机 UML类图 源码实现 优点 缺点 感悟 模式定义 抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而 ...
- django-rest-framework-源码解析003-视图家族和路由(APIView/GenericAPIView/mixins/generics/viewsets)
视图家族 视图家族在rest_framework源码位置和学习曲线为: rest_framework.views: 基本视图(APIView) rest_framework.generics: 工具视 ...
- 数据库(二):初识sql语句
进击のpython ***** 数据库--初识sql语句 前面提到了说,数据库管理系统就像我们曾经做过的输入命令返回结果的socket通信差不多 那既然提到了命令,在MySQL中,有一些基本的语句,就 ...
- 爬虫(三)-之Urllib库的基本使用
什么是Urllib Urllib是python内置的HTTP请求库 包括以下模块 urllib.request 请求模块 urllib.error 异常处理模块 urllib.parse url解 ...
- UDP 绑定信息
""" 建立->绑定本地ip地址和端口号->接收数据->转码输出->关闭客户端 """ from socket im ...
- 一图看懂华为云DevCloud如何应对敏捷开发的测试挑战
作为敏捷开发中测试团队的一员,在微服务测试过程中,你是不是也遇到同样困惑:服务不具备独立验证能力.自动化用例开发效率很低等? 华为云DevCloud API全场景测试技术来支招~围绕API的全场景,打 ...
- PHP imagecolorallocatealpha - 为一幅图像分配颜色和透明度
imagecolorallocatealpha — 为一幅图像分配颜色和透明度.高佣联盟 www.cgewang.com 语法 int imagecolorallocatealpha ( resour ...