购物卡案例源代码:
<html ng-app>
<head>
<title>Your shopping Cart</title>
</head>
<body >
<script src="angular.min.js"></script>
<div ng-controller="CartController">
<h1>Your Order</h1>
<div ng-repeat="item in items">
<span>{{$index+1}}</span>
<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>
function CartController($scope){
$scope.bill = {};
$scope.items = [
{title:"Paint pots",quantity:8,price:3.95},
{title:"Polka dots",quantity:5,price:12.95},
{title:"Pebbles",quantity:5,price:6.95},
];
$scope.totalCart =function(){
var total = 0;
var len = $scope.items.length
for(var i = 0;i < len;i++){
total = total + $scope.items[i].price * $scope.items[i].quantity;
}
return total;
}
$scope.subtotal = function(){
return $scope.totalCart() - $scope.bill.discount;
};
function calculateDiscount(newValue,oldValue,scope){
$scope.bill.discount = newValue > 100 ? 10 : 0;
}
$scope.$watch($scope.totalCart, calculateDiscount);
}
</script>
</body>
</html>
上一个实例中关于watch的应用正确执行,但是在其性能方面存在一个潜在的问题。尽管这个问题不是非常明显,但如果你在totalCart()函数中设置一个调试跟踪断点,你就可以发现它这个函数被调用了六次来刷新当前页面。尽管在这个小应用中并没有发现多少这个问题,但是在更复杂的应用中,一次执行调用函数6次将会是一个严重的问题。
totalCart()函数执行为什么是6次?我们可以很容易的跟踪发现其中三次调用,分别在下面的函数中分别调用一次:
1、在模板{{ totalCart | currency }}中;
2、在subtotal()函数中;
3、在$watch()函数中
然后,在Angular中,这些函数将会被在此运行,由此得到totalCart()函数执行了6次。Angular再次运行上述函数时为了证实在你的模板中可传递的改变已经完全传递,同时也证实了你的模型已经稳定。Angular通过复制所有监控的属性,并将这些值与当前的值相比较来查看他们是否改变来完成这个检查。事实上,Angular可能将这个流程运行十次来确保完全完全传递。如果经过十次循环之后这些改变依然发生,Angular将会报错并退出。如果这些发生了,你可能会有一个需要去维护的相关的循环。
虽然你现在还需要来关注这个问题,但是当完全学习过Angular之后,你就发现这将不在个问题了。当Angular在忙着在Javascript中完成数据绑定时,我们已经制作出一个名为Object.observe()的低层次本机实现。有了这个实现,Angular将会在出现快速本地数据绑定的任何地方使用Object.observe().
对于在使用watch()时所出现的问题,现在有几种方法可以解决这个问题。一种方式是在创建一个$watch来监控tems数组的变化,并且在$scope中将total、discount、subtotal作为属性值重新计算。利用这种方式我们对之前的模板进行更新:
<html ng-app>
<head>
<title>Your shopping Cart</title>
</head>
<body >
<script src="angular.min.js"></script>
<div ng-controller="CartController">
<h1>Your Order</h1>
<div ng-repeat="item in items">
<span>{{$index+1}}</span>
<span>{{item.title}}</span>
<input ng-model="item.quantity">
<span>{{item.price | currency}}</span>
<span>{{item.price * item.quantity | currency}}</span>
</div>
<div>Total : {{bill.total | currency}}</div>
<div>Discount:{{bill.discount |currency}}</div>
<div>Subtotal:{{bill.subtotal |currency}}</div>
</div>
<script>
function CartController($scope){
$scope.bill = {};
$scope.items = [
{title:"Paint pots",quantity:8,price:3.95},
{title:"Polka dots",quantity:5,price:12.95},
{title:"Pebbles",quantity:5,price:6.95},
];
var calculateTotals = 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;
}
$scope.bill.total= total;
$scope.bill.discount = total > 100 ? 10:0;
$scope.bill.subtotal = total - $scope.bill.discount;
};
$scope.$watch("items", calculateTotals,true);
}
</script>
</body>
</html>
注意此处$watch将items作为一个字符串。这或许是因为$watch函数要么能够采用一个函数(像我们之前所用的那样),要么使用一个字符串。如果一个字符串被传递给$watch函数中,那么下一步它将会在它被调用的$scope作用域中作为表达式来进行计算。
这个方法在你的应用中或许运行正常。但是,因为我们在监控items数组,Angular将会对该数组进行复制并进行比较。对于一个较大的items列表,如果每次Angular评估页面时我们仅仅计算bill属性值,那么这种方式的运行效果可能会更好些。我们可以创建一个仅仅带有一个watchFn的$watch,该$watch将会重新计算我们的属性值。更新代码:
<html ng-app>
<head>
<title>Your shopping Cart</title>
</head>
<body >
<script src="angular.min.js"></script>
<div ng-controller="CartController">
<h1>Your Order</h1>
<div ng-repeat="item in items">
<span>{{$index+1}}</span>
<span>{{item.title}}</span>
<input ng-model="item.quantity">
<span>{{item.price | currency}}</span>
<span>{{item.price * item.quantity | currency}}</span>
</div>
<div>Total : {{bill.total | currency}}</div>
<div>Discount:{{bill.discount |currency}}</div>
<div>Subtotal:{{bill.subtotal |currency}}</div>
</div>
<script>
function CartController($scope){
$scope.bill = {};
$scope.items = [
{title:"Paint pots",quantity:8,price:3.95},
{title:"Polka dots",quantity:5,price:12.95},
{title:"Pebbles",quantity:5,price:6.95},
];
$scope.$watch(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;
}
$scope.bill.total= total;
$scope.bill.discount = total > 100 ? 10:0;
$scope.bill.subtotal = total - $scope.bill.discount;
});
}
</script>
</body>
</html>
监控多个属性值:
如果你想监控多个属性或多个对象,并且每当他们中的任何一各改变时执行相应的函数时该如何进行处理?这里有两种基本的选择:
1、将这些属性值或对象放到一个数组或对象中,然后将$watch函数的deepWatch设置为TRUE。
2、监控属性的连接值;
在第一种情况中,如果你在你的作用域中获得一个对象的两个属性值a和b,并且当两者发生改变时调用callMe()函数。纳闷你可以同时监控这个属性值a和b,方式如下:
$scope.$watch("things.a + things.b",callMe(.....));
当然a和b可以分属于不同的对象,并且你可以按照你的意愿进行属性连接。如果这个列表很长,那么你将会更希望写一个函数,这个函数返回一个连接值而不是因为逻辑而依赖于一个表达式。
在第二种情况下,你可能想来监控一个对象的所有属性值变化。在这种情况下,你可以以下面的方式进行设置:
$scope.$watch("things", callMe(.....), true);
在这里,将第三个参数设置为TRUE来让Angular对things的所有属性进行遍历,并在things的任意一个属性值发生变化时调用callMe()函数。对于这种方式同样适用于一个数组,其方式和作用与一个对象时一样的。