说!你为什么升级

angularjs与angular

ts方面:

强类型语言约束,来减少运行时错误,在编写的时候就能发现

框架方面:

  • 编译器支持 AOT 模式,大大减少了包的大小和提高了应用的性能,之前是JIT模式,运行时编译
  • 重新设计的DI系统
    • angular1 应用程序中所有的依赖项都是单例,我们不能控制是否使用新的实例。状态不好维护
    • 命名空间冲突
    • 不能和模块加载器一起使用
    • DI 耦合度太高:angular1 中 DI 功能已经被框架集成了,我们不能单独使用它的 DI 特性
  • 新的组件和模块化方案,支持组件继承
  • 去除了scope,变化检测不再使用\$scope.\$watch()、\$scope.\$apply()、\$timeout() 等
  • 路由支持懒加载和预加载
  • 跨平台能力:为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口
    • ELementRef:
    • 等等
  • 并且angularjs 不再维护了,业务代码也不容易维护了
DI与IoC

IoC 是什么

Ioc - Inversion of Control , 即”控制反转”。在开发中, IoC 意味着你设计好的对象交给容器控制,而不是使用传统的方式,在对象内部直接控制。  

如何理解好 IoC 呢?理解好 IoC的关键是要明确”谁控制谁,控制什么,为何是反转(有反转就应该有正转),哪些方面反转了”,我们来深入分析一下。  

  • 谁控制谁,控制什么: 在传统的程序设计中,我们直接在对象内部通过 new 的方式创建对象,是程序主动创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?主要是控制外部资源获取。
  • 为何是反转了,哪些方面反转了: 有反转就有正转,传统应用程序是由我们自己在对象中主动控制去获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转了;哪些方面反转了?依赖对象的获取被反转了。

DI是什么

DI - Dependency Injection,即”依赖注入”:组件之间的依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

IoC 和 DI 有什么关系?

其实它们是同一个概念的不同角度描述

angularjs中的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var INSTANTIATING = {}, // 是否实例化中
providerSuffix = 'Provider', // provider后缀
path = []; // 依赖路径

var factory = function(serviceName, caller) { // 实例工厂
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined,
serviceName);
});

function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) { // 依赖对象已创建
if (cache[serviceName] === INSTANTIATING) {// 判断是否存在循环依赖
throw $injectorMinErr('cdep', 'Circular dependency found:
{0}',serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else { // 依赖对象未创建
try {
path.unshift(serviceName); // 用于跟踪依赖路径
cache[serviceName] = INSTANTIATING;
// 实例化 serviceName 对应的依赖对象并存储
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName]; // 实例化失败,从缓存中移除
}
throw err;
} finally {
path.shift();
}
}
}

getService 方法,如果实例存在,直接从缓存中拿出来。如果不存在就创建并缓存

angularjs实现DI的方式和缺点

  • 单例:由它的创建方式可以知道
  • 命名空间冲突:在系统中我们使用字符串来标识 service 的名称,假设我们在项目中已有一个 CarService,然而第三方库中也引入了同样的服务,这样的话就容易出现混淆
  • 未能和模块加载器结合: 在浏览器环境中,很多场景都是异步的过程,我们需要的依赖模块并不是一开始就加载好的,或许我们在创建的时候才会去加载依赖模块,再进行依赖创建,而 angualr 的 IoC 容器没法做到这点。

angular中的DI

虽然也是单例的,但是Angular 中注入器是有层级结构的,即创建完注入器,我们可以基于它创建它的子注入器。

  • resolveAndCreate() - 根据设置的 provider 数组创建注入器
  • resolveAndCreateChild() - 调用已有注入器对象上的该方法,创建子注入器
脏值检查

Zones

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

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

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
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

在angularjs中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Angular 1.x Demo</title>
<script src="//cdn.bootcss.com/angular.js/1.6.3/angular.min.js"></script>
</head>
<body ng-app="exeApp">
<div ng-controller="MainCtrl">
<h4>Hello {{ name }}</h4>
</div>
<script type="text/javascript">
angular.module('exeApp', [])
.controller('MainCtrl', ['$scope', function ($scope) {
$scope.name = 'Angular';

setTimeout(function () {
$scope.name = 'Angular 2';
}, 2000);
}]);
</script>
</body>
</html>

2639567102-58cd4c537232d_articlex

在angularjs中必须使用\$timeout或者$scope.\$digest()的方式来触发更新

在angular中

1
2
3
4
5
6
7
// 浏览器环境
window.setTimeout.toString()
"function setTimeout() { [native code] }"

// angular 环境
window.setTimeout.toString()
"function setTimeout(){return f(this, arguments)}"

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

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

NgZone

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 对所有组件执行变化检测,若发生变化则更新视图
JIT与AOT

在 Angular 应用程序中,包含了我们通过 Angular 提供的 API 实现的自定义指令。这些自定义指令对浏览器来说,都是无法识别的,因此每个 Angular 应用程序在运行前,都需要经历一个编译的阶段。

Just-in-Time 编译模式开发流程

  • 使用 TypeScript 开发 Angular 应用
  • 运行 tsc 编译 TypeScript 代码
  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等
  • 部署应用

应用部署后,当用户通过浏览器访问我们应用的时候,她将经历以下步骤(非严格 CSP):

  • 下载应用相关的资源,如 JavaScript 文件、图片、样式资源
  • Angular 启动
  • Angular 进入 JiT 编译模式,开始编译我们应用中的指令或组件,生成相应的 JavaScript 代码
  • 应用完成渲染

Ahead-Of-Time 编译模式开发流程

  • 使用 TypeScript 开发 Angular 应用
  • 运行 ngc 编译应用程序
    • 使用 Angular Compiler 编译模板,一般输出 TypeScript 代码
    • 运行 tsc 编译 TypeScript 代码
  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等
  • 部署应用

应用部署后,相比于 JIT 编译模式,在 AOT 模式下用户访问我们的应用,只需经历以下步骤:

  • 下载应用相关的资源,如 JavaScript 文件、图片、样式资源
  • Angular 启动
  • 应用完成渲染
特性 JIT AOT
编译平台 (Browser) 浏览器 (Server) 服务器
编译时机 Runtime (运行时) Build (构建阶段)
包大小 较大 较小
执行性能 - 更好
启动时间 - 更短

除此之外 AOT 还有以下优点:

  • 在客户端我们不需要导入体积庞大的 angular 编译器,这样可以减少我们 JS 脚本库的大小
  • 使用 AOT 编译后的应用,不再包含任何 HTML 片段,取而代之的是编译生成的 TypeScript 代码,这样的话 TypeScript 编译器就能提前发现错误。总而言之,采用 AOT 编译模式,我们的模板是类型安全的。
ELementRef

在应用层直接操作 DOM,就会造成应用层与渲染层之间强耦合,导致我们的应用无法运行在不同环境。

参考资料

https://segmentfault.com/a/1190000008754631