数据的双向绑定

Angular实现了双向绑定机制。所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面。

一个最简单的示例就是这样:

<div ng-controller="CounterCtrl"><span ng-bind="counter"></span><button ng-click="counter++">increase</button>
</div>
function CounterCtrl($scope) {$scope.counter = 1;
}

这个例子很简单,毫无特别之处,每当点击一次按钮,界面上的数字就增加一。

绑定数据是怎样生效的

初学AngularJS的人可能会踩到这样的坑,假设有一个指令:

var app = angular.module("test", []);app.directive("myclick", function() {return function (scope, element, attr) {element.on("click", function() {scope.counter++;});};
});app.controller("CounterCtrl", function($scope) {$scope.counter = 0;
});
<body ng-app="test"><div ng-controller="CounterCtrl"><button myclick>increase</button><span ng-bind="counter"></span></div>
</body>

这个时候,点击按钮,界面上的数字并不会增加。很多人会感到迷惑,因为他查看调试器,发现数据确实已经增加了,Angular不是双向绑定吗,为什么数据变化了,界面没有跟着刷新?

试试在scope.counter++;这句之后加一句scope.digest();再看看是不是好了?

为什么要这么做呢,什么情况下要这么做呢?我们发现第一个例子中并没有digest,而且,如果你写了digest,它还会抛出异常,说正在做其他的digest,这是怎么回事?

我们先想想,假如没有AngularJS,我们想要自己实现这么个功能,应该怎样?

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>two-way binding</title></head><body onload="init()"><button ng-click="inc">increase 1</button><button ng-click="inc2">increase 2</button><span style="color:red" ng-bind="counter"></span><span style="color:blue" ng-bind="counter"></span><span style="color:green" ng-bind="counter"></span><script type="text/javascript">/* 数据模型区开始 */var counter = 0;function inc() {counter++;}function inc2() {counter+=2;}/* 数据模型区结束 *//* 绑定关系区开始 */function init() {bind();}function bind() {var list = document.querySelectorAll("[ng-click]");for (var i=0; i<list.length; i++) {list[i].onclick = (function(index) {return function() {window[list[index].getAttribute("ng-click")]();apply();};})(i);}}function apply() {var list = document.querySelectorAll("[ng-bind='counter']");for (var i=0; i<list.length; i++) {list[i].innerHTML = counter;}}/* 绑定关系区结束 */</script></body>
</html>

可以看到,在这么一个简单的例子中,我们做了一些双向绑定的事情。从两个按钮的点击到数据的变更,这个很好理解,但我们没有直接使用DOM的onclick方法,而是搞了一个ng-click,然后在bind里面把这个ng-click对应的函数拿出来,绑定到onclick的事件处理函数中。为什么要这样呢?因为数据虽然变更了,但是还没有往界面上填充,我们需要在此做一些附加操作。

从另外一个方面看,当数据变更的时候,需要把这个变更应用到界面上,也就是那三个span里。但由于Angular使用的是脏检测,意味着当改变数据之后,你自己要做一些事情来触发脏检测,然后再应用到这个数据对应的DOM元素上。问题就在于,怎样触发脏检测?什么时候触发?

我们知道,一些基于setter的框架,它可以在给数据设值的时候,对DOM元素上的绑定变量作重新赋值。脏检测的机制没有这个阶段,它没有任何途径在数据变更之后立即得到通知,所以只能在每个事件入口中手动调用apply(),把数据的变更应用到界面上。在真正的Angular实现中,这里先进行脏检测,确定数据有变化了,然后才对界面设值。

所以,我们在ng-click里面封装真正的click,最重要的作用是为了在之后追加一次apply(),把数据的变更应用到界面上去。

那么,为什么在ng-click里面调用$digest的话,会报错呢?因为Angular的设计,同一时间只允许一个$digest运行,而ng-click这种内置指令已经触发了$digest,当前的还没有走完,所以就出错了。

$digest和$apply

在Angular中,有$apply和$digest两个函数,我们刚才是通过$digest来让这个数据应用到界面上。但这个时候,也可以不用$digest,而是使用$apply,效果是一样的,那么,它们的差异是什么呢?

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

