angularjs-1.3代码学习 模块

花了点时间,阅读了下angularjs的源码。本次先从模块化开始。

angular可以通过module的api来实现前端代码的模块化管理。跟define类似。但不具备异步加载脚本的功能。先从最基本的module开始。查看代码发现angular对module的定义是在setupModuleLoader方法中定义的

function setupModuleLoader(window) {

  var $injectorMinErr = minErr(‘$injector‘);
  var ngMinErr = minErr(‘ng‘);

  function ensure(obj, name, factory) {
    return obj[name] || (obj[name] = factory());
  }

  var angular = ensure(window, ‘angular‘, Object);

  // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
  angular.$$minErr = angular.$$minErr || minErr;

  return ensure(angular, ‘module‘, function() {

    return function module(name, requires, configFn) {
      //省略.....
      return ensure(modules, name, function() {
     //省略...

        return moduleInstance;
      });
    };
  });

}

可以看到源码中是通过ensure函数,将module定义到angular,并且在函数内返回函数,这就使用到了闭包,angular的目的是为了存储对应的module信息。在整个angular模块系统中,源码通过modules来存储所有用户定义的模块。当用户对自己定义的模块注册服务时(factory,service,provider,value时),这些服务会被存在各自模块内部的invokeQueue数组中维护。这同样是一个闭包。

当setupModuleLoader方法执行结束之后,返回的值是一个moduleInstance。这也是我们在外部调用angular.module时返回的值,看一下内部结构:

var moduleInstance = {
          // Private state
          _invokeQueue: invokeQueue,
          _configBlocks: configBlocks,
          _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;
          }
        };

我们可以使用_invokeQueue属性查看,当前module下面注册了哪些服务。另外moduleInstance也向外部公开了诸如provider,factory.service这些接口,供用户去注册自定义服务。至于如何注册的,是通过其内部的invokeLater方法,统统加入到invokeQueue数组中

function invokeLater(provider, method, insertMethod, queue) {
          if (!queue) queue = invokeQueue;
          return function() {
            queue[insertMethod || ‘push‘]([provider, method, arguments]);
            return moduleInstance;
          };
        }

目前,我们大致了解了模块的定义和一般服务的注册。大致调用代码如下:

var app = angular.module("A", []); //定义一个模块A
app.service("A1", function(){
            console.log(1);
});  //在模块A上定义一个服务A1

ok,万事俱备,只欠业炎。我们如何使用它?angular提供了一个injector的方法,我们来看一下。从内部函数publishExternalAPI中看到injector的定义,它来源于angular的一个叫做createInInjector的函数。

function createInjector(modulesToLoad, strictDi) {
  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = ‘Provider‘,
      path = [],
      loadedModules = new HashMap([], true),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      };
    var  providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) {
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr(‘unpr‘, "Unknown provider: {0}", path.join(‘ <- ‘));
          }))
    var instanceCache = {};
    var instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
          }));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });

  return instanceInjector;
 // 省略.....
}

可以看出,内部定义了两个核心对象providerCache和instanceCache。它们都是由createInternalInjector函数生成。那进入createInternalInjector函数里看一下

function createInternalInjector(cache, factory) {
    //....
return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {
          //对象本身是否有该属性
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
      }
    };
}

因为createInjector函数中最终返回的是instanceInjector,而此君指向的则是instanceCache的$injector属性,而这个属性则是createInternalInjector函数的返回值。换言之,用户可以在外部调用这些方法

var injector = angular.injector(["A"]);
//其中injector就是createInternalInjector函数的返回的对象

在我们调用这些接口之前,需要告诉angular去执行哪个模块里的服务,让我们回到createInjector函数中,看一下函数最后的for循环

forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });

可以猜想这个loadModules函数的作用应该是获取模块。简单看一下loadModules

