• 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 + '
    ' + e.stack;
            }
            throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:
    {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方法,达到内置注册的服务得到执行。

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

    
    
  • 相关阅读:
    Python GUI编程(Tkinter)——Label and Button
    第一章——正则表达式(特殊符号和字符)
    最近用到的Blender相关技术总结
    第一章—正则表达式(简介/动机)
    发个随笔纪念Manim配置成功
    JUC并发工具的使用和原理
    线程启动与终止原理
    cpu缓存一致性
    布隆过滤器
    对象并不一定都是在堆上分配内存(逃逸分析)
  • 原文地址:https://www.cnblogs.com/wumadi/p/6616778.html
Copyright © 2020-2023  润新知