angularjs源码分析之:angularjs执行流程
angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题。其中涉及到很多概念,比如:directive,controller,service,compile,link,scope,isolate scope,双向绑定,mvvm等。最近准备把这些都慢慢搞懂,分析源码并贴到博客园,如有分析不对的地方,还望各位包容并指正。
angularjs源码分析之:angularjs执行流程
先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bower install angularjs安装的。

几个重要方法
  bindJQuery();
  publishExternalAPI(angular);
  jqLite(document).ready(function() {
    angularInit(document, bootstrap);
  });
20121行,bindJQuery,尝试绑定jQuery对象,如果没有则采用内置的jqLite
20123行,publishExternalAPI,初始化angular环境。
1820-1848行,把一些基础api挂载到angular上,如:extend,forEach,isFunction等。
1850行,angularModule = setupModuleLoader(window); 此方法为模块加载器,在angular上添加了module方法,最后返回的实质上是:
angular.module = function module(name,require,configFn);
当我们angular.module('myApp'),只传一个参数,为getter操作,返回moduleInstance
当我们angular.module('myApp',[]) 时返回对象moduleInstance
var moduleInstance = {
          // Private state
          _invokeQueue: invokeQueue,
          _runBlocks: runBlocks,
          requires: requires,
          name: name,
          provider: invokeLater('$provide', 'provider'),
          factory: invokeLater('$provide', 'factory'),
          service: invokeLater('$provide', 'service'),
          value: invokeLater('$provide', 'value'),
          constant: invokeLater('$provide', 'constant', 'unshift'),
          animation: invokeLater('$animateProvider', 'register'),
          filter: invokeLater('$filterProvider', 'register'),
          controller: invokeLater('$controllerProvider', 'register'),
          directive: invokeLater('$compileProvider', 'directive'),
          config: config,
          run: function(block) {
            runBlocks.push(block);
            return this;
          }
}
因为这样,我们才可以链式操作 ,如: .factory().controller().
这里需要重点提到两个函数:ensure 和 invokeLater。
通过ensure(obj,nam,factory)可以实现:先检索obj.name,如果有就是getter操作,否则为setter操作。
此机制完成了在windows对象上声明angular属性,在angular对象上声明module属性。
通过invokeLater可以返回一个闭包,当调用config,provider(即moduleInstance返回的那些api)调用时,实质上就是调用这个闭包,拿
app.provider('myprovider',['$window',function($window){ //code}]) 举例,此api调用后,会将:
('$provide','provider',['$window',function($window){}]) push进invokeQueue数组中,注意此处只是入队操作,并未执行。在后面执行时,实际上市通过 第一个参数调用第二个参数方法名,把第三个参数当变量传入,即:args[0][args[1]].apply(args[0],args[2]);具体后面会分析到。
注意:上面提到api,run比较特殊,只把block放入到runBlock中,并没有放入invokeQueue中。
20125,domready后调用angularInit
function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
  function append(element) {
    element && elements.push(element);
  }
  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });
  forEach(elements, function(element) {
    if (!appElement) {
      var className = ' ' + element.className + ' ';
      var match = NG_APP_CLASS_REGEXP.exec(className);
      if (match) {
        appElement = element;
        module = (match[2] || '').replace(/\s+/g, ',');
      } else {
        forEach(element.attributes, function(attr) {
          if (!appElement && names[attr.name]) {
            appElement = element;
            module = attr.value;
          }
        });
      }
    }
  });
  if (appElement) {
    bootstrap(appElement, module ? [module] : []);
  }
}
遍历names,通过document.getElementById(name) 或者是 querySelectorAll(name)检索到 element后存入 elements数组中,最后获取到appElement以及module。举个例子:我们一般会在文档开始的html标签上写 ng-app="myApp".通过以上方法,我们最后可以得到 名为myApp的module,后调用bootstrap(appElement,[module]);
bootstrap中需要重点关注 doBootstrap方法
var doBootstrap = function() {
    element = jqLite(element);
    if (element.injector()) {
      var tag = (element[0] === document) ? 'document' : startingTag(element);
      throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
    }
    //通过上面分析我们知道此时 modules 暂时是这样的: modules = ['myApp'];
    modules = modules || [];
    //添加$provide这个数组
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);
    //添加 ng这个 module ,注意:1857行  我们注册过ng 这个module,并在1854行 我们注册过 它的依赖模块'ngLocale',
    //angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 我们注册过ngLocale这个module
    modules.unshift('ng');
    //调用createInjector(module) 此时:module为:
    //['ng',['$provide',function(){}],'myApp']  两个type为string,一个为array
    var injector = createInjector(modules);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
       function(scope, element, compile, injector, animate) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    return injector;
  };
