AngularJS数据的双向绑定
数据的双向绑定
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数据的双向绑定相关推荐
- React入门---事件与数据的双向绑定-9
上一节中,我们是从父组件给子组件传送数据,要实现事件与数据的双向绑定,我们来看如何从子组件向父组件传送数据; 接触之前,我们看一些里面函数绑定的知识: 例:通过点击事件改变state的age属性值: ...
- 【Vue】—数据的双向绑定v-model
[Vue]-数据的双向绑定v-model
- 【Vue2 组件间数据的双向绑定】
组件间数据的双向绑定 一.通过自定义事件 二.通过v-model 三.通过.sync修饰符 四.provide和inject 代码中的p-8,mt-8等,都是自己写的工具类,复制代码的同学,可以自行删 ...
- vue3数据的双向绑定
vue3关于数据的双向绑定 一.script setup 现在,没必要把数据写到data里面,或者是写一个setup函数,再进行return出去. import进来的组件,可以直接在页面中使用,不再需 ...
- v-model实现数据的双向绑定
Vue.js提供了v-model指令,用于在表单类元素上双向绑定数据, 比如,在数据框上使用时,输入的内容会实时映射到绑定的数据上. <div id="app">< ...
- 微信小程序input数据的双向绑定
先来看一下html代码 再来看下js代码 这种绑定实例就是给input的一个bindinput属性,指定了一个方法名字.如果想要实现双向绑定,必须使用this.setdata这个方法. 注意!我们在发 ...
- Vue之数据的双向绑定
在vue中实现数据双向绑定的核心在于v-model 示例: <input type="text" name=" " v-model="userN ...
- Vue中实现父子组件的数据的双向绑定(vue.sync的用法)
项目场景: 前两天日常开发时,遇到需要父子组件双向绑定的问题,出现了一些BUG,但是考虑到组件的可维护性,vue中是不允许子组件改变父组件传的props值的 问题描述: 问题场景就是子组件传递数据给父 ...
- angularjs input标签用一个日期插件后数据不能双向绑定了_微信如何定时发朋友圈?(最方便最好用的办法!)...
微信怎么发朋友圈(微信如何定时发朋友圈)作为一个运营新媒体的小编,很多情况下,我都会遇到定时发文的情况,对于我来说,定时发文很简单. 只要将文案编辑好,使用平台的定时发文功能就可以,所以,我就想,微信 ...
最新文章
- nopi 的使用记录
- 又是加拿大!连年拒签NeurIPS参会者被指太荒唐,Hinton亲自过问也没辙
- iaas层次化结构--从业务需求到设计需求
- python + pyqt5 UI和信号槽分离方法
- linux 正则查找email_Hello Iris简易微博类App开发教程3-查找用户和用户登录
- 操作系统 非连续分配_操作系统中的连续和非连续内存分配
- 判断form表单里面的元素属性是否有数据_html form标签的action属性是什么意思?又有哪些用法?(附实例)...
- 反转二叉树 java_leetcode刷题笔记-226. 翻转二叉树(java实现)
- 白话空间统计之二十五:空间权重矩阵(三)解构空间权重矩阵
- 微型计算机基础知识答案,计算机基础知识授课试题及答案
- Xshell官网下载地址
- Linux源码安装pgadmin4,赵彦昌博客 - linux ubuntu 安装pgadmin4
- python基础数据类型之字典(基础三)
- High Reward Low Risk Strategies
- 神州优车拟41亿元收购宝沃汽车67%股权
- 朋友圈投票活动-刷票案例实现与分析
- 8月英语——知耻而后勇
- 避坑:git在push本地文件到远程时,报错ailed to push some refs to https://xx/xx.git的解决办法
- 数字图像处理 - 灰度变换与空间滤波
- Ubuntu 搭建SVN服务器