`

详解AngularJS中的$watch

阅读更多

一.使用$watch监控数据模型的变化

        在scope内置的所有函数中,用得最多的可能就是$watch函数了,当你的数据模型中某一部分发生变化时,$watch函数可以向你发出通知。你可以监控单个对象的属性,也可以监控需要经过计算的结果(函数),实际上只要能够被当作属性访问到,或者可以当作一个JavaScript函数被计算出来,就可以被$watch函数监控。它的函数签名为:

$watch(watchFn, watchAction, deepLWatch)

其中每个参数的详细含义如下:

        watchFn:该参数是一个带有Angular表达式或者函数的字符串,它会返回被监控的数据模型的当前值。这个表达式将会被执人行很多次,所以要保证它不会产生其他副作用。也就是说,要保证它可以被调用很多次而不会改变状态。基于同样的原因 ,监控表达式应该很容易被计算出来。如果你使用字符串传递了一个Angular表达式,那么它将会针对调用它的那个作用域中的对象而执行。

        watchAction:这是一个函数或者表达式,当watchFn发生变化时会被调用。如果是函数的形式,它将会接收到watchFn的新旧两个值,以及作用域对象的引用。其函数签名为function(newValue,oldValue,scope)。

        deepWatch:如果设置为true,这个可选的布尔型参数将会命令Angular去检查被监控对象的每个属性是否发生了变化。如果你想要监控数组中的元素,或者对象上的所有属性,而不只是监控一个简单的值,你就可以使用这个参数。由于Angular需要遍历数组或者对象,如果集合比较大,那么运算负担就会比较重。

        $watch函数会返回一个函数,当你不再需要接收变更通知时,可以用这个返回的函数注销监控器。

        如果我们需要监控一个属性,然后接着注销监控,我们可以使用以下代码:

...
var dereg = $scope.$watch('someModel.someProperty', callbackOnChange());
...
dereg();

        例如,当用户添加到购物车中的商品价值超过100美元的时候,我们给他10美元的折扣。实例如下:

<html ng-app='myApp'>
<head>
	<title>Watch实例</title>
</head>
<body ng-controller='CartController'>
	<h1>您的订单</h1>
	<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>
		<button ng-click="remove($index)">Remove</button>
	</div>
	<div>Total:{{totalCart() | currency}}</div>
	<div>Discount:{{bill.discount | currency}}</div>
	<div>Subtotal:{{subtotal() | currency}}</div>
	<script src="lib/angular/angular.js"></script>
	<script>
		var myApp=angular.module('myApp',[])
		myApp.controller('CartController', function($scope) {
			$scope.bill = {};
			$scope.items = [{title:'西瓜',quantity:8,price:3.95},{title:'西红柿',quantity:17,price:12.95},{title:'苹果',quantity:5,price:6.95}];
			$scope.remove = function(index) {
				$scope.items.splice(index,1);
			};
			$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;
			};
			$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>

运行效果:


将西红柿、苹果Remove后,折扣变为了0。

        注意CartController底部,我们在totalCart()的值上面设置了一监控,用来计算此次购物的总价。只要这个值发生变化,监控器就会调用calculateDiscount(),然后我们就可以把折扣设置为相应的值。如果总价超过100美元,我们将会把折扣设置为10美元。否则,折扣为0。

 

二.watch()中的性能注意事项

        上面的例子可以正确的运行,但是却存在潜在的性能问题。如果你在totalCart()中打一个调试断点,就会发现在渲染这个页面时,该函数被调用了6次。虽然在当前这个应用中它所引起的性能问题并不明显,但是在更加复杂的应用中,运行6次就会成为一个问题。

        为什么是6次呢?其中3次我们可以很容易跟踪到,因为在以下每种情况下它都会运行一次:

        a.模板{{totalCart() | currency}}

        b.subtotal()函数

        c.$watch函数

        然后Angular把以上整个过程又重复了一遍,最终就是6次。Angular这样做的目的是,检测模型中的变更已经被完整地进行了传播,并且模型已经被设置好。Angular的做法是,把所有的被监控的属性都拷贝一份,然后把它们和当前的值进行比较,看看是否发生了变化。实际上,Angular可能会把以上过程运行10次,从而确保进行了完整的传播。如果经过10次重复之后被监控的属性还在发生变化,Angular将会报错并退出。如果出现了这种情况,你可能需要解决循环依赖的问题。

        有人开发了一个叫做Object.observe()(http://updates.html5rocks.com/2012/11/Respond-to-change-with-object-observe)的底层本地化实现。实现了这一点之后,Angular将会在所有存在此实现的地方自动使用Object.observe(),让你的数据绑定操作就像本地化代码一样快速。

        Angular带有一个很好的Chrome调试插件Batarang,它会自动为你指出哪些数据绑定操作比较昂贵。

        有几种方法可以解决这个问题。一种方法是监控items数组的变化,然后重新计算$scope属性中的总价、折扣和小计值。如下所示:

<html ng-app='myApp'>
<head>
	<title>Watch实例</title>
</head>
<body ng-controller='CartController'>
	<h1>您的订单</h1>
	<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>
		<button ng-click="remove($index)">Remove</button>
	</div>
	<div>Total:{{bill.total | currency}}</div>
	<div>Discount:{{bill.discount | currency}}</div>
	<div>Subtotal:{{bill.subtotal | currency}}</div>
	<script src="lib/angular/angular.js"></script>
	<script>
		var myApp=angular.module('myApp',[])
		myApp.controller('CartController', function($scope) {
			$scope.bill = {};
			$scope.items = [{title:'西瓜',quantity:8,price:3.95},{title:'西红柿',quantity:17,price:12.95},{title:'苹果',quantity:5,price:6.95}];
			$scope.remove = function(index) {
				$scope.items.splice(index,1);
			};
			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函数我们就可以实现这一点,如下所示:

<html ng-app='myApp'>
<head>
	<title>Watch实例</title>
</head>
<body ng-controller='CartController'>
	<h1>您的订单</h1>
	<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>
		<button ng-click="remove($index)">Remove</button>
	</div>
	<div>Total:{{bill.total | currency}}</div>
	<div>Discount:{{bill.discount | currency}}</div>
	<div>Subtotal:{{bill.subtotal | currency}}</div>
	<script src="lib/angular/angular.js"></script>
	<script>
		var myApp=angular.module('myApp',[])
		myApp.controller('CartController', function($scope) {
			$scope.bill = {};
			$scope.items = [{title:'西瓜',quantity:8,price:3.95},{title:'西红柿',quantity:17,price:12.95},{title:'苹果',quantity:5,price:6.95}];
			$scope.remove = function(index) {
				$scope.items.splice(index,1);
			};
			$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>

 

三.监控多个东西

        如果想监控多个属性或者对象,并且当其中任何一个发生变化时就去执行一个函数,应该怎么做呢?有两种基本的选择:

        a.监控把这些属性连接起来之后的值

        b.把它们放到一个数组对象中,然后给deepWatch参数传递一个true值

        在第一种情况下,如果在你的作用域中存在一个things对象,它带有两个属性a和b,当这两个属性发生变化时都需要执行callMe()函数,你可以同时监控这两个属性,示例如下:

$scope.$watch('things.a + things.b',callMe(...));

        当然,a和b也可以属于不同的对象,只要需要,这个列表可以无限长。如果列表非常长,你就需要写一个函数来返回连接之后的值,而不是依赖一个表达式来完成这一逻辑。

        在第二种情况下,需要监控things对象上的所有属性,你可以这样做

$scope.$watch('things',callMe(...),true);

        这里,给第三个参数传递了一个true,请求Angular遍历things的属性,然后当其中任何一个属性发生变化时就调用callMe()。这一机制在数组上的运行方式和在对象上的运行方式相同。

 

资料来源:《用AngularJS开发下一代Web应用》

  • 大小: 23.7 KB
  • 大小: 18.2 KB
分享到:
评论

相关推荐

    angularjs 中$apply,$digest,$watch详解

    在AngularJS中,$apply,$digest和$watch是核心的脏检查机制,它们共同确保了模型和视图之间的同步。下面将详细解释这三个概念及其工作原理。 1. **$apply (通知)** `$apply`是AngularJS提供的一个方法,它的主要...

    angularJS中$apply()方法详解

    在AngularJS中,$apply()是一个非常重要的概念,它是用来手动启动AngularJS的脏检查机制,确保数据的双向绑定得以更新。接下来,我们将详细探讨$apply()方法的细节和使用场景。 首先,Scope在AngularJS中扮演着核心...

    AngularJs $parse、$eval和$observe、$watch详解

    在AngularJS中,有多个内置服务和方法可以帮助开发者实现数据绑定和状态管理,其中包括了$parse、$eval、$observe和$watch。本文将详解这些方法的作用、用法及它们之间的区别和联系。 首先,我们来了解$parse服务。...

    AngularJS入门教程之数据绑定原理详解

    - **$watch列表**:AngularJS在编译阶段会检查模板中的指令,并为每个指令创建相应的$watch。当模板加载并链接到作用域时,$watch列表会被填充。在$digest循环中,AngularJS遍历这个列表,逐个检查$watch。 ### ...

    Angular中的$watch方法详解

    Angular中的$watch方法详解 Angular中的$watch方法是AngularJS框架中的一种重要机制,用于监听模型的变化,并在变化时触发某些事件。下面将对$watch方法进行详细的介绍。 一、$watch方法简介 $watch方法是...

    关于angular js_$watch监控属性和对象详解

    $Watch:(监听一个model,当一个model每次改变时,都会触发第二个函数) $watch(‘watchFn’,watchAction,deepWatch) watchFn:带有Angular 表达式或者函数的字符串,它会返回被监控的数据模型的当前值。 watchAction...

    AngularJs Using $location详解及示例代码

    $location提供了更高级别的抽象,它与AngularJS的声明周期集成,允许你通过`$watch`等机制来监听URL变化。此外,$location在HTML5模式下可以无缝工作,即使在不支持HTML5 History API的旧版浏览器中也能提供回退方案...

    AngularJS语法详解(续)

    在AngularJS中,可以通过$watch服务来监控数据模型的变化。$watch允许我们指定一个监控函数watchFn和一个当监控项发生变化时需要执行的操作watchAction。如果需要深度监控对象内部的所有属性变化,可以设置deepWatch...

    对angular 监控数据模型变化的事件方法$watch详解

    在AngularJS中,`$watch`是核心的事件监听机制,用于监控数据模型(model)的变化。这个功能强大且灵活的方法可以帮助开发者在模型值改变时执行相应的操作,从而实现响应式应用。本文将深入讲解`$watch`的工作原理、...

    详解AngularJS脏检查机制及$timeout的妙用

    在脏检查循环中,AngularJS会递归检查所有注册的watch表达式,如果发现数据有变化,就会触发相应的watcher函数来更新视图。这个过程会一直持续到所有watcher函数都不再报告数据变更为止。一旦完成一轮检查而没有发现...

    AngularJs demo 例子(表单验证) form

    **AngularJS 表单验证详解** AngularJS 是一个强大的前端JavaScript框架,主要用于构建动态Web应用。在AngularJS中,表单验证是一个至关重要的部分,它能够帮助开发者在用户提交数据之前进行实时验证,提高用户体验...

    AngularJS 工作原理详解

    **AngularJS工作原理详解** AngularJS 是一个流行的前端JavaScript框架,用于构建动态Web应用程序。其核心特性在于双向数据绑定和声明式编程。要深入理解AngularJS的运行机制,我们需要了解以下几个关键步骤: 1. ...

    angularjs1.2.2

    **AngularJS 1.2.2 知识点详解** AngularJS 是一款强大的前端JavaScript框架,由Google维护,主要用于构建单页应用(Single Page Applications, SPA)。在AngularJS 1.2.2版本中,它引入了多个改进和优化,为开发者...

    AngularJS 执行流程详细介绍

    一旦$digest循环确定没有更多的模型变化,它就会稳定下来,并且执行过程会离开AngularJS的上下文,返回到浏览器中。此时,DOM会被更新,视图也会反映出模型最新的状态。 这一连串的执行流程构成了AngularJS的核心...

    Build_Your_Own_Angularjs

    - **$watch 和 $digest**:AngularJS通过$watch方法监听Scope对象中的属性变化,并在属性发生变化时触发相应的函数。而$digest则是AngularJS内部用来检测属性变化并更新DOM的过程。 - **检查脏值**:当属性值发生...

    最新AngularJS开发宝典视频教程 后盾网AngularJS培训视频教程 后盾网.txt

    ├最新AngularJS开发宝典—第007讲 ng-model在表单中双向绑定实例分析讲解.mp4 ├最新AngularJS开发宝典—第008讲 ng-value的使用实例讲解.mp4 ├最新AngularJS开发宝典—第009讲 实例讲解angular控制单选框.mp4 ├...

Global site tag (gtag.js) - Google Analytics