AngularJS 2 尽管还在Alpha阶段,但主要功能和文档已经发布。让我我们了解下Angular 1 和 2 的区别,以及新的设计目标将如何实现。
Angular 2 当前仍处于 Alpha/开发预览阶段,但是主要功能和核心文档都已经可用了。让我们一起了解下 Angular 2 的设计目标,以及实现它们的计划:
- Angular 2 主要目标
- 更易于推论
- Angular 1 vs Angular 2 变化侦测
- 基于 Zones 的更透明的内部构件
- 改进的堆栈跟踪
- 大幅提升的性能 (以及原理)
- 改进的模块化
- 改进的依赖注入
- Web 组件友好 (如何达成以及原理)
- 支持影子 DOM
- 支持 Android 和 iOS 的原生移动渲染
- 支持服务端渲染
- 改进的可测试性
- 向 Angular 2 迁移的路径
- 总结
Angular 2 主要目标
Angular 2 的主要目标是创建一个简单易用并且快速工作的 web 框架。让我们看看这是如何达到的:
目标:更易于推论
在当前版本的 Angular 中,我们有时不得已对应特定的使用场景推论框架内部构建,比如必须推论应用事件初始化和摘要循环:
- 在 Angular 1 中没有摘要循环结束事件 (查看原因),因为这种事件可能会促发更多的变化,以至于使摘要循环持续下去
- 我们必须推论何时调用 $scope.apply 或 $cope.digest,而这并不总是容易的
- 有时我们必须调用 $timeoutto让Angular 结束摘要循环,当 DOM 稳定时再做一些操作
为了使 Angular 2 更易于推论,一个目标是创建更多开箱即用的透明内部构建。开始之前,让我们看看 Angular 1 的绑定机制是如何实现的,然后如何使它更透明。
Angular 1 如何实现绑定
Angular 1 这么流行的主要原因之一是,ng-model 功能可以使界面上的改动立即反应在一个简单 Javascript 对象上。
根据这个 podcast (查看 3:50 处),Angular 1 是这样完成此功能的:
Javascript 运行时中,每一样东西都是可以依设计打补丁的 – 如果需要我们可以改变 Number 类
Angular 在启动时会给所有的异步交互点打补丁:
超时
Ajax 请求
浏览器事件
Websockets,等等
在那些交互点,Angular 会对 scope 对象进行变动检查,如果发现有变动就激发相应的监视器
重新运行变动检查,检查是否有更多的变化发生,重新运行监视器,等等
Angular 1 绑定运行的后果
结果是 DOM 一直同简单 Javascript 对象进行同步,尽管这样可以工作,但是这使得有时难以进行推论:
- 不清楚哪些监视器会运行,什么顺序,多少次
- 模型更新顺序难以推论和预期
- 摘要循环多次运行导致时间消耗
Angular 团队制定 Angular 2 开发方向时,其中一点是提取 Angular 代码中的异步交互点补丁机制,以便可以重用它。
Zones 介绍
这些重构的结果就是 Zone.js,它类似于 Java 中的 thread-local 上下文。
他可以用于很多场景,比如可以允许框架生成更长的跨越多个 JavaScript VM 的堆栈跟踪信息。
Angular 2 如何因 Zones 而更透明
Angular 2 使用 zones 机制使摘要循环不再被需要。简单的非 Angular 指定代码可以透明地激发一个Angular 2 摘要,如下是由一个 zone 中的组件激发的示例:
element.addEventListener('keyup', function () {
console.log('Key pressed.');
}); });
不再需要 $scope.apply 或 $scope.digest,每样东西都透明地工作。或许我们不必推论出 zones 适用于大多数一般场景,但是可以通过使用 VmTurnZone 在 Angular zone 外运行代码。
目标: 提升性能
上面描述的消化周期明确表示,这一切都将会耗费时间,尽管很多性能在 Angular 1.3 和 Angular 1.4 版本中得到改进。
但不清楚哪些性能可以改进更多,原因之一是存在变化检测循环的可能性。
为了更好地理解如何实现性能提升(比 Angular 1 快5到10倍),参考了很多播客和博客 。我会尽量在这里总结 Angular 2 更快的两个主要原因:
更为快速的检测一个单向绑定
它提供了一项检测单向绑定的机制,这项机制可以允许 Javascript 虚拟机对于代码到源代码的实时编译进行优化和完善。相对于递归性扫描对像的变化,这份机制会创建一个方法,这个方法将在 Angular 启动时去检查这个绑定是否已经改变。有了这样的一个检测函数,我们很容易的自己亲手编写类似函数来测试绑定对象的变化,同时它也很容易被虚拟机优化。
避免扫描部分组件树
Angular2 也可以让开发者为变化检测机制做出相应的一些保障,而不用不断地扫描一部分的组件树。就基本上来说,开发者将有两个选择:
- 创建一个可见的对象:Angular 将会发现这个对象并且注册去观察这个对象。在这种状况下,如果这个对象发生改变或者保留原来的装态,Angular 将会通过观察机制获得消息,所以就不需要为这个对象运行变化检测机制。
- 创建一个不可见的对象, 可以使用 Facebook 提供的 immutable.js。 同样的,Angular 也会检测到这个对象,而且也不需要变化检测机制去检测这个不可见的对象。
目标: 提升模块化
在 Angular 1 中,Angular 的模块几乎都依赖于注入容器以及其他相关功能。
这些模块的例子都不是异步加载的,以 AMD 模块为例,根据他们的依赖性列出第一次的加载所需的依赖。
Angular 1 和模块懒加载
Angular 1 的懒加载是类似于 ocLazyLoad 方式的解决方案,但是理想情况下应该是本地框架能更易懂,这在这个播客的 (13:06)的地方,Angular 2 也是这样的情况。
将 npm 优化为前端包的管理工具
同时 Angular 团队一直尝试改进 NPM,让它变得更加前端友好化;不仅仅是对于 Angular 而言,同时也是对于任何前端 library 而言。
而 Angular 2 则没有这样的问题,假如我们选择npm, 我们完全可以利用新型的ES6 模块加载器,ES6通过利用es6-module-loader pollyfill 使其变成一个标准的同步模块加载器。
目标: 改进依赖注入
在Angular 1 的世界里,依赖注入在构建多模块应用时是一项技术的飞跃, 但是在一些极端的案例中,如果不做出一些重要的变化是不能解决这些问题的。
Angular 1 包含对象全局池
Angular 1 其中一个 DI 案例中每个应用仅有一个对象全局池。这就意味着,如果说主路由 loadsbackendService 我们导向了路由 B,可以延迟加载其他服务指定到这个路由。
问题就是,我们说可以延迟加载一个 secondbackendService,使用完全不同的实现:这就会重写第一个!当前还没有办法同一名字有两个不同实现的两个服务,这就会阻止用一个安全的方式从 Angular 1 实现延迟加载。
Angular 1 会静默重写模块,当他们有相同的名字
这是一个特性,允许在测试的时候模拟替换服务层的服务,但是如果恰巧在同一模块加载了两次就会发生问题。
Angular 1 的多重依赖注入机制
在 Angular 1 中, 我们可以使用在多重地方使用不同的方法进行注入:
- 在链接方法中通过位置注入
- 在直接定义中通过名字注入
- 在controller方法中通过名字,等等。
Angular 2 将会作出怎样的该进
而在 Angular 2 中有且仅有一种依赖注入机制: 在构造函数中通过类型注入。
constructor(keyUtils: KeyboardUtils) {
this.keyUtils = keyUtils; } });
事实上,如果只有一种机制,那么它将变得更加容易学习。同时这种依赖注入器是类似层级结构,在不同层次的组件树,有可能实现对相同类型的不同实现。
如果一个组件没有定义依赖,它会代理给上层注入器查找依赖,依次往上。这让 Angular 2 提供原生的懒加载成为可能。
目标:对 Web Component 友好
Web Components 是 web 的未来,Angular 2 一开始就打算跟未来的的 web component 库良好协作。 为此,Angular 2 模板语法的一个目标就是保持特性定义简洁,不将任何 Angular 表达式置于其中 —— 一切都通过属性绑定。
为了理解为什么这个很重要,来看下面的例子:
<ng1-component>
<web-component-widget setting="{{angularExpresssion}}"></web-component-widget>
</ng1-component>
这里有个跟未来 web component 交互的 Angular 1 组件。
这里有什么问题呢?
web component 的行为跟浏览器组件的行为类似,比如有 img 标签。
因此,在页面初始化并且在 Angular 介入之前,Angular 表达式将被传给组件,并直接作用于它。比如 image 元素用提供的 url 立即加载图片。
这也是为什么需要像 ng-src 这样的属性来克服这个问题。
Angular 2 如何做到更好地跟 Web Components 交互?
Angular 2 的模板语法会避免绑定到普通属性,除非要读取常量:
<ng2-component> <web-component-widget [setting]="angularExpresssion"></web-component-widget> </ng2-component>
[setting] 是一个往组件属性写入表达式值的属性绑定。为了避免跟 web component 互操作问题,在普通属性里绝不会出现 Angular 表达式。
支持 Shadow DOM
Web 组件的主要特征之一就是 Shadow DOM。 这是浏览器自身的一种机制,它允许构建本地进行查找组件,看起来是select新的一种实现方式。
一个web组件还是可以通过正常的HTML/CSS 脚本实现,但是同时从主页面隔离了。在某种程度上来说,就像是在同一个iframe里拥有各自的document根节点。
由于现阶段只有 Chrome 才实现了 Shadow DOM, Angular 2 通过以下3种机制来支持它:
- 默认方式:默认情况下,Shadow DOM 不会和内部组件同时出现在同一个组件树来做为主页面。
- 模拟Shadow DOM:Shadow DOM CSS 隔离机制可以通过 Polymer 实现,这个类库可以使的组件中的CSS动态地加上前缀,使得CSS更加清晰明白。
- 真正的Shadow DOM: 正如上文说的那样,只有在 Chrome 浏览器中工作
目标:原生移动支持 – iOS 和 Android
Angular 2 会有两层,应用层和渲染层。例如一个组件可以用不同的 @View 修饰器修饰,根据运行环境可以在运行时生效。
与 React Native 一样,Angular 2 支持:
一次学习,到处书写。
这意味着创建原生应用时可以重用你在创建 web 应用时学习的知识。尽管总是有些区别。
目标:为服务器端渲染提供支持
支持服务器端的渲染对于搜索引擎的优化和用户感知体验来说是非常重要的;在一个比较大型的Angular 1 的应用中,即使使用了预先定义的缓存模块,我们可以清楚地看到当应用开始启动时,页面的加载过程。
这时候看来 Angualr2 的这部分特征不是很清晰明朗,但是这种思路或许可以从以下几个方面得到体现:
- 启动开始, 同时所有的组件都被绑定
- 而渲染没有实现
- 一个页面在服务器被渲染后 , 然后发送到客户端
- Angular 将会把它解析 ,接着会吧解析后的页面注入到 DOM 中,这样就避免了出现闪烁的效果
目标: 增加测试可行性
相对而言 Angular 2 很难写真正的单元测试, 因为像 ng-model 真的需要一个 DOM 做测试,这导致这个方案就像使用 PhantomJs.
这个方式产生的问题是这种测试不再是单元测试,这种集成测试有下列问题:
- 执行缓慢
- 脆弱难以维护
这些问题导致一个倒置的 test pyramid, 进而我们大部分测试,包括UI测试,集成测试很难做到真正的单元测试。这意味着构造不断被真正 bug 之外的东西打破,测试努力收效甚微。
引入独立的渲染层会使单元测试更快,依赖更少,更方便代码的书写和维护,可以更频繁地使用。
目标: 迁移到 Angular 2
Angular 2 的目标之一是为 Angualr 1 提供一个清晰的迁移路径。Angular 2 最初版本发布临近时这会变得更加清晰,但是现在路由可能是一个主要的可行迁移办法。
新的 Angular 2 路由向下兼容 Angular 1,将允许一个工程同时有 Angualr 1 和 Angular 2 路由 。
结论
我真的为 Angular 2 感到兴奋,在尝试几个组件之后,我可以看到它是如何的简单易学,对开发者更加透明。很多事情就像这个文章前面说过的,像 Zones 很容易使用。
与第三方库的集成大大改进了,如果 npm 也做一些改进对前端代码的改进就是巨大的。
想尝试吗?
最好不要马上尝试,如果你想试试,这儿有一个seed project, Visual Studio Code editor 或者 Webstorm 已经提供 Typescript 1.5 支持.