浅谈Angularjs中不同类型的双向数据绑定


Posted in Javascript onJuly 16, 2018

Angularjs1.X中两种不同的双向数据绑定

聊聊 Angularjs1.x中那些活见鬼的事情。

一. html与Controller中的双向数据绑定

html-Controller的双向数据绑定,在开发中非常常见,也是Angularjs1.x的宣传点之一,使用中并没有太多问题。

1.1数据从html流向controller

也就是从 视图层 流向 模型层 ,原生html中需要使用表单元素(例如 input 标签)来收集用户输入信息,Angularjs中通过在表单元素上使用 ng-model 标签,当用户输入信息时,同步将用户输入的信息赋值给controller中的变量:

<body ng-app="myApp">
  <div id="main" ng-controller="myCtrl">
    <p>改变输出值:</p>
    <input type="text" ng-model="testInfo.content" ng-change="showInput()">
  </div>
 <script src="./angular.min.js"></script>
 <script>
  angular.module('myApp',[])
  .controller('myCtrl',['$scope',function($scope){
    $scope.showInput = function() {
     console.log($scope.testInfo.content);
    }
  }]);
 </script>
</body>

在页面上输入1234567即可看到,每次在页面输入数字后,控制台输出的 $scope,testInfo.content 的值都和页面保持一致:

浅谈Angularjs中不同类型的双向数据绑定

1.2 数据从controller流向html

也就是从 模型层 流向 数据层 ,当controller中的数据模型变量发生变化后,Angularjs又会根据数据模型的值去改变 ng-model 指令绑定的表单元素的值,使用 ng-bind 指令也可以被动获得来自controller的数据流。

我们编写如下demo进行测试:

<body ng-app="myApp">
  <div id="main" ng-controller="myCtrl">
    <button ng-click="add()">+1</button>
    <p>改变输出值:</p>
    <input type="text" ng-model="testInfo.content">
    <p>使用ng-bind绑定的标签:</p>
    <p ng-bind="testInfo.content"></p>
  </div>
  <script src="./angular.min.js"></script>
  <script>
  angular.module('myApp', [])
    .controller('myCtrl', ['$scope', function($scope) {
      //初始化
      $scope.testInfo = {
        content: 0
      }

      $scope.add = function () {
        $scope.testInfo.content += 1;
        console.log($scope.testInfo.content);
      }
    }]);
  </script>
</body>

demo中,每次点击 +1 按钮, $scope.testInfo.content 的值会增加1,我们可以看到页面上的结果:

浅谈Angularjs中不同类型的双向数据绑定

1.3 你丫倒是刷视图啊

来看看第一个 活见鬼 的例子,demo跟上面很类似,只是将 鼠标点击触发 的方式改成了 定时器自动触发

<body ng-app="myApp">
  <div id="main" ng-controller="myCtrl">
    <button ng-click="add()">+1</button>
    <p>改变输出值:</p>
    <input type="text" ng-model="testInfo.content">
    <p>使用ng-bind绑定的标签:</p>
    <p ng-bind="testInfo.content"></p>
  </div>
  <script src="./angular.min.js"></script>
  <script>
  angular.module('myApp', [])
    .controller('myCtrl', ['$scope', function($scope) {
      //初始化
      $scope.testInfo = {
        content: 0
      }

      //定时自增
      setInterval(function () {
       $scope.testInfo.content += 1;
       console.log('$scope.testInfo.content的值现在是:',$scope.testInfo.content);
      },1000)
    }]);
  </script>
</body>

你会活见鬼地发现,数据模型一直在变,但是页面却没有刷新:

浅谈Angularjs中不同类型的双向数据绑定

这里就是 Angularjs1.X 双向数据绑定中的第一个坑 ,你会发现 $scope上绑定的数据模型html中显示的内容 有时候并不是实时关联的。这其实和 Angularjs1.X 的执行机制有关系。

如果我们自己来考虑,javascript中有一个变量的值发生了变化,现在要将这个值同步到html页面上,需要怎么做呢?我们需要获取到这个DOM元素,然后改变它的 innerHTML 属性,如果是表单元素就修改 value 。其实 Angularjs 也是这样做的,只不过使用了自己的封装的方法—— $apply() 。那么此处的问题其实就在于,在 setInterval 的回调函数中去修改数据模型的值时,没有触发 $apply() 方法来更新视图,而通过调用 Angularjs 封装的 ng-* 方法(例如 ng-click 点击方法)来修改视图模型时,会自动触发 $apply() 方法,视图也就同步刷新了。

解决方案1

使用 Angularjs 封装过的 $interval 服务来实现定时任务,感兴趣的读者可以自己看一下 Angularjs 源码中 $intervalProvider 的部分,就会发现在方法最后的地方调用了 $rootScope.$apply()

解决方案2

如果依然使用javascript原生的定时方法,那么则需要在修改完视图的数据模型后,手动调用 $scope.$apply() 方法来将数据模型的变动同步到html页面中。

二. Controller与Directive中的双向数据绑定

