浅谈AngularJs 双向绑定原理(数据绑定机制)


Posted in Javascript onDecember 07, 2017

那么什么是双向绑定,下面简单进行讲解。

首先我们要理解数据绑定。我们看到的网站页面中,是由数据和设计两部分组合而成。将设计转换成浏览器能理解的语言,便是html和css主要做的工作。而将数据显示在页面上,并且有一定的交互效果(比如点击等用户操作及对应的页面反应)则是js主要完成的工作。很多时候我们不可能每次更新数据便刷新页面(get请求),而是通过向后端请求相关数据,并通过无刷新加载的方式进行更新页面(post请求)。那么数据进行更新后,页面上相应的位置也能自动做出对应的修改,便是数据绑定。

在以前的开发模式中,这一步一般通过jq操作DOM结构,从而进行更新页面。但这样带来的是大量的代码和大量的操作。如果能在开始的时候,便已经确定好从后端获取的数据到页面上需要进行的操作,当数据发生改变,页面的相关内容也自动发生变化,这样便能极大地方便前端工程师的开发。在新的框架中(angualr,react,vue等),通过对数据的监视,发现变化便根据已经写好的规则进行修改页面,便实现了数据绑定。可以看出,数据绑定是M(model,数据)通过VM(model-view,数据与页面之间的变换规则)向V(view)的一个修改。

而双向绑定则是增加了一条反向的路。在用户操作页面(比如在Input中输入值)的时候,数据能及时发生变化,并且根据数据的变化,页面的另一处也做出对应的修改。有一个常见的例子就是淘宝中的购物车,在商品数量发生变化的时候,商品价格也能及时变化。这样便实现了V——M——VM——V的一个双向绑定。

AngularJs 为 scope 模型上设置了一个 监听队列,用来监听数据变化并更新 view 。每次绑定一个东西到 view(html) 上时 AngularJs 就会往 $watch 队列里插入一条 $watch,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时,$digest 循环就会触发。$digest 会遍历所有的 $watch。从而更新DOM。

$watch

这有点类似于我们的观察者模式,在当前作用域$scope下,我们创建一个监控器$watchers和一个监听器$watch,$watchers 负责管理所有的 $watch,当我们每次绑定到UI上的时候就<font color=red>自动</font>创建一个$watch,并把它放到 $watchers。

controller.js

app.controller('MainCtrl', function($scope) {
 $scope.Hello = "Hello";
 $scope.world = "World";
});

index.html

<div>{{Hello}}</div>

这里,即便我们在$scope上添加了两个变量,<font color=red>但是只有一个绑定在了UI上,因此在这里只生成了一个$watch</font>

$digest

当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发。$digest将会遍历我们的$watch,如果$watch没有变化,这个循环检测就将停止,如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。这就是脏检查(Dirty Checking)机制

controller.js

app.controller('MainCtrl', function() {
 $scope.name = "Foo";

 $scope.changeFoo = function() {
  $scope.name = "Bar";
 }
});

index.js

<div>{{ name }}</div>
<button ng-click="changeFoo()">Change the name</button>
  1. 当我们按下按钮
  2. 浏览器接收到一个事件,进入angular context。
  3. $digest循环开始执行,查询每个$watch是否变化。
  4. 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
  5. 新的$digest循环没有检测到变化。
  6. 更新与$scope.name新值相应部分的DOM。

$apply

$apply 我们可以直接理解为刷新UI。<font color=red>如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入,之后的$digest检测机制就不会触发</font>

app.directive('clickable', function() {
 return {
  restrict: "E",
  scope: {
  foo: '='
  },
  template: '<ul style="background-color: lightblue"><li>{{foo}}</li></ul>',
  link: function(scope, element, attrs) {
  element.bind('click', function() {
   scope.foo++;
   console.log(scope.foo);
  });
  }
 }
});

当我们调用clickable指令的时候,我们可以看到foo的值增加了,但是界面上显示的内容并没有改变。$digest脏检测机制没有触发,检测foo的$watch就没有执行。

$apply()方法的两种形式

1) 无参

$scope.$apply();
element.bind('click', function() {
 scope.foo++;
 //if error
 scope.$apply();
});

当我们使用这种形式的时候,如果在scope.$apply之前程序发生异常,那scope.$apply没有执行,界面就不会更新

2) 有参

$scope.$apply(function(){
 ...
})
element.bind('click', function() {
 scope.$apply(function() {
  scope.foo++;
 });
})

如果用这种形式,即使后面的发生异常,数据还是会更新。

在 AngularJS 中使用 $watch

常用的使用方式:

$scope.name = 'Hello';
$scope.$watch('name', function(newValue, oldValue) {
 if (newValue === oldValue) { return; } 
 $scope.updated++;
});

传入到$watch()中的第二个参数是一个回调函数,该函数在name的值发生变化的时候会被调用。

