zoukankan      html  css  js  c++  java
  • angularjs执行流程

    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*([wd_]+);?)?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与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。

    到此,执行流程也就都出来了

  • 相关阅读:
    javaWeb css图文混排
    Junit 测试 @Test 红名问题
    java面试题:已知一个数组[2,4,6,2,1,5],将该数组进行排序(降序,不能用工具类进行排序),创建两条线程交替输出排序后的数组,线程名自定义
    【转】正则表达式的分组
    正则表达式基础
    IIS连接数修改
    【整理】fiddler不能监听 localhost和 127.0.0.1的问题
    【转】WKT、SRID、EPSG概念
    WPF中查找控件的扩展类
    【转】告别码农,成为真正的程序员
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3771304.html
Copyright © 2011-2022 走看看