说明
Angularjs在初始化的时候做了什么
尝试绑定jQuery对象,如果没有则采用内置的jqLite(30561行):
1 | //try to bind to jquery now so that one can write jqLite(document).ready() |
publishExternalAPI
初始化angular环境,这也是angularjs实现其重要特性–扩展指令、依赖注入的重要步骤:
- 把一些基础api挂载到angular上(2455行):
1 | extend(angular, { |
- 在angular上添加了module方法(2488行):
- 在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性;
setupModuleLoader
返回的内容中包含了provider
、factory
、service
、value
、constant
、controller
、directive
等诸多对象,方便用angular.module链式调用这些对象;
1 | angularModule = setupModuleLoader(window); |
1 | //链式调用 |
当dom ready时,开始执行程序的初始化
- 执行初始化的代码(30708行):
1 | jqLite(document).ready(function() { |
- angularInit函数(1591行):
- 获取
ng-app
的应用根节点(含有xx-app
属性的dom); - 获取启动模块(
xx-app
的值xx为 [‘ng-‘, ‘data-ng-‘, ‘ng:’, ‘x-ng-‘] 任一一种; - 将上述结果传给bootstrap函数:
bootstrap(appElement, module ? [module] : [], config);
- 获取
1 | // The element `element` has priority over any other element |
- boostrap函数(1679行):
1 | function bootstrap(element, modules, config) { |
- 除了系统会自动调用bootsrap函数,也可以自己手动调用:
1 | angular.module('demo', []) |
$get
的分析
$get
见源码16163行;- 构造函数如下:
1 | function Scope() { |
含义:
- $id, 通过nextUid方法来生成一个唯一的标识
- $$phase, 这是一个状态标识,一般在dirty check时用到,表明现在在哪个阶段
- $parent, 代表自己的上级scope属性
- $$watchers, 保存scope变量当前所有的监控数据,是一个数组
- $$nextSibling, 下一个兄弟scope属性
- $$prevSibling, 前一个兄弟scope属性
- $$childHead, 第一个子级scope属性
- $$childTail, 最后一个子级scope属性
- $$destroyed, 表示是否被销毁
- $$asyncQueue, 代表异步操作的数组
- $$postDigestQueue, 代表一个在dirty check之后执行的数组
- $$listeners, 代表scope变量当前所有的监听数据,是一个数组
创建子级作用域是通过
$new
方法:
1 | $new: function(isolate, parent) { |
- 分析:
- isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的;
- 如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例;
- 通用的操作就是,设置当前作用域的
$$childTail
,$$childTail.$$nextSibling
,$$childHead
,this.$$childTail
为生成的子级作用域;设置子级域的$parent
为当前作用域,$$prevSibling
为当前作用域最后一个子级作用域;
$watch
的分析
$watch
在16452行,具体如下:
1 | $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { |
对象含义:
- fn, 代表监听函数,当监控表达式新旧不相等时会执行此函数
- last, 保存最后一次发生变化的监控表达式的值
- get, 保存一个监控表达式对应的函数,目的是用来获取表达式的值然后用来进行新旧对比的
- exp, 保存一个原始的监控表达式
- eq, 保存$watch函数的第三个参数,表示是否进行深度比较
检查传递进来的监听参数是否为函数,如果是一个有效的字符串,则通过parse来解析生成一个函数,否则赋值为一个noop占位函数,最后生成一个包装函数,函数体的内容就是执行刚才生成的监听函数,默认传递当前作用域.
- 检查监控表达式是否为字符串并且执行表达式的constant为true,代表这个字符串是一个常量,那么,系统在处理这种监听的时候,执行完一次监听函数之后就会删除这个
$watch
.最后往当前作用域里的$$watchers
数组头中添加$watch
信息,注意这里的返回值,利用JS的闭包保留了当前的watcher,然后返回一个函数,这个就是用来删除监听用的.
$digest
分析
- 见源码16812行;
- digest方法是dirty check的核心,主要思路是先执行
$$asyncQueue
队列中的表达式,然后开启一个loop来的执行所有的watch里的监听函数,前提是前后两次的值是否不相等,假如ttl超过系统默认值,则dirth check结束,最后执行$$postDigestQueue
队列里的表达式.
1 | $digest: function() { |
- 核心就是两个loop,外loop保证所有的model都能检测到,内loop则是真实的检测每个watch,watch.get就是计算监控表达式的值,这个用来跟旧值进行对比,假如不相等,则执行监听函数;
- 比较完之后,把新值传给watch.last,然后执行watch.fn也就是监听函数,传递三个参数,分别是:最新计算的值,上次计算的值(假如是第一次的话,则传递新值),最后一个参数是当前作用域实例,这里有一个设置外loop的条件值,那就是dirty = true,也就是说只要内loop执行了一次watch,则外loop还要接着执行,这是为了保证所有的model都能监测一次,虽然这个有点浪费性能,不过超过ttl设置的值后,dirty check会强制关闭,并抛出异常;
- 当检查完一个作用域内的所有watch之后,则开始深度遍历当前作用域的子级或者父级,虽然这有些影响性能,就像这里的注释写的那样yes,
this code is a bit crazy
$apply
分析
- 见源码17121行;
1 | $apply: function(expr) { |
- 这个方法一般用在,不在ng的上下文中执行js代码的情况,比如原生的DOM事件中执行想改变ng中某些model的值,这个时候就要使用
$apply
方法了; - 代码中,首先让当前阶段标识为
$apply
,这个可以防止使用$apply
方法时检查是否已经在这个阶段了,然后就是执行$eval
方法, 这个方法上面有讲到,最后执行$digest
方法,来使ng中的M或者VM改变.