function loadModules(modulesToLoad) {
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function(module) {
      if (loadedModules.get(module)) return; //先尝试去loadedModules查找
      loadedModules.put(module, true);

      function runInvokeQueue(queue) {
        var i, ii;
        for (i = 0, ii = queue.length; i < ii; i++) {
            //获取用户之前需要注册的服务
          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      try {
        if (isString(module)) {
          moduleFn = angularModule(module); //angularModule其实就是外部用户调用的module接口,获取到对应模块
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);//如果模块有依赖则事先获取依赖模块
          runInvokeQueue(moduleFn._invokeQueue);//之前所有的声明的服务都被存在放这个模块的invokeQueue数组中
          runInvokeQueue(moduleFn._configBlocks);
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, ‘module‘);
        }
      } catch (e) {
        if (isArray(module)) {
          module = module[module.length - 1];
        }
        if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
          // Safari & FF‘s stack traces don‘t contain error.message content
          // unlike those of Chrome and IE
          // So if stack doesn‘t contain message, we create a new string that contains both.
          // Since error.stack is read-only in Safari, I‘m overriding e and not e.stack here.
          /* jshint -W022 */
          e = e.message + ‘\n‘ + e.stack;
        }
        throw $injectorMinErr(‘modulerr‘, "Failed to instantiate module {0} due to:\n{1}",
                  module, e.stack || e.message || e);
      }
    });
    return runBlocks;
  }

在runInvokeQueue中,我们看到源码获取了之前用户存在放invokeQueue中的服务对象,并且通过get方法及getService方法去获取服务。这里有个细节地方,源码是通过providerInjector的get方法来获取的,那我们看getService源码的时候,其中的cache则应该是在之前createInjector函数中的providerCache。

function getService(serviceName, caller) {
      if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) {
          throw $injectorMinErr(‘cdep‘, ‘Circular dependency found: {0}‘,
                    serviceName + ‘ <- ‘ + path.join(‘ <- ‘));
        }
        return cache[serviceName];
      } else {
        try {
          path.unshift(serviceName);
          cache[serviceName] = INSTANTIATING;
          return cache[serviceName] = factory(serviceName, caller);
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }

基本还是先尝试在cache中找一次,没有则创建并保存在cache中。因为之前providerInjector中就已经有了$provider何$inject这样的方法。所以在

var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);

中,provider就是:

providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      };

然后,再有后面的一步provider[invokeArgs[1].apply(provider, invokeArgs[2])]。看到这里我们突然恍然大悟,开始angular为什么要使用

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‘),

这类写法注册的原因了,也就是现在我们才真正调用具体service,factory或者是value等api去执行或者讲要初始化我们自定义的服务。这里我们顺便看一下factory,service和provider的内部实现,这些API以后我们会经常用到:

function provider(name, provider_) {
    assertNotHasOwnProperty(name, ‘service‘);
    if (isFunction(provider_) || isArray(provider_)) {
      provider_ = providerInjector.instantiate(provider_); //实例化
    }
    if (!provider_.$get) {
      throw $injectorMinErr(‘pget‘, "Provider ‘{0}‘ must define $get factory method.", name);
    }
    return providerCache[name + providerSuffix] = provider_; //并存储在本地缓存中
  }

function factory(name, factoryFn, enforce) {
    return provider(name, { //底层调用的是provider
      $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
    });
  } 

  function service(name, constructor) {
    return factory(name, [‘$injector‘, function($injector) { //底层调用factory
      return $injector.instantiate(constructor);
    }]);
  }

  function value(name, val) { return factory(name, valueFn(val), false); } //底层调用factory

factory,service和value基本底层都是使用的provider方法。可以看到provider方法需要$get的这种键值对的方式将函数传入。所以我们在外部使用这一系列API的方式,大致

var app = angular.module("A", []);
        app.service("A1", function(){
            console.log(1);
        });
       app.factory("B1", [function(){
           console.log(2);
           return {};
       }]);
       app.provider("C1", {
           $get: function(){
               console.log(3);
           }
       });
       app.value("D1", 4);
       app.constant("E1", 5);

