Angularjs 双向绑定机制解析
文章转自:http://www.2cto.com/kf/201408/327594.html
- AngularJs 的元素与模型双向绑定依赖于循环检测它们之间的值,这种做法叫做脏检测,这几天研究了一下其源码,将 Angular 的实现分享一下。
首先看看如何将 Model 的变更更新到 UIAngular 的 Model 是一个 Scope 的类型,每个 Scope 都归属于一个 Directive 对象,比如 $rootScope 就归属于 ng-app。从 ng-app 往下,每个 Directive 创建的 Scope 都会一层一层链接下去,形成一个以 $rootScope 为根的链表,注意 Scope 还有同级的概念,形容更贴切我觉得应该是一棵树。我们大概看一下 Scope 都有哪些成员:function Scope() {this.$id = nextUid();// 依次为: 阶段、父 Scope、Watch 函数集、下一个同级 Scope、上一个同级 Scope、首个子级 Scope、最后一个子级 Scopethis.$$phase = this.$parent = this.$$watchers =this.$$nextSibling = this.$$prevSibling =this.$$childHead = this.$$childTail = null;// 重写 this 属性以便支持原型链this['this'] = this.$root = this;this.$$destroyed = false;// 以当前 Scope 为上下文的异步求值队列,也就是一堆 Angular 表达式this.$$asyncQueue = [];this.$$postDigestQueue = [];this.$$listeners = {};this.$$listenerCount = {};this.$$isolateBindings = {};}Scope.$digest,这是 Angular 提供的从 Model 更新到 UI 的接口,你从哪个 Scope 调用,那它就会从这个 Scope 开始遍历,通知模型更改给各个 watch 函数,来看看 $digest 的源码:$digest: function() {var watch, value, last,watchers,asyncQueue = this.$$asyncQueue,postDigestQueue = this.$$postDigestQueue,length,dirty, ttl = TTL,next, current, target = this,watchLog = [],logIdx, logMsg, asyncTask;// 标识阶段,防止多次进入beginPhase('$digest');// 最后一个检测到脏值的 watch 函数lastDirtyWatch = null;// 开始脏检测,只要还有脏值或异步队列不为空就会一直循环do {dirty = false;// 当前遍历到的 Scopecurrent = target;// 处理异步队列中所有任务, 这个队列由 scope.$evalAsync 方法输入while(asyncQueue.length) {try {asyncTask = asyncQueue.shift();asyncTask.scope.$eval(asyncTask.expression);} catch (e) {clearPhase();$exceptionHandler(e);}lastDirtyWatch = null;}traverseScopesLoop:do {// 取出当前 Scope 的所有 watch 函数if ((watchers = current.$$watchers)) {length = watchers.length;while (length--) {try {watch = watchers[length];if (watch) {// 1.取 watch 函数的运算新值,直接与 watch 函数最后一次值比较// 2.如果比较失败则尝试调用 watch 函数的 equal 函数,如果没有 equal 函数则直接比较新旧值是否都是 numberif ((value = watch.get(current)) !== (last = watch.last) &&!(watch.eq? equals(value, last): (typeof value == 'number' && typeof last == 'number'&& isNaN(value) && isNaN(last)))) {// 检测到值改变,设置一些标识dirty = true;lastDirtyWatch = watch;watch.last = watch.eq ? copy(value, null) : value;// 调用 watch 函数的变更通知函数, 也就是说各个 directive 从这里更新 UIwatch.fn(value, ((last === initWatchVal) ? value : last), current);// 当 digest 调用次数大于 5 的时候(默认10),记录下来以便开发人员分析。if (ttl < 5) {logIdx = 4 - ttl;if (!watchLog[logIdx]) watchLog[logIdx] = [];logMsg = (isFunction(watch.exp))? 'fn: ' + (watch.exp.name || watch.exp.toString()): watch.exp;logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);watchLog[logIdx].push(logMsg);}} else if (watch === lastDirtyWatch) {// If the most recently dirty watcher is now clean, short circuit since the remaining watchers// have already been tested.dirty = false;break traverseScopesLoop;}}} catch (e) {clearPhase();$exceptionHandler(e);}}}// 恕我理解不能,下边这三句是卖萌吗// Insanity Warning: scope depth-first traversal// yes, this code is a bit crazy, but it works and we have tests to prove it!// this piece should be kept in sync with the traversal in $broadcast// 没有子级 Scope,也没有同级 Scopeif (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {// 又判断一遍不知道为什么,不过这个时候 next === undefined 了,也就退出当前 Scope 的 watch 遍历了while(current !== target && !(next = current.$$nextSibling)) {current = current.$parent;}}} while ((current = next));// 当 TTL 用完,依旧有未处理的脏值和异步队列则抛出异常if((dirty || asyncQueue.length) && !(ttl--)) {clearPhase();throw $rootScopeMinErr('infdig','{0} $digest() iterations reached. Aborting!\n' +'Watchers fired in the last 5 iterations: {1}',TTL, toJson(watchLog));}} while (dirty || asyncQueue.length);// 退出 digest 阶段,允许其他人调用clearPhase();while(postDigestQueue.length) {try {postDigestQueue.shift()();} catch (e) {$exceptionHandler(e);}}}虽然看起来很长,但是很容易理解,默认从 $rootScope 开始遍历,对每个 watch 函数求值比较,出现新值则调用通知函数,由通知函数更新 UI,我们来看看 ng-model 是怎么注册通知函数的:$scope.$watch(function ngModelWatch() {var value = ngModelGet($scope);// 如果 ng-model 当前记录的 modelValue 不等于 Scope 的最新值if (ctrl.$modelValue !== value) {var formatters = ctrl.$formatters,idx = formatters.length;// 使用格式化器格式新值,比如 number,email 之类ctrl.$modelValue = value;while(idx--) {value = formatters[idx](value);}// 将新值更新到 UIif (ctrl.$viewValue !== value) {ctrl.$viewValue = value;ctrl.$render();}}return value;});那么 UI 更改如何更新到 Model 呢很简单,靠 Directive 编译时绑定的事件,比如 ng-model 绑定到一个输入框的时候事件代码如下:var ngEventDirectives = {};forEach('click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),function(name) {var directiveName = directiveNormalize('ng-' + name);ngEventDirectives[directiveName] = ['$parse', function($parse) {return {compile: function($element, attr) {var fn = $parse(attr[directiveName]);return function(scope, element, attr) {// 触发以上指定的事件,就将元素的 scope 和 event 对象一起发送给 direciveelement.on(lowercase(name), function(event) {scope.$apply(function() {fn(scope, {$event:event});});});};}};}];});Directive 接收到输入事件后根据需要再去 Update Model 就好啦。相信经过以上研究应该对 Angular 的绑定机制相当了解了吧,现在可别跟人家说起脏检测就觉得是一个 while(true) 一直在求值效率好低什么的,跟你平时用事件没啥两样,多了几次循环而已。最后注意一点就是平时你通常不需要手动调用 scope.$digest,特别是当你的代码在一个 $digest 中被回调的时候,因为已经进入了 digest 阶段所以你再调用则会抛出异常。我们只在没有 Scope 上下文的代码里边需要调用 digest,因为此时你对 UI 或 Model 的更改 Angular 并不知情。
Angularjs 双向绑定机制解析的更多相关文章
- 深入学习AngularJS中数据的双向绑定机制
来自:http://www.jb51.net/article/80454.htm Angular JS (Angular.JS) 是一组用来开发Web页面的框架.模板以及数据绑定和丰富UI组件.它支持 ...
- angularjs深入理解向指令传递数据,双向绑定机制
<!DOCTYPE html> <html lang="zh-CN" ng-app="app"> <head> <me ...
- AngularJS双向绑定,手动实施观察
实现这样的一个需求:页面中某个地方显示某个文本框的值经过计算得到的结果,而且是文本框值每次变化显示的计算结果也跟着动态变化. 在controller中可以声明一个对象,它的一个字段用来存储初始值: $ ...
- angularJS双向绑定和依赖反转
一.双向绑定: UI<-->数据 数据->UI (数据改变UI跟着变) UI->数据 (UI改变数据跟着变) 数据改变->UI改变原理: 监听数据是否改变,如果改变更新U ...
- AngularJs双向绑定详解
双向绑定的三个重要方法: $scope.$apply() $scope.$digest() $scope.$watch() 一.$scope.$watch() 我理解的$watch就是将对某个数据的监 ...
- vue的双向绑定原理解析(vue项目重构二)
现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...
- mvvm双向绑定机制的原理和代码实现
mvvm框架的双向绑定,即当对象改变时,自动改变相关的dom元素的值,反之,当dom元素改变时,能自动更新对象的值,当然dom元素一般是指可输出的input元素. 1. 首先实现单向绑定,在指定对象的 ...
- AngularJs双向绑定
模型数据(Data) 模型是从AngularJS作用域对象的属性引申的.模型中的数据可能是Javascript对象.数组或基本类型,这都不重要,重要的是,他们都属于AngularJS作用域对象. An ...
- AngularJS 脏检查机制
脏检查是AngularJS的核心机制之一,它是实现双向绑定.MVVM模式的重要基础. 一.digest循环 AngularJS将双向绑定转换为一个堆watch表达式,然后递归检查这些watch表达式的 ...
随机推荐
- Javascript execCommon
http://blog.csdn.net/woshinia/article/details/18664903 https://developer.mozilla.org/zh-CN/docs/Web/ ...
- CSS基本知识1-CSS基本概念
CSS基本概念: 选择器{属性:值;属性:值} CSS继承:子元素继承父元素样式,父子关系看DOM结构. CSS覆盖: 浏览器缺省设置 外部样式表 内部样式表(位于 <head> 标签内部 ...
- spring-初始化完成后运行指定内容
方案1:继承ApplicationListener public class InstantiationTracingBeanPostProcessor implements ApplicationL ...
- OpenLayers的定制
最近因为工作的需要,把主流的的一些GIS的javascript库看了一遍,主要是ArcGIS Server API for Javascript,Openlayers和Leaflet. 先说说ArcG ...
- 走进AngularJs(二) ng模板中常用指令的使用方式
通过使用模板,我们可以把model和controller中的数据组装起来呈现给浏览器,还可以通过数据绑定,实时更新视图,让我们的页面变成动态的.ng的模板真是让我爱不释手.学习ng道路还很漫长,从模板 ...
- 【USACO 2.4 】Bessie Come Home
题意:给出n条有权的双向边(10000),求到达Z最近的大写字母,及其距离. 题解:即求Z出发的最短路,用dijstra就可以了,注意边要开到20000以上. /* TASK: comehome LA ...
- SHOI2016游记&滚粗记&酱油记
Day0 学校刚期中考完,全科血崩,感觉这次真要考不到一本线了tat 晚上写了个可持久化trie的题,也懒得敲板子(上个礼拜都敲过了),就碎叫了 Day1 上午起床吃饭水群看球,吃完中饭就去考场了. ...
- 表格不被内容撑大,且超出的内容变为省略号(css)
今天写代码,发现表格会被内容撑大,影响到了网页的整体布局. 百度了一解决方法,下面是代码和我的备注 table{table-layout: fixed;} //固定表格 table td ...
- npm 使用记录
在 Mint 下安装 pencil,折腾半天,发现它对 firefox 的支持,只到 46.0 .本来打算研究下怎么用 xulrunner 来跑 pencil 这个 web 应用,查看项目资源的时候, ...
- Alpha阶段项目Postmortem
以下对成员名字的简称: 陈鸿超 = 陈1 陈彦吉 = 陈2 石浩然 = 石 韩青长 = 韩 1. 设想和目标 1.1 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? ...