先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bower install angularjs安装的。
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*([\w\d_]+);?)?\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]);
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; /** ...省略若干 **/主要是四个变量:
providerCacheproviderInjectorinstanceCacheinstancheInjectorproviderCache初始化只有一个对象 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 }两个重要方法:
loadModulescreateInternalInjector 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与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。
到此,执行流程也就都出来了。
收藏自angularjs源码分析之:angularjs执行流程