浅谈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 相关文章推荐
js函数调用常用方法详解
Dec 03 Javascript
JS限制Textarea文本域字符个数的具体实现
Aug 02 Javascript
js判断某个方法是否存在实例代码
Jan 10 Javascript
JavaScript通过事件代理高亮显示表格行的方法
May 27 Javascript
基于JQuery实现的跑马灯效果(文字无缝向上翻动)
Dec 02 Javascript
JS中传递参数的几种不同方法比较
Jan 20 Javascript
vue使用vue-cli快速创建工程
Jul 28 Javascript
Javascript防止图片拉伸的自适应处理方法
Dec 26 Javascript
vue.js2.0点击获取自己的属性和jquery方法
Feb 23 jQuery
详解Vue-cli3.X使用px2rem遇到的问题
Aug 09 Javascript
Vue+iview+webpack ie浏览器兼容简单处理
Sep 20 Javascript
Vue循环中多个input绑定指定v-model实例
Aug 31 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中几个常用的魔术常量
2012/02/23 PHP
smarty基础之拼接字符串的详解
2013/06/18 PHP
PHP实现把MySQL数据库导出为.sql文件实例(仿PHPMyadmin导出功能)
2014/05/10 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十六)
2014/06/30 PHP
浅谈ThinkPHP的URL重写
2014/11/25 PHP
浅谈PHP链表数据结构(单链表)
2016/06/08 PHP
js读取配置文件自写
2014/02/11 Javascript
动态加载jquery库的方法
2014/02/12 Javascript
js用typeof方法判断undefined类型
2014/07/15 Javascript
JS中产生20位随机数以0-9为例也可以是a-z A-Z
2014/08/01 Javascript
jQuery mobile类库使用时加载导航历史的方法简介
2015/12/04 Javascript
使用Promise解决多层异步调用的简单学习心得
2016/05/17 Javascript
jQuery实现公告新闻自动滚屏效果实例代码
2016/07/14 Javascript
微信小程序 在Chrome浏览器上运行以及WebStorm的使用
2016/09/27 Javascript
详解在Vue中通过自定义指令获取dom元素
2017/03/04 Javascript
基于JavaScript实现前端数据多条件筛选功能
2020/08/19 Javascript
React Native中Navigator的使用方法示例
2017/10/13 Javascript
详解Vue.directive 自定义指令
2019/03/27 Javascript
[48:21]林俊杰圣堂刺客超神杀戮秀
2014/10/29 DOTA
Python中的__new__与__init__魔术方法理解笔记
2014/11/08 Python
Django添加sitemap的方法示例
2018/08/06 Python
python实现beta分布概率密度函数的方法
2019/07/08 Python
python opencv将图片转为灰度图的方法示例
2019/07/31 Python
Python 日期时间datetime 加一天,减一天,加减一小时一分钟,加减一年
2020/04/16 Python
python连接mysql有哪些方法
2020/06/24 Python
CSS3字体效果的设置方法小结
2016/06/13 HTML / CSS
人力资源管理专业毕业生自我评价
2013/09/21 职场文书
协议书范本
2014/04/23 职场文书
单位在职证明书
2014/09/11 职场文书
八荣八耻演讲稿
2014/09/15 职场文书
2014年十一国庆节爱国演讲稿
2014/09/23 职场文书
合伙开公司协议书范本
2014/10/28 职场文书
学习型家庭事迹材料(2016精选版)
2016/02/29 职场文书
车位出租协议书范本
2016/03/19 职场文书
选择比努力更重要?这是长期以来对“努力”的最大误解
2019/07/12 职场文书
PyTorch中permute的使用方法
2022/04/26 Python