有兴趣的朋友可以试下,service使用factory的方法定义可以不可以^_^。至此,我们之前使用injector()方法实际上获取到了instanceInjector。目前我们还是没有触发或者执行我们定义的服务,实际上instanceInjector上提供了如下几个方法:

return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {
          //对象本身是否有该属性
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
      }
    };

当我们执行get方法的时候,我们注册的服务的以执行!

function getService(serviceName, caller) {
      if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) {
          throw $injectorMinErr(‘cdep‘, ‘Circular dependency found: {0}‘,
                    serviceName + ‘ <- ‘ + path.join(‘ <- ‘));
        }
        return cache[serviceName];
      } else {
        try {
          path.unshift(serviceName);
          cache[serviceName] = INSTANTIATING;
          return cache[serviceName] = factory(serviceName, caller);
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }

这里你可能会奇怪这个factory到底是哪个?是来自于providerInjector的还是来自instanceInjector的?实际上factory是由instanceInjector中定义的回调函数。为什么呢?因为上文我们说了外部调用injector的时候angular内部返回的是instanceInjector。所以在instanceInjector上调用get时,这个factory应该就是instanceInjector上注册的回调函数,来看一下这个回调:

function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
          }

在instanceInjector的回调里面调用providerInjector的get,其实这个很清楚,之前angular将所有服务存储都存放在了providerCache里面,而外部对用户而言,他无法直接修改providerCache里面的东西。重复的get我们就不看,来看一下invoke方法:

function invoke(fn, self, locals, serviceName) {
      if (typeof locals === ‘string‘) {
        serviceName = locals;
        locals = null;
      }

      var args = [],
          $inject = createInjector.$$annotate(fn, strictDi, serviceName),
          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, serviceName)
        );
      }
      if (isArray(fn)) {
        fn = fn[length];
      }

      // http://jsperf.com/angularjs-invoke-apply-vs-switch
      // #5388
      return fn.apply(self, args); //执行服务
    }

千呼万唤始出来啊。服务的执行在invoke方法得到执行,那么我们算是基本的将module和服务注册和执行的流程简单的过了一遍。

简单回顾一下,angular的module实际上就是用了两层闭包来管理对象,没有动态异步加载模块,这个功能有点类似namespace。比较鸡肋。另外返回的moduleInstance中有factory,service,provider,value等一些可以注册服务的接口,其内部使用了providerInjector何instanceInjector来存储相关服务。通过invoke来执行相应模块里的相应服务。目前我们还没有看angular的bootstrap部分,其内部也是调用invoke方法,达到内置注册的服务得到执行。

时间不多,内容刚好,以上是个人阅读源码的一些理解,有不对或者偏差的地方,还希望园友们斧正。共同进步。

				
时间: 2024-11-05 11:35:10

angularjs-1.3代码学习 模块的相关文章

一个女大学生的代码学习之路(二)

首先说一下,写这种文章是由于我在四月四日晚上,在手动搭建自己的第一个ssh项目的时候,遇到了一个配置的问题,怎么解决也弄不好,当时是四号晚上九点,我看了一眼表,我就想两个小时之内,我要是能搞定就算行了,但是其实,我搞到三点才OK(凌晨),那时候已经是五号了,转天是一家子去扫墓的时候,结果我居然以这种一个理由没有去,理由是我太累了么?我只是就是搭了一个架子,就是由于我的包太混乱了,导致不兼容,所以tomcat总也不启动,你可能认为好笑,这么简单一个问题怎么就费这多多时间呢,但是作为一个刚接触三框架

AngularJS进阶(四十)创建模块、服务

