1. 程式人生 > >angularjs-1.3代碼學習 模塊

angularjs-1.3代碼學習 模塊

一個 簡單回顧 loaded 無法 ring eof elf only 開始

花了點時間,閱讀了下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方法,達到內置註冊的服務得到執行。

時間不多,內容剛好,以上是個人閱讀源碼的一些理解,有不對或者偏差的地方,還希望園友們斧正。共同進步。

angularjs-1.3代碼學習 模塊