浅谈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 相关文章推荐
简单常用的幻灯片播放实现代码
Sep 25 Javascript
jquery创建表格(自动增加表格)代码分享
Dec 25 Javascript
jQuery中事件对象e的事件冒泡用法示例介绍
Apr 25 Javascript
js实现索引图片切换效果
Nov 21 Javascript
JS实现简易的图片拖拽排序实例代码
Jun 09 Javascript
Vue实例中生命周期created和mounted的区别详解
Aug 25 Javascript
vue按需引入element Transfer 穿梭框
Sep 30 Javascript
python爬取安居客二手房网站数据(实例讲解)
Oct 19 Javascript
Parcel 打包示例(React HelloWorld)
Jan 16 Javascript
关于TypeScript模块导入的那些事
Jun 12 Javascript
Vue源码之关于vm.$delete()/Vue.use()内部原理详解
May 01 Javascript
vue使用better-scroll实现滑动以及左右联动
Jun 30 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
一个分页的论坛
2006/10/09 PHP
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
2007/08/26 PHP
逐步提升php框架的性能
2008/01/10 PHP
php遍历数组的方法分享
2012/03/22 PHP
php函数与传递参数实例分析
2014/11/15 PHP
PHP的关于变量和日期处理的一些面试题目整理
2015/08/10 PHP
PHP错误机制知识汇总
2016/03/24 PHP
Yii CFileCache 获取不到值的原因分析
2017/02/08 PHP
php基于session锁防止阻塞请求的方法分析
2017/08/07 PHP
PHP的JSON封装、转变及输出操作示例
2019/09/27 PHP
php操作redis命令及代码实例大全
2020/11/19 PHP
利用JQuery为搜索栏增加tag提示
2009/06/22 Javascript
node.js中的fs.chownSync方法使用说明
2014/12/16 Javascript
JavaScript中split() 使用方法汇总
2015/04/17 Javascript
javascript实现动态改变层大小的方法
2015/05/14 Javascript
JAVA四种基本排序方法实例总结
2015/07/24 Javascript
JSON字符串转JSON对象
2015/07/31 Javascript
jQuery实现的网页右下角tab样式在线客服效果代码
2015/10/23 Javascript
JS 学习总结之正则表达式的懒惰性和贪婪性
2017/07/03 Javascript
详解基于DllPlugin和DllReferencePlugin的webpack构建优化
2018/06/28 Javascript
原生js实现移动端Touch轮播图的方法步骤
2019/01/03 Javascript
Vue项目如何引入bootstrap、elementUI、echarts
2020/11/26 Vue.js
[00:35]DOTA2上海特级锦标赛 MVP.Phx战队宣传片
2016/03/04 DOTA
python简单线程和协程学习心得(分享)
2017/06/14 Python
pandas.DataFrame删除/选取含有特定数值的行或列实例
2018/11/07 Python
对python读写文件去重、RE、set的使用详解
2018/12/11 Python
python自带tkinter库实现棋盘覆盖图形界面
2019/07/17 Python
Django实现分页显示效果
2019/10/31 Python
html5 offlline 缓存使用示例
2013/06/24 HTML / CSS
html5唤醒APP小记
2019/03/27 HTML / CSS
中国医药集团国药在线:国药网
2017/02/06 全球购物
澳大利亚在线批发商:Simply Wholesale
2021/02/24 全球购物
研究生求职自荐书
2014/06/23 职场文书
2014年惩防体系建设工作总结
2014/12/01 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
ORM模型框架操作mysql数据库的方法
2021/07/25 MySQL