angularjs双向数据绑定原理解析

来源:互联网 发布:北京java应届生工资 编辑:程序博客网 时间:2024/04/20 06:09

angularjs的双向数据绑定

脏值(发生了变化的值)检查不等于定时轮询,而是特定事件触发才会执行
只有指定事件触发后才会进入脏值轮询。
- DOM事件,譬如用户输入文本,点击按钮等。(ng-click)
- XHR(ajax)响应事件 (http)Location(location)
- Timer事件(timeout,interval)
- 执行digest()apply()

$digest函数的作用是调用这个监控函数,并且比较它返回的值和上一次返回值的差异。如果不相同,监听器就是脏的,它的监听函数就应当被调用。

$digest$apply

  • 在Angular中,有applydigest两个函数.

  • applyscope(或者是 direcvie 里的 link 函数中的 scope)的一个函数,调用它会强制一次 digestapply 的标志)。

差异

$apply可以带参数,它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非Angular框架的代码时,可以把代码写在这个里面调用。

当调用 digestapply 的时候,会触发作用域树上的所有监控

原理

双向数据绑定意味着当 view 中有任何数据发生变化会自动地反馈到 scope 的数据上,当 scope 模型发生变化时,view 中的数据也会更新到最新的值。

AngularJS 确实在幕后为 scope 模型上设置了一个 监听队列,用来监听数据变化并更新 view 。

每次绑定一个东西到 view 上时 AngularJS 就会往 watchwatch,用来检测它监视的 model 里是否有变化的东西。

以一次click操作执行的监听为例:
- 按下按钮
- 浏览器接收到一个事件,进入 angular context
- digestwatch 是否变化
- 由于监视 scope.valwatch 报告了变化,它会强制再执行一次 digestdigest 循环没有检测到变化。
- 浏览器拿回控制权,更新与 $scope.val 新值相应部分的 DOM 。

digestmodeldigest 循环的次数达到了一定阈值(抛出异常防止无限循环)。

代码示例(重点)

  • 使用了 JavaScript 中的 setTimeout() 来更新一个 scope model

  • 用指令设置一个 DOM 事件 listener 并且在该 listener 中修改了一些 models

页面中绑定的message值不会刷新。

$scope.setMsg = function() {      setTimeout(function() {          $scope.message = 'hello world';          console.log('message:' + $scope.message);      }, 2000);  }  $scope.setMsg();

要想解决以上问题[message值不实时刷新]有以下三种方法。

1、 直接使用:$scope.$apply();

setTimeout(function() {      $scope.message = 'hello world';      console.log('message:' + $scope.message2);    $scope.$apply();}, 2000);

2、 $scope.$apply(执行相应操作)

$scope.$apply可以替换为$scope.$digest

setTimeout(function() {      $scope.$apply(function() {          $scope.message1 = 'hello world';           console.log('message1:' + $scope.message);      });  }, 2000);

3、 使用angular封装的$timeout

$scope.$apply可以替换为$scope.$digest
- $timeout是angular对于setTimeout的封装,$interval是angular对于setInterval的封装。
- 会自动在内部函数执行完后调用$scope.$apply();(脏值检查)。

$scope.setMsg = function() {      $timeout(function() {          $scope.message2 = 'hello world';          console.log('message2:' + $scope.message2);    }, 2000);  } 

注:

  1. 在取message的值时必须在controller作用域内

  2. $timeout需要在控制器中注入后使用

使用$watch监听的潜在坑点

先大致介绍下$watch.
- $scope.$watch()可用于监听变量、数组或对象。有三个参数
- 第一个参数:被监听的对象[必填]
- 第二个参数:监听到变化时执行的函数[必填]
- 第三个参数:布尔值,true意为监听监听对象内容的改变,false意为监听监听对象地址或引用的改变[选填,默认false]
- 要想取消监听只需执行watch函数的返回值(没有理解就看看下边的代码)

var cancelWatch = $scope.$watch('message2', function(newValue, oldValue) {//监听到变化执行的操作},true);//取消监听cancelWatch();

之前一直以为可以用$watch监听到变化以后强制执行$scope.$apply();,直到自己写代码测试才发现这么写会报错。

$scope.setMsg = function() {      setTimeout(function() {          $scope.message2 = 'hello steven';      }, 2000);  }var cancelWatch = $scope.$watch('message2', function(newValue, oldValue) {    $scope.$digest();},true);

报错信息原描述:At any point in time there can be only one $digest or $apply operation in progress. This is to prevent very hard to detect bugs from entering your application. The stack trace of this error allows you to trace the origin of the currently executing $apply or$digest call, which caused the error.

也就是说在任意时刻只能有一个$digest$apply执行操作进行当前当前页面的脏值检查。

  • 初始化程序时监听是开启的
  • 初始化时其实已经通过变量提升改变了$scope.message2(此时相当于定义为了undefined)
  • $watch监听到数据变化提前执行了$scope.$digest(),重复调用监听报错

取消 $timeout

var customTimeout = $timeout(function () {    // your code}, 1000);$timeout.cancel(customTimeout);

通过编译执行阶段流程理解数据双向绑定

编译阶段:

  • ng-model 和 input 指令 在 标签中设置了一个 keydown 监听器
  • 在{{greeting}} 插值(也就是表达式)这里设置了一个 $watch 来监测 username 的变化

执行阶段:

  • 在 输入框中按下 ‘X’ 键引起浏览器发出一个 keydown 事件
  • input 指令捕捉到输入值的改变调用 $apply(“username = ‘X’;”) 进入Angular的执行环境来更新应用的数据模型
  • Angular将 username = ‘X’; 作用在数据模型之上,这样 scope.username 就被赋值为 ‘X’ 了
  • $digest 轮循开始
  • $watch 列表中监测到 username 有一个变化,然后通知 {{greeting}} 插值表达式,进而更新DOM
  • 执行离开Angular的上下文,进而 keydown 事件结束,然后执行也就退出了 JavaScript的上下文;这样 $digest 完成
  • 浏览器用更新了的值重新渲染视图

END