如果要监听的是一个对象,那还需要第三个参数:

$scope.data.name = 'Hello';
$scope.$watch('data', function(newValue, oldValue) {
 if (newValue === oldValue) { return; } 
 $scope.updated++;
}, true);

表示比较的是对象的值而不是引用,如果不加第三个参数true,在 data.name 变化时,不会触发相应操作,因为引用的是同一引用。

总结

1) 只有在$scope变量绑定到页面上,才会创建 $watch

2) $apply决定事件是否可以进入angular context

3) $digest 循环检查model时最少两次,最多10次(多于10次抛出异常,防止无限检查)

4) AngularJs自带的指令已经实现了$apply,所以不需要我们额外的编写

5) 在自定义指令时,建议使用带function参数的$apply

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 冒泡排序 正序和倒序实现代码
Dec 14 Javascript
解析瀑布流布局:JS+绝对定位的实现
May 08 Javascript
jquery中常用的函数和属性详细解析
Mar 07 Javascript
jquery实现的导航固定效果
Apr 28 Javascript
JS+Canvas 实现下雨下雪效果
May 18 Javascript
javascript获取select标签选中的值
Jun 04 Javascript
BootStrap网页中代码显示用法详解
Oct 21 Javascript
vue.js声明式渲染和条件与循环基础知识
Jul 31 Javascript
基于对象合并功能的实现示例
Oct 10 Javascript
VUE DOM加载后执行自定义事件的方法
Sep 07 Javascript
使用Angular 6创建各种动画效果的方法
Oct 10 Javascript
如何利用JavaScript编写更好的条件语句详解
Aug 10 Javascript
面包屑导航详解
Dec 07 #Javascript
谈谈JS中的!!
Dec 07 #Javascript
JS动态添加元素及绑定事件造成程序重复执行解决
Dec 07 #Javascript
Angular2.0/4.0 使用Echarts图表的示例代码
Dec 07 #Javascript
jquery学习笔记之无new构建详解
Dec 07 #jQuery
利用Node.js检测端口是否被占用的方法
Dec 07 #Javascript
禁止弹窗中蒙层底部页面跟随滚动的几种方法
Dec 07 #Javascript
You might like
PHP编码转换函数 自动转换字符集支持数组转换
2012/12/16 PHP
深入解析yii权限分级式访问控制的实现(非RBAC法)
2013/06/13 PHP
一段实用的php验证码函数
2016/05/19 PHP
PHP面试常用算法(推荐)
2016/07/22 PHP
如何用PHP做到页面注册审核
2017/03/02 PHP
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
2010/07/06 Javascript
查看源码的工具 学习jQuery源码不错的工具
2011/12/26 Javascript
jquery中常用的函数和属性详细解析
2014/03/07 Javascript
老生常谈combobox和combotree模糊查询
2017/04/17 Javascript
利用Vue.js实现求职在线之职位查询功能
2017/07/03 Javascript
代码详解Vuejs响应式原理
2017/12/20 Javascript
node.js爬取中关村的在线电瓶车信息
2018/11/13 Javascript
vue 移动端记录页面浏览位置的方法
2020/03/11 Javascript
vue.js 解决v-model让select默认选中不生效的问题
2020/07/28 Javascript
Swiper实现导航栏滚动效果
2020/10/16 Javascript
[50:20]DOTA2上海特级锦标赛主赛事日 - 5 总决赛Liquid VS Secret第四局
2016/03/06 DOTA
Python程序中设置HTTP代理
2016/11/06 Python
Python cookbook(数据结构与算法)从序列中移除重复项且保持元素间顺序不变的方法
2018/03/13 Python
Python通用循环的构造方法实例分析
2018/12/19 Python
几行Python代码爬取3000+上市公司的信息
2019/01/24 Python
使用 python pyautogui实现鼠标键盘控制功能
2019/08/04 Python
Python3将ipa包中的文件按大小排序
2020/04/17 Python
Python classmethod装饰器原理及用法解析
2020/10/17 Python
Python之Sklearn使用入门教程
2021/02/19 Python
以色列的身体护理及家居香薰品牌:Sabon NYC
2018/02/23 全球购物
美国在线购买空气净化器、除湿器、加湿器网站:AllergyBuyersClub
2021/03/16 全球购物
Android面试宝典
2013/08/06 面试题
公司年会演讲稿范文
2014/01/11 职场文书
电工工作职责范本
2014/02/22 职场文书
机关作风整顿个人整改措施2014
2014/09/17 职场文书
大学拉赞助协议书范文
2014/09/26 职场文书
学校开学标语
2014/10/06 职场文书
校运会通讯稿
2015/07/18 职场文书
MongoDB使用profile分析慢查询的步骤
2021/04/30 MongoDB
Windows11里微软已经将驱动程序安装位置A盘删除
2021/11/21 数码科技
使用Ajax实现无刷新上传文件
2022/04/12 Javascript