createInjector是重点,拿出来单独分析
function createInjector(modulesToLoad) {
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap(),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function() {
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},
      instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(servicename) {
            var provider = providerInjector.get(servicename + providerSuffix);
            return instanceInjector.invoke(provider.$get, provider);
          }));
  forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
  return instanceInjector;
  /**
  ...省略若干
  **/
* 主要是四个变量:
providerCache,providerInjector,instanceCache,instancheInjector
providerCache初始化只有一个对象 providerCache = { $provide:{}} ,紧接着调用createInternalInjector 方法返回一个若干重两级api(annotate,get等)赋值给providerCache.$injector 以及provoderInjector.则结果就变成这样了:
providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      $injector:{
          get:getService,
          annotate:annotate,
          instantiate:instantiate,
          invoke:invoke,
          has:has
      }
}
而providerInjector变成了这样:
providerInjector:{
      nvoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}
同样,instanceCache和instanceInjector变成:
instanceCache:{
      $injector:{
          invoke: invoke,
          instantiate: instantiate,
          get: getService,
          annotate: annotate,
          has: has
      }
}
instanceInjector = {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}
* 两个重要方法:
loadModules,createInternalInjector
function loadModules(modulesToLoad){
    //刚才说了,modulesToLoad长这样:['ng',['$provider',function(){}], 'myApp']
    forEach(modulesToLoad, function(module) {
        if (isString(module)) {
             // module为字符串时进入此判断。
       moduleFn = angularModule(module);
            //迭代,把所有依赖模块的runBlocks都取出
            runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
            //前面已经提到,每个module下有个_invokeQueue存了一堆controller,service之类东西,现在就可以遍历拿出来运行啦
            for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
                //还记得我刚才说了具体怎么调用的把:第一个参数调用名为第二个参数的方法,传入第三个参数
                var invokeArgs = invokeQueue[i],
                    provider = providerInjector.get(invokeArgs[0]);
                provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
            }
        }else if(isFunction(module)){
            //这个我还没找到…………
        }else if(isArray(module)){
             //这里就是第二参数的情形了,用invoke方法执行后将结果存到runBlocks
            runBlocks.push(providerInjector.invoke(module));
        }
    }
    return runBlocks;
}
重量级函数createInternalInjector 闪亮登场,正是因为有这个函数,才能实现那么优雅的DI。
function createInternalInjector (){
    function getService(serviceName) {};
    function invoke(fn, self, locals){};
    function instantiate(Type, locals) {};
    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: function(name) {
        //
      }
    };
}
这么重要的函数实际上是一个工厂,最后返回五个方法。下面一一分析:
* annotate,只获取依赖注入列表
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;
  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
          arg.replace(FN_ARG, function(all, underscore, name){
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}
传参有两种形式,1.annotate(fn(injectName)) 以及 2.annotate([injectName,function(){}]).分别进入代码中的两个判断,如果是只传了fn,且fn有参数,则利用
fn.toString返回函数体本身,再通过正则取出injectName数组添加到fn.$inject上。 如果通过第二方式调用,取出所有$inject数组
* invoke,通过annoate取出依赖注入,将依赖注入为参数调用函数体。如:xx.invoke(function($window){});
function invoke(fn, self, locals){
      var args = [],
          $inject = annotate(fn),
          length, i,
          key;
      for(i = 0, length = $inject.length; i < length; i++) {
        key = $inject[i];
        if (typeof key !== 'string') {
          throw $injectorMinErr('itkn',
                  'Incorrect injection token! Expected service name as string, got {0}', key);
        }
        args.push(
          locals && locals.hasOwnProperty(key)
          ? locals[key]
          : getService(key)
        );
    //省略若干
    //switch做了优化处理,罗列了一些常见的参数个数
   switch (self ? -1 : args.length) {
        case  0: return fn();
        //省略若干
}
* instantiate 此函数比较特殊,也比较有用,但一般不直接用,隐藏较深。
我们考虑这样一种情况,一般较创建的写法。
app.provider('myProvider',function(){ 
    //do something
    this.$get = function(){
        return obj;
    }
});
假如我们想要在angular中用一些设计模式,我们换一种写法:
app.provider('myProvider',myProvider);
function myProvider(){
   BaseClass.apply(this,arguments);
}
unit.inherits(BaseClass,myProvider);
extend(myProvider,{
   $get:function(){
        return something
   }
});
这样也可以实现我们需要的,而且可扩展。但如果我们没有了解过instantiate这个方法,你看到此写法会觉得疑惑不解。
instantiate(Type,locals)方法创建了一个空的构造函数,并继承自传入的Type,然后实例化得到instance,最后在调用invoke函数。
万事俱备,只欠东风
做了这么多准备,各种provider也有了,我们需要在页面上展示效果了
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
通过$apply将作用域转入angular作用域,所谓angular作用域是指:angular采用dirity-check方式进行检测,达到双向绑定。
再利用compile函数编译整个页面文档,识别出directive,按照优先级排序,执行他们的compilie函数,最后返回link function的结合。通过scope与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。
到此,执行流程也就都出来了。
angularjs源码分析之:angularjs执行流程的更多相关文章
- Yii2 源码分析  入口文件执行流程
		Yii2 源码分析 入口文件执行流程 1. 入口文件:web/index.php,第12行.(new yii\web\Application($config)->run()) 入口文件主要做4 ... 
- asyncio源码分析之基本执行流程
		基于async关键字的原生协程 # 定义一个简单的原生协程cor async def cor(): print('enter cor') print('exit cor') print(type(co ... 
- (转)linux内存源码分析 - 内存回收(整体流程)
		http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ... 
- JVM源码分析之JVM启动流程
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十四篇. 今天呢!灯塔君跟大家讲: JVM源码分析之JVM启动流程 前言: 执行Java类的main方法,程序就能运 ... 
- Solr4.8.0源码分析(5)之查询流程分析总述
		Solr4.8.0源码分析(5)之查询流程分析总述 前面已经写到,solr查询是通过http发送命令,solr servlet接受并进行处理.所以solr的查询流程从SolrDispatchsFilt ... 
- HDFS源码分析DataXceiver之整体流程
		在<HDFS源码分析之DataXceiverServer>一文中,我们了解到在DataNode中,有一个后台工作的线程DataXceiverServer.它被用于接收来自客户端或其他数据节 ... 
- AngularJS 源码分析1
		AngularJS简介 angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里 再贴上一个 ... 
- MyBatis源码分析-SQL语句执行的完整流程
		MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ... 
- Tomcat源码分析之—具体启动流程分析
		从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ... 
随机推荐
- Windowsphone本地应用信息与市场信息的获取
			本地信息都存放在 WMAppManifest 里面,获取就不用说了...知道位置 就知道怎么获取了.. 主要是讲那个 市场上面的详情怎么获取,就是API调用显示在这个页面里面的详情: public v ... 
- Jetson TK1 Restore 步骤
			 Jetson TK1 Restore 步骤 下载驱动包和文件系统包: 1:驱动包 2:文件系统 具体参见文档:http://download.csdn.net/detail/xiabodan/7 ... 
- 基于opencv网络摄像头在ubuntu下的视频获取
			 基于opencv网络摄像头在ubuntu下的视频获取 1 工具 原料 平台 :UBUNTU12.04 安装库 Opencv-2.3 2 安装编译运行步骤 安装编译opencv-2.3 参 ... 
- mvc路由设置参数配置类似于url重写
			1.新建的mvc项目中Global.asax 2.在另外一个控制器中的视图中 3. 4. 
- Zookeeper 脑裂
			转自 http://blog.csdn.net/u010185262/article/details/49910301 Zookeeper zookeeper是一个分布式应用程序的协调服务.它是一个为 ... 
- [css filter]filter在界面实现滤镜效果
			最近逛当当,发现当当尾品会的首页推荐最底端的商品链接是灰色的图片,然后鼠标hover之后就会变成正常的彩色 肯定不是通过img来改变的,然后直接看了一下源码,其实是用的filter属性 _(:з」∠) ... 
- C#判断ip地址是否ping的通
			Ping pingSender = new Ping(); PingReply reply = pingSender.Send("127.0.0.1",120);//第一个参数为i ... 
- Swing基础
			Swing基础 JFrame JPanel 绘图:paint 监听事件: ActionListener KeyListener Listener和Adapter 计时器:Timer Time ... 
- Linux下使用Magent+Memcached缓存服务器集群部署
			1.编译安装libevent cd /root/soft_hhf/ wget http://cloud.github.com/downloads/libevent/libevent/libeven ... 
- Redbean:入门(二) - Find
			<?php require_once 'rb.php'; $tableName = 'link'; //连接数据库 R::setup('mysql:host=localhost;dbname=h ... 