var app = angular.module("test", []);app.directive("myclick", function() {return function (scope, element, attr) {element.on("click", function() {scope.counter++;scope.$apply(function() {scope.counter++;});});};
});app.controller("CounterCtrl", function($scope) {$scope.counter = 0;
});

除此之外,还有别的区别吗?

在简单的数据模型中,这两者没有本质差别,但是当有层次结构的时候,就不一样了。考虑到有两层作用域,我们可以在父作用域上调用这两个函数,也可以在子作用域上调用,这个时候就能看到差别了。

对于$digest来说,在父作用域和子作用域上调用是有差别的,但是,对于$apply来说,这两者一样。我们来构造一个特殊的示例:

var app = angular.module("test", []);app.directive("increasea", function() {return function (scope, element, attr) {element.on("click", function() {scope.a++;scope.$digest();});};
});app.directive("increaseb", function() {return function (scope, element, attr) {element.on("click", function() {scope.b++;scope.$digest();    //这个换成$apply即可});};
});app.controller("OuterCtrl", ["$scope", function($scope) {$scope.a = 1;$scope.$watch("a", function(newVal) {console.log("a:" + newVal);});$scope.$on("test", function(evt) {$scope.a++;});
}]);app.controller("InnerCtrl", ["$scope", function($scope) {$scope.b = 2;$scope.$watch("b", function(newVal) {console.log("b:" + newVal);$scope.$emit("test", newVal);});
}]);
<div ng-app="test"><div ng-controller="OuterCtrl"><div ng-controller="InnerCtrl"><button increaseb>increase b</button><span ng-bind="b"></span></div><button increasea>increase a</button><span ng-bind="a"></span></div>
</div>

这时候,我们就能看出差别了,在increase b按钮上点击,这时候,a跟b的值其实都已经变化了,但是界面上的a没有更新,直到点击一次increase a,这时候刚才对a的累加才会一次更新上来。怎么解决这个问题呢?只需在increaseb这个指令的实现中,把$digest换成$apply即可。

当调用$digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用$apply的时候,会触发作用域树上的所有监控。

因此,从性能上讲,如果能确定自己作的这个数据变更所造成的影响范围,应当尽量调用$digest,只有当无法精确知道数据变更造成的影响范围时,才去用$apply,很暴力地遍历整个作用域树,调用其中所有的监控。

从另外一个角度,我们也可以看到,为什么调用外部框架的时候,是推荐放在$apply中,因为只有这个地方才是对所有数据变更都应用的地方,如果用$digest,有可能临时丢失数据变更。

脏检测的利弊

很多人对Angular的脏检测机制感到不屑,推崇基于setter,getter的观测机制,在我看来,这只是同一个事情的不同实现方式,并没有谁完全胜过谁,两者是各有优劣的。

大家都知道,在循环中批量添加DOM元素的时候,会推荐使用DocumentFragment,为什么呢,因为如果每次都对DOM产生变更,它都要修改DOM树的结构,性能影响大,如果我们能先在文档碎片中把DOM结构创建好,然后整体添加到主文档中,这个DOM树的变更就会一次完成,性能会提高很多。

同理,在Angular框架里,考虑到这样的场景:

function TestCtrl($scope) {$scope.numOfCheckedItems = 0;var list = [];for (var i=0; i<10000; i++) {list.push({index: i,checked: false});}$scope.list = list;$scope.toggleChecked = function(flag) {for (var i=0; i<list.length; i++) {list[i].checked = flag;$scope.numOfCheckedItems++;}};
}

如果界面上某个文本绑定这个numOfCheckedItems,会怎样?在脏检测的机制下,这个过程毫无压力,一次做完所有数据变更,然后整体应用到界面上。这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。

所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。

AngularJS数据的双向绑定相关推荐

  1. React入门---事件与数据的双向绑定-9

    上一节中,我们是从父组件给子组件传送数据,要实现事件与数据的双向绑定,我们来看如何从子组件向父组件传送数据; 接触之前,我们看一些里面函数绑定的知识: 例:通过点击事件改变state的age属性值: ...

  2. 【Vue】—数据的双向绑定v-model

    [Vue]-数据的双向绑定v-model

  3. 【Vue2 组件间数据的双向绑定】

    组件间数据的双向绑定 一.通过自定义事件 二.通过v-model 三.通过.sync修饰符 四.provide和inject 代码中的p-8,mt-8等,都是自己写的工具类,复制代码的同学,可以自行删 ...

  4. vue3数据的双向绑定

    vue3关于数据的双向绑定 一.script setup 现在,没必要把数据写到data里面,或者是写一个setup函数,再进行return出去. import进来的组件,可以直接在页面中使用,不再需 ...

  5. v-model实现数据的双向绑定

    Vue.js提供了v-model指令,用于在表单类元素上双向绑定数据, 比如,在数据框上使用时,输入的内容会实时映射到绑定的数据上. <div id="app">< ...

  6. 微信小程序input数据的双向绑定

    先来看一下html代码 再来看下js代码 这种绑定实例就是给input的一个bindinput属性,指定了一个方法名字.如果想要实现双向绑定,必须使用this.setdata这个方法. 注意!我们在发 ...

  7. Vue之数据的双向绑定

    在vue中实现数据双向绑定的核心在于v-model 示例: <input type="text" name=" " v-model="userN ...

  8. Vue中实现父子组件的数据的双向绑定(vue.sync的用法)

    项目场景: 前两天日常开发时,遇到需要父子组件双向绑定的问题,出现了一些BUG,但是考虑到组件的可维护性,vue中是不允许子组件改变父组件传的props值的 问题描述: 问题场景就是子组件传递数据给父 ...

  9. angularjs input标签用一个日期插件后数据不能双向绑定了_微信如何定时发朋友圈?(最方便最好用的办法!)...

    微信怎么发朋友圈(微信如何定时发朋友圈)作为一个运营新媒体的小编,很多情况下,我都会遇到定时发文的情况,对于我来说,定时发文很简单. 只要将文案编辑好,使用平台的定时发文功能就可以,所以,我就想,微信 ...

最新文章

  1. nopi 的使用记录
  2. 又是加拿大!连年拒签NeurIPS参会者被指太荒唐,Hinton亲自过问也没辙
  3. iaas层次化结构--从业务需求到设计需求
  4. python + pyqt5 UI和信号槽分离方法
  5. linux 正则查找email_Hello Iris简易微博类App开发教程3-查找用户和用户登录
  6. 操作系统 非连续分配_操作系统中的连续和非连续内存分配
  7. 判断form表单里面的元素属性是否有数据_html form标签的action属性是什么意思?又有哪些用法?(附实例)...
  8. 反转二叉树 java_leetcode刷题笔记-226. 翻转二叉树(java实现)
  9. 白话空间统计之二十五:空间权重矩阵(三)解构空间权重矩阵
  10. 微型计算机基础知识答案,计算机基础知识授课试题及答案
  11. Xshell官网下载地址
  12. Linux源码安装pgadmin4,赵彦昌博客 - linux ubuntu 安装pgadmin4
  13. python基础数据类型之字典(基础三)
  14. High Reward Low Risk Strategies
  15. 神州优车拟41亿元收购宝沃汽车67%股权
  16. 朋友圈投票活动-刷票案例实现与分析
  17. 8月英语——知耻而后勇
  18. 避坑:git在push本地文件到远程时,报错ailed to push some refs to https://xx/xx.git的解决办法
  19. 数字图像处理 - 灰度变换与空间滤波
  20. Ubuntu 搭建SVN服务器

热门文章

  1. 综合布线命名规划原则
  2. gzinflate php cetnos,Centos 6.5升级git版本的办法
  3. spad dtof lidar车载IMX459更新系列一特性和功能
  4. 计算机毕业设计ssm虚拟银行业务培训游戏
  5. 示波器正确测量电源纹波
  6. 计算机和建筑的影响,计算机应用于建筑设计中的影响
  7. 列表变成向量 列表变向量 list vector
  8. SqlServer数据库常用连接字符串
  9. MySQL 自定义函数一文读懂
  10. 豪杰信息杯I 湘潭oj1268-Strange Optimization