Angular JS数据的双向绑定

接触AngularJS许了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同。为此,记录了一些思考,给自己回顾,也供他人参考。
初步大致有以下几个方面:
数据的双向绑定
Angular实现了双向绑定机制。所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面。
一个最简单的示例就是这样:
<div ng-controller="CounterCtrl">
<span ng-bind="counter"></span>
<button ng-click="counter=counter+1">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这样把批量操作延时到一次更新,否则性能会更低。
所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Angular
# JS数据的双向绑定
# JS数据绑定
# JS数据绑定详解
# javascript实现数据双向绑定的三种方式小结
# AngularJS学习笔记(三)数据双向绑定的简单实例
# AngularJS 双向数据绑定详解简单实例
# JS原生数据双向绑定实现代码
# JavaScript中双向数据绑定详解
# jQuery实现html双向绑定功能示例
# js最简单的双向绑定实例讲解
# 绑定
# 这个时候
# 还没有
# 就能
# 才是
# 更应
# 这时候
# 可以看到
# 考虑到
# 它可以
# 各有
# 什么呢
# 另外一个
# 这么做
# 才对
# 这两者
# 的人
# 的是
# 哪些方面
# 就会
相关文章:
盐城做公司网站,江苏电子版退休证办理流程?
焦点电影公司作品,电影焦点结局是什么?
制作国外网站的软件,国外有哪些比较优质的网站推荐?
网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?
公司网站制作需要多少钱,找人做公司网站需要多少钱?
,制作一个手机app网站要多少钱?
如何快速搭建安全的FTP站点?
如何通过VPS建站实现广告与增值服务盈利?
正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?
建站之星安装后如何自定义网站颜色与字体?
详解jQuery中基本的动画方法
javascript中的try catch异常捕获机制用法分析
小型网站制作HTML,*游戏网站怎么搭建?
专业网站建设制作报价,网页设计制作要考什么证?
Python lxml的etree和ElementTree有什么区别
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
教程网站设计制作软件,怎么创建自己的一个网站?
如何快速搭建高效WAP手机网站?
建站中国必看指南:CMS建站系统+手机网站搭建核心技巧解析
微信小程序制作网站有哪些,微信小程序需要做网站吗?
IOS倒计时设置UIButton标题title的抖动问题
如何在云主机上快速搭建多站点网站?
如何实现建站之星域名转发设置?
javascript基本数据类型及类型检测常用方法小结
如何在Golang中处理模块冲突_解决依赖版本不兼容问题
深圳网站制作的公司有哪些,dido官方网站?
如何通过VPS搭建网站快速盈利?
制作宣传网站的软件,小红书可以宣传网站吗?
如何将凡科建站内容保存为本地文件?
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?
定制建站如何定义?其核心优势是什么?
建站主机解析:虚拟主机配置与服务器选择指南
如何挑选高效建站主机与优质域名?
北京网站制作公司哪家好一点,北京租房网站有哪些?
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
制作网站的公司有哪些,做一个公司网站要多少钱?
湖州网站制作公司有哪些,浙江中蓝新能源公司官网?
中山网站制作网页,中山新生登记系统登记流程?
如何在云指建站中生成FTP站点?
免费视频制作网站,更新又快又好的免费电影网站?
如何在IIS中新建站点并解决端口绑定冲突?
建站主机选购指南:核心配置与性价比推荐解析
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
如何用PHP工具快速搭建高效网站?
番禺网站制作公司哪家值得合作,番禺图书馆新馆开放了吗?
重庆网站制作公司哪家好,重庆中考招生办官方网站?
武清网站制作公司,天津武清个人营业执照注销查询系统网站?
如何配置支付宝与微信支付功能?
如何快速查询域名建站关键信息?
c++23 std::expected怎么用 c++优雅处理函数错误返回【详解】
*请认真填写需求信息,我们会在24小时内与您取得联系。