Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular源码解读setupModuleLoader函数 #25

Closed
Wscats opened this issue May 25, 2016 · 0 comments
Closed

Angular源码解读setupModuleLoader函数 #25

Wscats opened this issue May 25, 2016 · 0 comments
Labels

Comments

@Wscats
Copy link
Owner

Wscats commented May 25, 2016

setupModuleLoader其实看字面意思就可以知道它为模板加载器,就是为module设置加载器
function setupModuleLoader(window) {}
首先传入了window对象,作用为为后面ensure判断window全局对象是否含有属性angular

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

这里有个闭包函数,作用是判断对象中是否存在某个属性,没有的话创建对应的方法,并执行该方法
所以下面就会看到
ensure(ensure(window, "angular", Object), "module", function() {})
第一个ensure创建了为window对象创建angular
window.angular = Object()
第二个ensure创建了为angular对象创建module
到此步往下再看其实就变成了这样了
angular.module = function module(name,require,configFn);

  • name:字符串类型,代表模块的名称;
  • requires:字符串的数组,代表该模块依赖的其他模块列表,如果不依赖其他模块,用空数组即可;
  • configFn:用来对该模块进行一些配置。

这里就是我们常用到的模块加载部分
当我们angular.module('wscatApp'),只传一个参数,为getter操作,返回moduleInstance
当我们angular.module('wscatApp',[]) ,传了requires数组(空数组也行),为setter操作,也是返回对象moduleInstance
这两者有什么不同呢,可以看到module方法可以传递三个参数第二个参数require就是我们需要注入的依赖数组,其实就是注入所谓的DI

什么是DI呢,DI就是依赖注入
详情可以看这篇文章阐述
Javascript DI!Angular依赖注入的实现原理
截取里面几句话,方便这里的理解

  • 根据DI的原理,一个自然的推论就是:被注入的对象通常都是单例,因为创建了一个,就可以始终使用它了,不需要多次创建。
  • 在Angular中,所有主要编程元素都需要通过某种方式注册进去,比如myModule.service('serviceName', function()....这实际上就是把后面这个函数加入到一个容器中,要注意的是:angular全面实现了延迟初始化,也就是说,当这个对象没有被别人需要的时候,它是不会被创建的,这样对于提高性能有一定的帮助,特别是加快了启动速度。

这里的模块包括控制器、服务、过滤器、指令等子元素组成的整体
具体看源码这部分

moduleInstance = {
                            _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) {
                                return runBlocks.push(block), this
                            }
                        };

再往下看,里面有个闭包函数

var assertNotHasOwnProperty = function(name, context) {
                    if ("hasOwnProperty" === name) throw ngMinErr("badname", "hasOwnProperty is not a valid {0} name", context)
                };

这句也挺可爱的,module名称是不能以hasOwnProperty命名,否则会抛出”badname“的错误提醒。
下面继续执行
assertNotHasOwnProperty(name, "module"), requires && modules.hasOwnProperty(name) && (modules[name] = null), ensure(modules, name, function() {})
由于这里有多个异或操作,其实简单看起来就是进行了如下操作

assertNotHasOwnProperty(name, 'module');
      if (requires && modules.hasOwnProperty(name)) {
        modules[name] = null;
      }
      return ensure(modules, name, function() {})

这里很容易看出来,如果有传入requires数组(就算是空数组)还有modules对象里面有已定义好的对象或者属性,则会删除已有的module信息,将其置为null,并重新定义该属性

这里注意的是对比的modules对象,是在这个里面的
var modules = {};
它并不是全局的,而是只是在闭包**function(name, requires, configFn){}**局部作用域的。但由于是在闭包里面,所以它会一直保存在作用域中,相当于一直被缓存,不会被释放掉。
继续往下面看
if (!requires) throw $injectorMinErr("nomod", "Module '{0}' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.", name);
而requires如果为空,则表示是获取已有的模块
但如果requires不是空而是根本不存在,也就是说没有定义过这个module

看到上面这部分源码后启示我们在创建模块时,就算没有依赖其他模块,写法也应该是:
angular.module('wscatApp', []);
否则会到这里报错
angular.module('wscatApp',[...])和angular.module('wscatApp')
区别在于

  • angular.module('wscatApp',[...])会创建一个新的Angular模块,然后把方括号([...])中的依赖列表加载进来;
  • 而angular.module('wscatApp')会使用由第一个调用定义的现有的模块。

到这一步就是介绍这部分的最后一个闭包函数

function invokeLater(provider, method, insertMethod) {
                        return function() {
                            return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance
                        }
                    }