除了controller与html中的双向绑定, Angularjs 中还有另一个 双向数据绑定 ,那就是controller与directive之间的 绑定 。绑定的形式有很多种,我们先来看一下最常见的 双向绑定

2.1 directive中的双向数据绑定

在设定自定义指令的 scope 参数时,将属性的值设置为 = 就可以实现双向数据绑定,这里API的解释是:

父级controller中的指定变量会与自定义指令link函数中的变量 相互影响

下面的实例中,我们将看看controller中的数据模型 $scope.testInfo.content 的值与自定义指令中 scope.pagination 如何相互影响,是否如定义所说这里的绑定真的是双向的。示例界面如下(demo源码请见附件 demo.html 文件):

浅谈Angularjs中不同类型的双向数据绑定

  • 每次点击 +1 按钮, Scope.testInfo.content 的值都会增加1
  • 每次点击 show $scope.testInfo ,控制台都会打印出 $scope.testInfo 的值
  • 每次点击标签上的数字,则会打印出自定义指令中 scope.pagination 的值,并将该值进行自增

接下来的测试操作,我们将按照如下的流程进行:

  1. 点击5次+1按钮,再点击5次数字标签
  2. 点击show $scope.testInfo按钮

2.2 你丫怎么又不刷新了

随着上一节的操作步骤,我们一起来见证 双向数据绑定 中又一次闹鬼事件:

点击5次 +1 按钮,再点击5次数字标签

结果为:

浅谈Angularjs中不同类型的双向数据绑定

我们看到,第一次点击数字标签时,控制台打出了link函数中 scope.pagination 的值为5,这说明 $scope.testInfo.content 的值被传递给了自定义指令中的 scope.pagination ,也就是说数据从controller流向了directive 。而当我们再点击4次数字标签(一共点了5次)后,从控制台可以看出, scope.pagination 的值已经成为10,而页面上使用 ng-bind 指令获取到的结果却依旧是 5 。也就是说, 数据从没有从directive流向controller 。是不是有一种被骗的感觉?别着急,接着看。

点击 show $scope.testInfo 按钮

结果为:

浅谈Angularjs中不同类型的双向数据绑定

当我们点击 show $scope.testInfo 时,控制台打印出了$scope.testInfo.content的值为5,这下证据坐实了,明明说好的双向数据绑定,然而当自定义指令中的 scope.pagination 改变时, $scope.testInfo.content 并没有跟着一起改变。 But!!!! 我们会发现,这个 show $scope.testInfo 点下去以后,页面上通过 ng-bind 绑定的值却变成了 10 。也就是说, 数据又从directive流回了controller

官方建议使用 $watch 方法来追踪scope中的变量,而当我们这样做时,会发现 $watch 函数 仅能追踪到那些通过修改controller中的数据模型而影响link函数中变量的行为并更新视图 。

这里就是 Angularjs1.X 双向数据绑定中的第二个坑,controller和directive中所谓的 双向数据绑定 ,并不能追踪指定变量的所有变化,而且不是同步完成的。

其实这里的问题仍然和 Angularjs 的运行机制有关,解决方案如下:

解决方案1

使用自定义指令的 templateUrl 属性替换当前指令的模板,使用 ng-click 指令来绑定一个点击响应函数,在响应函数中改变 scope.piganation 的值。

解决方案2

在手动绑定的监听回调中,修改自定义指令作用域内的变量后,使用 scope.$emit( ) 方法通知其父级controller,并在controller中使用 $scope.$on( ) 方法监听同名事件,并修改对应的数据模型的值。

解决方案3

每当改变自定义指令中的变量值后,调用 scope.$apply() 方法,将directive中的变量值同步至controller的数据模型以及页面。

三.原理和实战总结

3.1 Angularjs中双向数据绑定的基本原理

Angularjs中的双向数据绑定,是通过一种叫做* "脏循环检查(dirty-checking)" 的机制实现的。

其基本过程是这样的,每当我们使用 ng-modelng-bind 指令将数据模型中的某个变量值和html页面上某个标签的内容联系起来时,Angular就会把这些变量放进一个 WatchCollection 的集合中,并自动帮我们来监控这些变量。每当 WatchCollection 中有变量出现变动时,Angular就会遍历 WatchCollection 来查看是否有其他监控中的变量也被影响,每当有一个变量被影响,Angular都会在遍历后再进行一次遍历,直到某一次遍历后 WatchCollection 中的变量都没有变化,则Angular会认为当前的改动已经稳定了,然后才会将数据模型的变化同步到DOM元素上去,也就实现了数据绑定。

我们可以把 WatchCollection 理解为当前页面的一种抽象,其中包含着页面上所有有可能发生变化的部分。

3.2 双向数据绑定的实践经验

想要在 Angularjs 项目中更加稳定地使用双向数据绑定,笔者的建议是:

Angularjs 项目中,尽可能地使用Angular告诉你的方式去编写所希望实现的功能。

我们可以回顾一下上面在使用双向数据绑定发生异常时的场景:

  • 使用了原生的定时器(Angular中你应该使用 $interval , $timeout 服务)
  • 用类原生方法(bind)为元素添加事件监听器,并在回调函数中修改了变量的值(Angular中,你应该使用 ng-click 来实现点击事件的监听)
  • ...