AngularJS进阶(四十)创建模块.服务 学习要点 使用模块构架应用 创建和使用服务 为什么要使用和创建服务与模块? 服务允许你打包可重用的功能,使之能在此应用中使用. 模块允许你打包可重用的功能,使之能跨应用使用. 一.应用程序模块化 先看看一个没有模块化的程序 <!DOCTYPE> <!-- use module --> <html ng-app="exampleApp"> <head> <title>Angluar

极客标签:可能是目前最好的前端代码学习工具

英国著名作家萧伯纳有一句名言:"两个人交换了苹果,每个人手里还是只有一个苹果:但是两个人交换了思想,每个人就同时有了两个人的思想."这说的是知识的分享对于人类进步的重要意义.时间到了现代,技术进步带给人们更多样化的沟通方式.可是,当人们交换各种类型的知识的时候,却发现依然没那么容易. 绝大多数有学问的人都选择了出书:网络的普及也让各种各样的博客出现,之后则是微信公众号的天下:也有人选择了言传身教的现代版--录制讲学视频. 但以上的各种方式都不适合编程学习,特别是web设计方面的学习.你

导航悬浮于顶部代码学习

导航悬浮于顶部代码学习,由于博客园视频限制,暂时不能全屏观看,如果想全屏观看高清视频,可以点击链接查看:http://www.zymseo.com/20.html jQuery页面滚动顶部悬浮导航是一款带二级下拉菜单的jQuery悬浮导航,导航可跟随页面滚动一直出现在顶部,很实用. 当你使用此效果装饰网页时,出现滚动条无法下拉的情况,是因为悬浮元素加入position:fixed样式之后,已经脱离布局,页面的高度不会把悬浮元素的高度计算在内.比如当前例子,浏览器的可视区域的高度为644px,内容

Android代码学习--点击事件的几种写法

由来:常规的写法参见<写一个apk>,每次点击按钮,按钮先查找文本框等元素,然后再操作,其实查找操作是很费时的操作,因此将该定义放到Activity的onCreate中:Oncreate只会执行一次,这样Activity一旦执行,就先创建好控件们了. 第二种方法:通过匿名内部类的方法:就是button.setOnClickListener(new OnClickListener(){ //实现OnClickListener接口 @Override public void onClick(Vie

公益图书馆-Contribute捐赠-代码学习

1.ContributeController.class.php 控制器 <?php /** * Created by PhpStorm. * User: lxd * Date: 14-7-27 * Time: 下午2:43 */ namespace Home\Controller; class ContributeController extends SyController{ /**@author lxd * @description 首页 */ public function index(

python学习-模块

模块基本知识 模块是实现某个功能的代码集合 模块分为三种: 内置模块:安装 python 时自带的模块 自定义模块:之前我们所写的所有函数也都可以被当做自定义模块 第三方模块:非安装 python 时自带的模块 1.模块的导入 Python之所以应用越来越广泛,在一定程度上也依赖于其为程序员提供了大量的模块以供使用,如果想要使用模块,则需要导入 导入模块的方法: import module #导入模块的所有内容 from module.xx import xx # 导入模块的某一个功能 from

Python 官方代码threading模块的一个死锁的bug

Python的threading模块有一个比较严重的bug:那就是可能会让线程的等待提前结束或者延迟,具体的原因是因为线程的wait操作判断超时时依赖于实时时间,即通过time.time()获取到的时候,为了显示这个问题,请看下面的例子: from threading import Thread from threading import Event import time e = Event() stop = False class MyThread(Thread): def __init__

TP框架代码学习 学习记录 3.2.3

文件:think.class.php PHP提供register_shutdown_function()这个函数,能够在脚本终止前回调注册的函数,也就是当 PHP 程序执行完成后执行的函数.register_shutdown_function 执行机制是:PHP把要调用的函数调入内存.当页面所有PHP语句都执行完成时,再调用此 函数.注意,在这个时候从内存中调用,不是从PHP页面中调用,所以上面的例子不能使用相对路径,因为PHP已经当原来的页面不存在了.就没有什么相对路 径可言.注意:regis