AngularJS 的常用特性(三)
6、表达式
在模板中使用表达式是为了以充分的灵活性在模板、业务逻辑和数据之间建立联系,同时又能避免让业务逻辑渗透到模板中。
<div ng-controller="SomeController"> <div>{{recompute() / 10}}</div> <ul ng-repeat="thing in things"> <li ng-class="{highlight: $index % 4 >= threshold($index)}"> {{otherFunction($index)}} </li> </ul> </div>
当然,对于第一个表达式,recompute() / 10,应该避免这种把业务逻辑放到模板中的方式,应该清晰地区分视图和控制器之间的职责,这样更便于测试。
虽然 Angular 里面的表达式比 Javascript 更严格,但是它们对 undefined 和 null 的容错性更好。如果遇到错误,模板只是简单地什么都不显示,而不会抛出一个 NullPointerException 错误,这样你就可以安全地使用未经初始化的模型值,而一旦它们被赋值以后就会立即显示出来。
7、区分 UI 和控制器的职责
在应用中控制器有三种职责:
- 为应用中的模型设置初始状态
- 通过 $scope 对象把数据模型和函数暴露给视图(UI模板)
- 监视模型其余部分的变化,并采取相应的动作
建议为视图中的每一块功能区域创建一个控制器,这样可以让控制器保持小巧和可管理的状态。
更为复杂的时候,你可以创建嵌套的控制器,通过内部的原型继承机制,父控制器对象上的 $scope 会被传递给内部嵌套控制器的 $scope。
<div ng-controller="ParentController"> <div ng-controller="ChildController">...</div> </div>
上面的例子中,ChildController 的 $scope 对象可以访问 ParentController 的 $scope 对象上的所有属性(和函数)。
8、使用 $watch 监控数据模型的变化
在 scope 内置的所有函数中,用到最多的可能就是 $watch 函数了,当你的数据模型中的某一部分发生变化时,$watch 可以向你发出通知,监控单个对象的属性,也可以监控需要经过计算的结果,只要能被当作属性访问到,或者可以当作一个 JavaScript 函数被计算出来,就可以被 $watch 函数监控。函数签名为:
$watch(watchFn, watchAction, deepWatch)
watchFn —— 该参数是一个带有 Angular 表达式或者函数的字符串,返回被监控的数据模型的当前值。它会被执行多次,所以要保证不会产生副作用;
watchAction —— 通常以函数的形式,接收到 watchFn 的新旧两个值,以及作用域对象的引用,函数签名为 function(newValue, oldValue, scope)
deepWatch —— 如果设置为 true,则会去检查被监控对象的每个属性是否发生了变化,一般监控一个数组或者多个对象时要设置为 true,但由于要遍历数组,所以运算负担会比较重。
$watch 函数会返回一个属性,当你不再需要接受变更通知时,可以用这个返回的函数注销监控器。如果需要监控一个属性,然后注销监控,可以如下:
... var dereg = $scope.$watch('someModel.someProperty', callbackOnChange()); ... dereg();
一个购物车的例子,当用户添加到购物车中的商品价格超过 100 美元的时候,会有 10 美元的折扣。使用下面的模板:
<html ng-app="shoppingCart"> <body> <div ng-controller="CartController"> <div ng-repeat="item in items"> <span>{{item.title}}</span> <input ng-model="item.quantity"> <span>{{item.price | currency}}</span> <span>{{item.price * item.quantity | currency}}</span> </div> <div>Total: {{totalCart() | currency}}</div> <div>Discount: {{bill.discount | currency}}</div> <div>Subtotal: {{subtotal() | currency}}</div> </div> <script src="src/angular.js"></script> <script src="shoppingCart.js"></script> </body> </html>
控制器如下:
var shoppingCart = angular.module('shoppingCart', []); shoppingCart.controller('CartController', function ($scope) { $scope.bill = {}; $scope.items = [ {title: 'Paint pots', quantity: 8, price: 3.95}, {title: 'Polka dots', quantity: 17, price: 12.95}, {title: 'Pebbles', quantity: 5, price: 6.95} ]; $scope.totalCart = function () { var total = 0; for (var i = 0, len = $scope.items.length; i < len; i++) { total = total + $scope.items[i].price * $scope.items[i].quantity; } return total; }; function calculateDiscount(newValue, oldValue, scoope) { $scope.bill.discount = newValue > 100 ? 10 : 0; } $scope.subtotal = function () { return $scope.totalCart() - $scope.bill.discount; }; $scope.$watch($scope.totalCart, calculateDiscount); });
用户看到的效果如图:
9、watch() 中的性能注意事项
断点调试 totalCart() 的代码,你会发现渲染这个页面时,该函数被调用了 6 次。其中 3 次发生在每次调用它的时候:
- 模板 {{totalCart() | currency}}
- subtotal() 函数
- $watch() 函数
然后 Angular 又把整个过程重复了一遍,这样的目的是:检测模型中的变更已经被完整地进行了传播,并且模型已经被设置好。Angular 的做法是,把所有被监控的属性都拷贝一份,然后把它们和当前的值进行比较,看看是否发生了变化。实际上,Angular 可能运行上面过程不止两遍,如果重复 10 遍发现属性还在变化,Angular 会报错并退出,这时候你需要解决循环依赖的问题了。
PS: 书里面说的是,由于 Angular 需要用 JavaScript 实现数据绑定,官方开发团队与 TC39 团队一起开发了一个叫做 Object.observe() 的底层本地化实现,这样可以让你的数据绑定操作就像本地化代码一样快速。
我们可以改成监控 items 数组的变化,然后重新计算 $scope 属性中的总价、折扣和小计值来减少函数调用次数。
模板修改如下:
<div>Total: {{totalCart() | currency}}</div> <div>Discount: {{bill.discount | currency}}</div> <div>Subtotal: {{subtotal() | currency}}</div>
控制器修改如下:
var shoppingCart = angular.module('shoppingCart', []); shoppingCart.controller('CartController', function ($scope) { $scope.bill = {}; $scope.items = [ {title: 'Paint pots', quantity: 8, price: 3.95}, {title: 'Polka dots', quantity: 17, price: 12.95}, {title: 'Pebbles', quantity: 5, price: 6.95} ]; function calculateDiscount(newValue, oldValue, scoope) { var total = 0; for (var i = 0, len = $scope.items.length; i < len; i++) { total = total + $scope.items[i].price * $scope.items[i].quantity; } $scope.bill.totalCart = total; $scope.bill.discount = newValue > 100 ? 10 : 0; $scope.bill.subtotal = total - $scope.bill.discount; } $scope.$watch('items', calculateDiscount, true); });
在调用 $watch 函数时把 items 写成了一个字符串,并且第三个参数设置为 true,可以监控整个 items 数组的变化,单需要制作一份数组的拷贝,用来进行比较操作。
所以,如果每次在 Angular 显示页面时只需要重新计算 bill 属性,那么性能会好很多。可以像下面这样重新计算属性值:
$scope.$watch(function (newValue, oldValue, scope) { var total = 0; for (var i = 0, len = $scope.items.length; i < len; i++) { total = total + $scope.items[i].price * $scope.items[i].quantity; } $scope.bill.totalCart = total; $scope.bill.discount = newValue > 100 ? 10 : 0; $scope.bill.subtotal = total - $scope.bill.discount; });
根据性能分析更是说明了这点(左边是第二种方式):
当然可以使用上面说过的方法,利用 $watch 返回的函数,移除不必要的 $watch,参见破狼大神的例子:Angular 移除不必要的 $watch
10、监控多个东西
监控多个东西,有两种基本的选择:
- 监控把这些属性连接起来之后的值
- 把它们放到一个数组或者对象中,然后给 deepWatch 参数传递一个 true 值。
第二种情况上面已经介绍,第一种情况比较简单,比如在你的作用域中存在一个 things 对象,它带有两个属性 a 和 b,当这两个属性发生变化时都需要执行 callMe() 函数,你可以同时监控着两个属性, 示例如下:
$scope.$watch('things.a + things.b', callMe(...));
当然,a 和 b 也可以属于不同的对象,这个列表可以很长,不过如果需要监控的属性比较多, 不妨把这个列表放入一个函数中,返回连接的值。
特别感谢:《用 AngularJS 开发下一代 Web 应用》
AngularJS 的常用特性(三)的更多相关文章
- AngularJS 的常用特性(五)
13.使用路由和 $location 切换视图 对于一些单页面应用来说,有时候需要为用户展示或者隐藏一些子页面视图,可以利用 Angular 的 $route 服务来管理这种场景. 你可以利用路由服务 ...
- AngularJS 的常用特性(一)
前言:AngularJS 是一款来自 Google 的前端 JS 框架,该框架已经被应用到了 Google 的多款产品中,这款框架最核心特性有:MVC.模块化.自动化双向数据绑定.语义化标签.依赖注入 ...
- AngularJS 的常用特性(四)
11.使用 Module(模块) 组织依赖关系 Angular 里面的模板,提供了一种方法,可以用来组织应用中一块功能区域的依赖关系:同时还提供了一种机制,可以自动解析依赖关系(又叫依赖注入),一般来 ...
- AngularJS 的常用特性(二)
3.列表.表格以及其他迭代型元素 ng-repeat可能是最有用的 Angular 指令了,它可以根据集合中的项目一次创建一组元素的多份拷贝. 比如一个学生名册系统需要从服务器上获取学生信息,目前先把 ...
- MVC常用特性
MVC常用特性使用 简介 在以前的文章中,我和大家讨论如何用SingalR和数据库通知来完成一个消息监控应用. 在上一篇文章中,我介绍了如何在MVC中对MongoDB进行CRUD操作. 今天,我将 ...
- C#网络程序设计(1)网络编程常识与C#常用特性
网络程序设计能够帮我们了解联网应用的底层通信原理! (1)网络编程常识: 1)什么是网络编程 只有主要实现进程(线程)相互通信和基本的网络应用原理性(协议)功能的程序,才能算是真正的网 ...
- AngularJS 最常用的几种功能
AngularJS 最常用的几种功能 2017-04-13 吐槽阿福 互联网吐槽大会 第一 迭代输出之ng-repeat标签ng-repeat让table ul ol等标签和js里的数组完美结合 1 ...
- Unity3D编辑器扩展(五)——常用特性(Attribute)以及Selection类
前面写了四篇关于编辑器的: Unity3D编辑器扩展(一)——定义自己的菜单按钮 Unity3D编辑器扩展(二)——定义自己的窗口 Unity3D编辑器扩展(三)——使用GUI绘制窗口 Unity3D ...
- mootools常用特性和示例(基础篇1)
网上关于mootools这个库的信息很少. 公司一些老的项目用到了mootools库,因为要维护,所以接触到了mootools. mootools(文档)官网:http://www.chinamoot ...
随机推荐
- selenium 简单指南
1.1 下载selenium2.0的包 官方download包地址:http://code.google.com/p/selenium/downloads/list 官方User Guide: h ...
- [Erlang21]Erlang性能分析工具eprof fporf的应用
前段时间项目改代码突然cpu波动很大,排查了好久都没有找到原因,只能求助于性能测试工具 : <<Erlang程序设计>>----Joe Armstorng[哈哈,登月第一人 ...
- 配置git使用socks5代理
git config --global http.proxy 'socks5://127.0.0.1:1080' git config --global https.proxy 'socks5://1 ...
- c#去除DataTable空列
网上搜了好多,没找到能用的,自己写一个,有发现错误的给我留言. private void RemoveNULLColumns(ref DataTable data)//删除空列 { try { Dat ...
- djngo 1.9版本以后 Foreignkey() 字段 第二个参数 on_delete 必不可少, mysql 外键可以为空
一.外键的删除 1.常见的使用方式(设置为null) class BookModel(models.Model): """ 书籍表 """ ...
- robot framework学习笔记之十-模板
测试模板可以让关键字驱动测试用例转换为数据驱动测试用例.鉴于普通测试用例是由关键字和可能的参 数组成,使用了模板的测试用例只需要定义模板关键字的参数即可
- Android中线程和线程池
我们知道线程是CPU调度的最小单位.在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的.在Android中,除了Thread外,扮演线程的角色有很多,如AsyncTask,Inte ...
- java使用Redis2--保存对象
Redis中并没有提供set(String key, Object obj)的方法,但提供了set(final byte[] key, final byte[] value) 的方法,可以通过把对象转 ...
- windows 域的安装方法
前面的博客中我们知道了 Windows AD域的升级,下面我谈谈Windows域的安装和卸载. 卸载AD域 配置备份AD域 安装子域 删除子域(必须在根域管理员模式下删除,否则无法删除) 删除命令 导 ...
- jpetStore 学习总结(2)
在写jpetstore时,最难理解的应该是数据库还有每个表之间的关系了,我在这里对数据库简单的介绍. 以下是数据库的所有表: account表是个人信息表,里面包括用户的名字,邮箱,地址,哪个城 ...