你会发现,每当自己没有按照Angular的方式去编写代码,或者没有按照一个模块设计的初衷去使用它时,就无法确切地得到期望的结果。这是很容易理解的,如果你没有按照Angular要求的方式书写代码,凭什么期望它对你的代码做出100%正确的回应呢?至于上述两种数据绑定中出现问题的解决方案,上文已经有所提及,此处不再赘述。

许多人都听说过 "尽量不要在controller中操作DOM" 这句话,实际上它并不意味着你在controller中操作DOM会导致程序报错,而是在说 如果你同时使用 jQueryAngular 两套系统来管理自己的代码,但又没有按照官方指定的方式来规避它们之间的冲突,那代码很可能会变得不稳定。 想想当年 腾讯电脑管家360安全卫士 将你的电脑卡死的场景,你就明白这样做的结果了。

四. 小结——所谓高手

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

Javascript 相关文章推荐
加速IE的Javascript document输出的方法
Dec 02 Javascript
JS实现标签页效果(配合css)
Apr 03 Javascript
firefox下jquery ajax返回object XMLDocument处理方法
Jan 26 Javascript
JavaScript事件委托实例分析
May 26 Javascript
利用JS生成博文目录及CSS定制博客
Feb 10 Javascript
JS Ajax请求如何防止重复提交
Jun 13 Javascript
浅谈jQuery animate easing的具体使用方法(推荐)
Jun 17 Javascript
angularjs点击图片放大实现上传图片预览
Feb 24 Javascript
javascript 判断用户有没有操作页面
Oct 17 Javascript
关于vue-router的那些事儿
May 23 Javascript
vue 点击按钮实现动态挂载子组件的方法
Sep 07 Javascript
webpack4.0+vue2.0利用批处理生成前端单页或多页应用的方法
Jun 28 Javascript
微信小程序实现循环动画效果
Jul 16 #Javascript
mpvue 如何使用腾讯视频插件的方法
Jul 16 #Javascript
Vue实现自定义下拉菜单功能
Jul 16 #Javascript
vue实现城市列表选择功能
Jul 16 #Javascript
JavaScript字符串转数字的5种方法及遇到的坑
Jul 16 #Javascript
Angular-UI Bootstrap组件实现警报功能
Jul 16 #Javascript
JS在if中的强制类型转换方式
Jul 15 #Javascript
You might like
php 变量未定义等错误的解决方法
2011/01/12 PHP
php+MySQL实现登录时验证登录名和密码是否正确
2016/05/10 PHP
浅谈php fopen下载远程文件的函数
2016/11/18 PHP
php实现购物车产品删除功能(2)
2020/07/23 PHP
PHP fopen函数用法实例讲解
2019/02/15 PHP
thinkphp5 模型实例化获得数据对象的教程
2019/10/18 PHP
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
2010/07/06 Javascript
ASP.NET MVC中EasyUI的datagrid跨域调用实现代码
2012/03/14 Javascript
解析页面加载与js函数的执行 onload or ready
2013/12/12 Javascript
JavaScript设计模式之建造者模式介绍
2014/12/28 Javascript
drag-and-drop实现图片浏览器预览
2015/08/06 Javascript
JS实现仿新浪微博发布内容为空时提示功能代码
2015/08/19 Javascript
整理Javascript数组学习笔记
2015/11/29 Javascript
浅析JavaScript 箭头函数 generator Date JSON
2016/05/23 Javascript
详解nodejs 配置文件处理方案
2019/01/02 NodeJs
详解服务端预渲染之Nuxt(介绍篇)
2019/04/07 Javascript
vue二选一tab栏切换新做法实现
2021/01/19 Vue.js
django session完成状态保持的方法
2018/11/27 Python
python 3.3 下载固定链接文件并保存的方法
2018/12/18 Python
Python 循环终止语句的三种方法小结
2019/06/24 Python
Pandas 重塑(stack)和轴向旋转(pivot)的实现
2019/07/22 Python
Python进程间通信 multiProcessing Queue队列实现详解
2019/09/23 Python
Python拆分大型CSV文件代码实例
2019/10/07 Python
python使用配置文件过程详解
2019/12/28 Python
Python3通过chmod修改目录或文件权限的方法示例
2020/06/08 Python
使用Keras中的ImageDataGenerator进行批次读图方式
2020/06/17 Python
弄清Pytorch显存的分配机制
2020/12/10 Python
新西兰航空中国官网:Air New Zealand China
2018/07/24 全球购物
Orvis官网:自1856年以来,优质服装、飞钓装备等
2018/12/17 全球购物
项目合作协议书
2014/09/23 职场文书
四风查摆剖析材料
2014/10/10 职场文书
行政执法作风整顿剖析材料
2014/10/11 职场文书
离婚协议书范本2014
2014/10/27 职场文书
关于颐和园的导游词
2015/01/30 职场文书
小学六年级班主任工作经验交流材料
2015/11/02 职场文书
MySQL索引失效的典型案例
2021/06/05 MySQL