invokeLater从字面意思不难看出是之后再调用
理解这里我决定用一段简单的代码来阐述

var app = angular.module('wsscat', []);
        app.controller('serCtrl', function($scope, cat) {
            $scope.arr = cat.arr
        })

实际上我们还可以这样写

var app = angular.module('wsscat', []);
app.controller('serCtrl', ['$scope', 'cat', function($scope, cat) {
            //$scope.arr = cat.arr
            $scope.arr = "wsscat"
        }])

app.controller('serCtrl', function($scope, cat) {})
的controller运行了moduleInstance对象中的的invokeLater函数
controller: invokeLater("$controllerProvider", "register")
invokeLater传递三个参数

  • 第一个我们传入了$controllerProvider对应了provider
  • 第二个我们传入了register对应了method
  • 第三个我们传入了'serCtrl', function($scope, cat) {$scope.arr = cat.arr}对应了arguments

所以实际上变成了这样invokeQueue.push()

invokeQueue.push([$controllerProvider,register,['$scope', 'cat', function($scope, cat) {
            //$scope.arr = cat.arr
            $scope.arr = "wsscat"
        }]])

invokeQueue[insertMethod || "push"]([provider, method, arguments])
invokeQueue默认队列操作是push,如果有传入其他insertMethod
constant: invokeLater("$provide", "constant", "unshift")
则是unshift操作
animation: invokeLater("$animateProvider", "register")
则是register操作
由于return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance最后执行队列存放之后,会返回invokeQueue,所以后面可以进行链式操作,例如这样
app.controller().factory()

其他的模块和实例如下
举一反三,这里就不继续展开写了,引入AngularJS模块详解写的列表,如下

  • requires
  • 表示模块的依赖数组,即angular.module方法的requires参数。

angular模块实例属性和方法
属性
name
模块的名字。

  • _invokeQueue、_configBlocks、_runBlocks
  • 分别对应invokeQueue、configBlocks、runBlocks。

方法
模块的以下方法最后全部会返回模块实例本身,形成执行链。

  • animation()
  • 调用这个方法表示这个模块将在$animateProvider中注册一个动画服务。
  • 原理:在invokeQueue尾部插入['$animateProvider', 'register', arguments]。
  • config()
  • 调用这个方法,表示给这个模块追加一个配置方法。
  • 原理:在configBlocks尾部插入['$injector','invoke', arguments]。
  • constant()
  • 调用这个方法表示这个模块将给默认的$provider注册一个常量。
  • 原理:在invokeQueue首部插入['$provide', 'constant', arguments]。
  • controller()
  • 调用这个方法表示模块将在$controllerProvider中注册一个控制器。
  • 原理:在invokeQueue尾部插入['$controllerProvider', 'register', arguments]。
  • directive()
  • 调用这个方法表示这个模块将在$compileProvider中注册一个指令。
  • 原理:在invokeQueue尾部插入['$compileProvider', 'directive', arguments]。
  • factory()
  • 调用这个方法表示这个模块中将生成一个服务工厂(隐式创建一个了服务提供商)。
  • 原理:在invokeQueue尾部插入['$provide', 'factory', arguments]。
  • filter()
  • 调用这个方法表示这个模块将在$filterProvider中注册一个过滤器。
  • 原理:在invokeQueue尾部插入['$filterProvider', 'register', arguments]。
  • provider()
  • 调用这个方法表示这个模块将添加一个服务提供商。
  • 原理:在invokeQueue尾部插入['$provide', 'provider', arguments]。
  • run(block)
  • 调用这个方法表示这个模块将执行某个功能块,block可以是方法,也可以是数组。
  • 原理:在invokeQueue尾部插入block。
  • service()
  • 调用这个方法表示这个模块将注册一个服务(隐式创建了一个服务提供商)。
  • 原理:在invokeQueue尾部插入['$provide', 'service', arguments]。
  • value()
  • 调用这个方法表示这个模块将注册一个变量(隐式创建了一个服务提供商)。

看最后一句
return configFn && config(configFn), moduleInstance
这句转化成这样容易点理解

if (configFn) {
          config(configFn);
        }

也就是如果声明module时存在第三个参数,那么就把第三个参数的设置放进invokeQueue队列中。这样,就可以在载入一个module的时候同时做一些其他的事情。
config = invokeLater("$injector", "invoke")
config(configFn)
invokeLater("$injector", "invoke")(configFn)

@Wscats Wscats changed the title Angular源码解读的setupModuleLoader函数 Angular源码解读setupModuleLoader函数 May 26, 2016
@Wscats Wscats added the notes label Jun 3, 2016
@Wscats Wscats closed this as completed Aug 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant