AngularJS原理篇

为了更好的解耦,各个框架(Extjsjquery)的目标都是实现MVC,只是方式不同,而angular采用的是双向数据绑定。

163139_OlCM_347877.png

图片摘自《用AngularJS开发下一代WEB应用》中文译者大漠穷秋的博客

如图,为了要实现双向数据绑定,angular以指令、scope、依赖注入、digest作为基础,而这些最终都是依赖于底层的JS版编译器。MVCServicefactorymodule上次都已经有介绍,那么这次我们先从compiler开始讲起。

一、compilerdirectives

相关源码可见src/ng/compiler.js(本篇源码的版本号1.3.0-beta.11,下同)。

angular实现了一个JS版的编译器,如我们所知的编译器一样,会有编译(compiler)、链接(link)两个过程。

compiler过程主要包含:

1. 将所有指令按优先级排序(详见collectDirectives函数);

2. 执行每个指令的compile函数(详见applyDirectivesToNode函数),如果一个指令需要被克隆很多次(比如:ng-repeat),compile函数只在编译阶段被执行一次,复制这些模板,但是link函数会针对每个被复制的实例来执行;

3. 把每个compile函数返回的link函数打包到一个总的link函数中(详见compileNodes函数)

link过程主要包含:

1. scope绑定到DOM上;(详见nodeLinkFn函数)

2. 在元素上注册事件监听器;

3. 使用$watch监控数据模型,从而获知值的变化;

常见的一些JS Template框架也采用了类似的compile策略以提升效率,比如HandleBarsExtJSXTemplate。从这个角度来看,可以把angular的指令看作增强版的JS Template机制。

 

二、依赖注入(DI)

相关源码可见src/auto/injector.js

熟悉Java的对依赖注入应该都不会陌生,Java实现依赖注入主要有三种方式:接口注入、set注入、构造注入,而angular采用的是构造注入。

163140_NHx5_347877.png 

代码分析:

98-使用toString()来获得方法的定义

99-正则表达式来查找方法的标志

100-102-解析注入参数成array,并保存到$inject

115-返回参数集合

之所以有fnarray的区别是因为依赖注入有以下两种写法:

163140_ucAo_347877.png 

angular在获取到注入参数后,根据参数名称来获取对象实体,从而注入所需对象:

163140_RjWs_347877.png 

既然是根据参数名称,对于fn的解析方式在进行js代码压缩时,如果把$scope$http压缩改名,则angular将找不到其对应的对象,会导致应用程序出错,而array的方式将能保留参数的字符串名称。

所以在定义你的服务时请尽量使用array的方式来定义参数。

 

三、双向数据绑定

相关源码可见src/ng/rootScope.js

双向数据绑定的核心问题是“脏值检测”——如何去判断值变化,难点是“循环依赖问题”——当值一直在变化(比如两个相互依赖的值发生改变),无法达到稳定状态(即值不变)时如何处理。

前面link过程中有说angular使用$watch来监控数据模型,以此来判断值的变化情况。我们先来看看$watch函数:

163140_q6be_347877.png 

参数:

watchExp:需要监视的值或表达式

listener:监听函数,值变化时执行

objectEquality:是否开启值检测,为true时会检测对象或者数组内部变更(即选择以===的方式比较还是angular.equals的方式)

内部变量:

arrayscope.$$watchers:存储注册过的所有监听器。每次digest时会去遍历array中所有监听器观察值的变化,直到值停止变更时才停止。

watcher

last:监视对象的旧值,用于与现在的值进行比较来确定值是否改变。如果不相同,监听器就是dirty=true,它的监听函数就应当被调用。

返回值:

返回值是个函数,如果执行该函数,就会把刚注册的这个监听器销毁。

 

当监听到任何变化时,都会触发$digest循环:

163140_mSt1_347877.png 

$digest函数中会遍历所有监听器,并比对所有监听的值的变化。如果所有监听器的值都没变化,则dirty=false,不需要做任何更新;如果有任何监听器的值发生改变,则设置dirty=true,并修改对应值,$digest也会再次执行,直到所有的监听器值没有了改变。

注:JavaScript里,NaNNot-a-Number)并不等于自身所以在脏检测函数里不显式处理NaN因为一个值为NaN的监听器会一直是dirty

163140_mv38_347877.png 

那如何解决循环依赖呢?angular采用控制检测值变化的迭代次数:TTL,默认为10次。

163140_Mj6e_347877.png

如果超出迭代次数,值还不稳定,则抛出异常。

一般情况下开发者是不需要直接用$digestangular提供的接口是$apply

163140_DAtd_347877.png 

$apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发digest循环。而且$apply只有在创建一个不是angular库方法执行序列时才需要手动调用,因为angular的事件及异步请求等都会自动调用$apply

注:在使用$apply时,请将要执行的事情包裹在$apply里面(即$scope.$apply(fn)的方式),因为如果要执行的事情出现异常,angular将无法捕获异常而导致出错,而$scope.$apply()已做了异常处理,并保证$digest()一定会被执行。

angular中还有一种延迟代码的方式,详见scope上的$evalAsync函数,它接受一个函数,把它列入计划,在当前正持续的digest中或者下一次digest之前执行。还实现了$$postDigest函数将执行计划记录在$$postDigestQueue中,digest之后运行为了不影响到被列入计划将要执行的那个digestangularscope实现了一种叫做阶段(phase)的东西。

 

通过$watch循环检测,每次viewmodel改变时,该改变都能被捕捉到,通过$digest循环修改对应的modelview,直到稳定状态,从而实现双向数据绑定。

 

结语:

除了以上介绍的这些之外,angular还有很多东西值得我们去深入探究和使用,如路由(routing)、表单校验,还有单元测试和集成测试,有兴趣的朋友可以自己去探索一番。

最后再推荐一个学习资源集合:github上搜索AngularJS-Learning

随着angular的发展,angular2.0已经在开发中。Angular2.0是一个针对移动应用的框架,同时也支持桌面环境。Angular2.0将基于ECMAScript6编写,有更快的更新检测(使用Object.observe()),更强大的功能(触摸动画、路由等),让我们一起期待。


转载于:https://my.oschina.net/yunuo/blog/294180

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值