Angular 1.3 版本终于放出,在更新了许多新特性的同时也修复了许多bug并且带来性能提升。为了帮助自己也帮助别人更早理解这些新特性,接下来将会有一个系列文章去介绍这些主要的新特性和改进。本片文章是这个系列的第一篇,将介绍这其中最重要的一个新特性:one-time bindng。

唉,先别急!我记得Angular的数据绑定是自动保持UI同步更新的啊?没错,这个特点非常重要,但不一定所有地方都需要。这种数据绑定的方式需要框架时刻监视着所有绑定过的数据,这种方式真的很消耗性能。one-time binding 正是为解决此问题而生的。在介绍 one-time binding 之前,先理解一下数据绑定和watcher的的概念。

理解数据绑定和watchers

为了实现数据绑定,Angular 使用了 $watch API去观察 scope 上的数据的改动。其中 scope 具体是什么如何形成的取决于你的代码。如果你没有手动创建一个 child scope ,例如通过 ngController 指令去创建,那么你可能是在和 $rootScope 打交道,这个 $rootScope 是指当前应用的一般通过 ngApp 指令创建的根 scope。

和 scope 打交道并观察其中的变化一般总是要用到所谓的 watcher 。Watchers 通过 DOM 中的 directives 注册。比方说通过直接 interpolation 指令去同步 scope 模型的值:

<p>Hello {{name}}!</p>

这个 interpolation 指令注册了一个用于 $rootScope 上 name 属性的 watcher,从而可以更新绑定了这个属性值的DOM。

我们可以在 socpe 上通过标识符直接定义一个属性并同时给它赋值,这样它就可以直接在DOM中显示:

angular.module('maApp', [])
.run(function($rootScope) {
  $rootScope.name = 'Pascal';
});

我们刚刚通过 interpolation 指令在 view 上绑定了一了一个 model 值,如果改变它的值,view 也会同时自动更新。可以通过添加一个按钮实现更新 name 属性值的操作:

<button ng-click="name = 'Christoph'">Click me!</button>

点击这个按钮会把 Christoph 赋给 name 属性,这触发了一个 $digest 可以保持 DOM 相应更新的轮询。在这个例子中数据的更新只是单向的(上->下)。如果是在用到 ngModel 的 input 元素的例子中,用户可以输入内容改变这个属性的值,同时改变也实时的返回到实际的 model 上。

这些是因为一个 $digest 轮询被触发才发生的,Angular 负责处理所有当前 scope 和它的 child scope 上注册过的 watchers,并检查 model 是否经过改动,然后直到 model 稳定调用并且没有更多的 listeners 被点燃,对应的 listeners 被调用。
以下是以上描述的代码效果展示:
plnkr

太多watcher的问题

现在我们已经对 Angular 中的数据绑定机制有了一个大致的了解,那么可能想到为什么会需要 one-time binding 这样的特性呢?

鉴于使用 watcher 来实现数据绑定的本质,我们可能会遇到有太多 watcher 时较差性能的问题。正如我们所了解的那样,watch expressions 和他们对应的 callback listeners 都是是注册在 scope 上的,基于此 Angular 可以在 $digest 轮询的时候处理它们也就保持了相应的 view 更行。简单地说,越多 watchers 被注册,Angular 要处理的也就越多。

现在想象一种有许多动态值在 view 中的场景。比如国际化过程是非常常见的一种情况,需要 Angular 的数据绑定去做应用的本地化,尽管语言只在初始设置页才会更改,而在运行时是不会改变的。此情景下每一个字符串都被本地化到 view 中,同时也被写入到 scope 里,并且注册一个对应的 watcher 用于下次 $digest 轮询时可能的更新。如果语言的确不太可能在运行时改变,这样的代价实在是太高了。

one-time binding 来拯救你们了!

终于到了主角登场的时刻了。那么什么是 one-time bindings ?文档里是这么说的:

One-time expressions will stop recalculating once they are stable, which happens after the first digest…
第一次 digest 后,One-time 表达式一旦稳定后就不会再更新。

上面提到的场景的确是 Angular 应当处理的问题。Angular 1.3 中引入了一种新语法用于表示 interpolation 指令绑定的 one-time。只需要在表达式前加入 :: 双引号即可。同样是上面的例子,我们只需要把:

<p>Hello {{name}}!</p>

改为:

<p>Hello {{::name}}!</p>

这同样适用于其他 Angular 中典型的表达式。例如在 ng-repeat 表达式中或者仅需要从内部暴露出一个属性值的指令(不会从外部改变),只需要在外部把它设置为 one-time 表达式:

<custom-directive two-way-attribute="::oneWayExpression"></custom-directive>

下面是实际例子中的效果,已经把上面例子中的 name 改成了 ::name表示 one-time binding,其他则代码完全一样。注意按钮的作用是把 name 更新为 Christoph,不过再试试看:
plnkr
name不会再发生改变!

原文链接


2hu10n92hen9
340 声望8 粉丝

生吞橘子