AngularJS 源码分析1

AngularJS简介

angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里

再贴上一个本文源码分析对应的angularjs源码合并版本1.2.4,精简版的,除掉了所有的注释, 请戳这里

从启动开始说起

定位到4939行,这里是angularjs开始执行初始化的地方,见代码

1
2
3
bindJQuery(), publishExternalAPI(angular), jqLite(document).ready(function() {
        angularInit(document, bootstrap)
    })

bindJQuery方法是检查是否引用jquery,没有的话jqlite就用本身自带的,否则切换到jquery中去.这个好理解

publishExternalAPI这个方法是绑定一些公共的方法到angular下面,这些是可以在网站中访问到的,像forEach,copy等公共方法,还有一个重要的任务就是初始化angular核心的模块,publishExternalAPI在465行,现在我们来分析里面的一些重要的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
function publishExternalAPI(angular) {
        extend(angular, {
            bootstrap: bootstrap,
            copy: copy,
            extend: extend,
            equals: equals,
            element: jqLite,
            forEach: forEach,
            injector: createInjector,
            noop: noop,
            bind: bind,
            toJson: toJson,
            fromJson: fromJson,
            identity: identity,
            isUndefined: isUndefined,
            isDefined: isDefined,
            isString: isString,
            isFunction: isFunction,
            isObject: isObject,
            isNumber: isNumber,
            isElement: isElement,
            isArray: isArray,
            version: version,
            isDate: isDate,
            lowercase: lowercase,
            uppercase: uppercase,
            callbacks: {
                counter: 0
            },
            $$minErr: minErr,
            $$csp: csp
        }), angularModule = setupModuleLoader(window);
        try {
            angularModule("ngLocale")
        } catch (e) {
            angularModule("ngLocale", []).provider("$locale", $LocaleProvider)
        }
        angularModule("ng", ["ngLocale"], ["$provide",
            function($provide) {
                $provide.provider("$compile", $CompileProvider).directive({
                    a: htmlAnchorDirective,
                    input: inputDirective,
                    textarea: inputDirective,
                    form: formDirective,
                    script: scriptDirective,
                    select: selectDirective,
                    style: styleDirective,
                    option: optionDirective,
                    ngBind: ngBindDirective,
                    ngBindHtml: ngBindHtmlDirective,
                    ngBindTemplate: ngBindTemplateDirective,
                    ngClass: ngClassDirective,
                    ngClassEven: ngClassEvenDirective,
                    ngClassOdd: ngClassOddDirective,
                    ngCloak: ngCloakDirective,
                    ngController: ngControllerDirective,
                    ngForm: ngFormDirective,
                    ngHide: ngHideDirective,
                    ngIf: ngIfDirective,
                    ngInclude: ngIncludeDirective,
                    ngInit: ngInitDirective,
                    ngNonBindable: ngNonBindableDirective,
                    ngPluralize: ngPluralizeDirective,
                    ngRepeat: ngRepeatDirective,
                    ngShow: ngShowDirective,
                    ngStyle: ngStyleDirective,
                    ngSwitch: ngSwitchDirective,
                    ngSwitchWhen: ngSwitchWhenDirective,
                    ngSwitchDefault: ngSwitchDefaultDirective,
                    ngOptions: ngOptionsDirective,
                    ngTransclude: ngTranscludeDirective,
                    ngModel: ngModelDirective,
                    ngList: ngListDirective,
                    ngChange: ngChangeDirective,
                    required: requiredDirective,
                    ngRequired: requiredDirective,
                    ngValue: ngValueDirective
                }).directive(ngAttributeAliasDirectives).directive(ngEventDirectives), $provide.provider({
                    $anchorScroll: $AnchorScrollProvider,
                    $animate: $AnimateProvider,
                    $browser: $BrowserProvider,
                    $cacheFactory: $CacheFactoryProvider,
                    $controller: $ControllerProvider,
                    $document: $DocumentProvider,
                    $exceptionHandler: $ExceptionHandlerProvider,
                    $filter: $FilterProvider,
                    $interpolate: $InterpolateProvider,
                    $interval: $IntervalProvider,
                    $http: $HttpProvider,
                    $httpBackend: $HttpBackendProvider,
                    $location: $LocationProvider,
                    $log: $LogProvider,
                    $parse: $ParseProvider,
                    $rootScope: $RootScopeProvider,
                    $q: $QProvider,
                    $sce: $SceProvider,
                    $sceDelegate: $SceDelegateProvider,
                    $sniffer: $SnifferProvider,
                    $templateCache: $TemplateCacheProvider,
                    $timeout: $TimeoutProvider,
                    $window: $WindowProvider
                })
            }
        ])
    }

方法体中的setupModuleLoader方法是一个模块加载器,这也是一个关键方法, 主要作用是创建和获取模块,代码见417行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function setupModuleLoader(window) {
 
        function ensure(obj, name, factory) {
            return obj[name] || (obj[name] = factory())
        }
 
        var $injectorMinErr = minErr("$injector"),
            ngMinErr = minErr("ng");
 
        return ensure(ensure(window, "angular", Object), "module", function() {
            var modules = {};
 
            return function(name, requires, configFn) {
                var assertNotHasOwnProperty = function(name, context) {
                    if ("hasOwnProperty" === name) throw ngMinErr("badname", "hasOwnProperty is not a valid {0} name", context)
                };
                return assertNotHasOwnProperty(name, "module"), requires && modules.hasOwnProperty(name) && (modules[name] = null), ensure(modules, name, function() {
                    function invokeLater(provider, method, insertMethod) {
                        return function() {
                            return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance
                        }
                    }
                    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);
                    var invokeQueue = [],
                        runBlocks = [],
                        config = invokeLater("$injector", "invoke"),
                        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
                            }
                        };
                    return configFn && config(configFn), moduleInstance
                })
            }
        })
    }

上面publishExternalAPI 方法中的angularModule = setupModuleLoader(window);是在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用angular.module方法.这里的angularModule其实就是相当于angular.module

angular.module在创建模块的时候,传递一个参数的时候,是获取模块;传递一个以上的是创建新模块;该方法返回的是一个moduleInstance对象,它的任务就是来创建控制器,服务,指令,以及配置方法,全局运行方法,而且是链式调用,因为每个方法都会返回moduleInstance,看这里

1
2
3
4
5
function invokeLater(provider, method, insertMethod) {
    return function() {
        return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance
    }
}

此处的return invokeQueue[insertMethod || "push"]([provider, method, arguments]), moduleInstance,逗号表达式是返回最后一个值

再来一个angular.module在项目中运用的代码

1
2
3
4
5
6
angular.module(‘demoApp‘, [])
.factory()
.controller()
.directive()
.config()
.run();

接下来再看publishExternalAPI的代码,因为ngLocale默认没有创建,所以angularModule("ngLocale")这个直接异常,跳到catch里执行angularModule("ngLocale", []).provider("$locale", $LocaleProvider),记住这里的provider方法,默认是把它的参数都存到invokeQueue数组中,以便在后面用到.

接下来开始创建ng模块,它依赖上面的ngLocale模块,注意创建模块的时候传了第三个参数,当创建模块的时候传了三个参数,默认第三参数会执行config(configFn),这个方法也是把相应的参数放入invokeQueue数组中,只不过前两参数是$injector,invoke,这里先透露一下,其实所有invokeQueue数组项中,三个参数的意思:第一个参数调用第二个参数,然后传递第三个参数,这个后面会讲到.

这里说下ng模块中第三个参数里的函数体,这里主要做了两件事,初始了$compile服务,并且利用compile服务的directive方法,把一些常用的指令都保存到compile服务中的一个内部数组中.

这里先说下$provide.provider,这个在angular里用的比较多,其实就是提前把定义的provider放入DI函数内的providerCache内,看如下代码,在740行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
function createInjector(modulesToLoad) {
        function supportObject(delegate) {
            return function(key, value) {
                return isObject(key) ? (forEach(key, reverseParams(delegate)), void 0) : delegate(key, value)
            }
        }
 
        function provider(name, provider_) {
            if (assertNotHasOwnProperty(name, "service"), (isFunction(provider_) || isArray(provider_)) && (provider_ = providerInjector.instantiate(provider_)), !provider_.$get) throw $injectorMinErr("pget", "Provider ‘{0}‘ must define $get factory method.", name);
            return providerCache[name + providerSuffix] = provider_
        }
 
        function factory(name, factoryFn) {
            return provider(name, {
                $get: factoryFn
            })
        }
 
        function service(name, constructor) {
            return factory(name, ["$injector",
                function($injector) {
                    return $injector.instantiate(constructor)
                }
            ])
        }
 
        function value(name, val) {
            return factory(name, valueFn(val))
        }
 
        function constant(name, value) {
            assertNotHasOwnProperty(name, "constant"), providerCache[name] = value, instanceCache[name] = value
        }
 
        function decorator(serviceName, decorFn) {
            var origProvider = providerInjector.get(serviceName + providerSuffix),
                orig$get = origProvider.$get;
            origProvider.$get = function() {
                var origInstance = instanceInjector.invoke(orig$get, origProvider);
                return instanceInjector.invoke(decorFn, null, {
                    $delegate: origInstance
                })
            }
        }
 
        function loadModules(modulesToLoad) {
            var moduleFn, invokeQueue, i, ii, runBlocks = [];
            return forEach(modulesToLoad, function(module) {
                if (!loadedModules.get(module)) {
                    loadedModules.put(module, !0);
                    try {
                        if (isString(module))
                            for (moduleFn = angularModule(module), runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks), invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; ii > i; i++) {
                                var invokeArgs = invokeQueue[i],
                                    provider = providerInjector.get(invokeArgs[0]);
                                provider[invokeArgs[1]].apply(provider, invokeArgs[2])
                            } else isFunction(module) ? runBlocks.push(providerInjector.invoke(module)) : isArray(module) ? runBlocks.push(providerInjector.invoke(module)) : assertArgFn(module, "module")
                    } catch (e) {
                        throw isArray(module) && (module = module[module.length - 1]), e.message && e.stack && -1 == e.stack.indexOf(e.message) && (e = e.message + "\n" + e.stack), $injectorMinErr("modulerr", "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e)
                    }
                }
            }),runBlocks
        }
 
        function createInternalInjector(cache, factory) {
            function getService(serviceName) {
                if (cache.hasOwnProperty(serviceName)) {
                    if (cache[serviceName] === INSTANTIATING) throw $injectorMinErr("cdep", "Circular dependency found: {0}", path.join(" <- "));
                    return cache[serviceName]
                }
                try {
                    return path.unshift(serviceName), cache[serviceName] = INSTANTIATING, cache[serviceName] = factory(serviceName)
                } finally {
                    path.shift()
                }
            }
 
            function invoke(fn, self, locals) {
                var length, i, key, args = [],
                    $inject = annotate(fn);
                for (i = 0, length = $inject.length; length > i; i++) {
                    if (key = $inject[i], "string" != typeof key) 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 (fn.$inject || (fn = fn[length]), self ? -1 : args.length) {
                    case 0:
                        return fn();
                    case 1:
                        return fn(args[0]);
                    case 2:
                        return fn(args[0], args[1]);
                    case 3:
                        return fn(args[0], args[1], args[2]);
                    case 4:
                        return fn(args[0], args[1], args[2], args[3]);
                    case 5:
                        return fn(args[0], args[1], args[2], args[3], args[4]);
                    case 6:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
                    case 7:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
                    case 8:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
                    case 9:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
                    case 10:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
                    default:
                        return fn.apply(self, args)
                }
            }
 
            function instantiate(Type, locals) {
                var instance, returnedValue, Constructor = function() {};
                return Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype, instance = new Constructor, returnedValue = invoke(Type, instance, locals), isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance
            }
            return {
                invoke: invoke,
                instantiate: instantiate,
                get: getService,
                annotate: annotate,
                has: function(name) {
                    return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name)
                }
            }
        }
        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)
            });
        return forEach(loadModules(modulesToLoad), function(fn) {
            instanceInjector.invoke(fn || noop)
        }), instanceInjector
    }

上面说的DI其实就是上面的createInjector函数,这个是angularjs管理依赖注入的核心函数,然后再看$provide.provider,其实就是调用内部函数provider

1
2
3
4
function provider(name, provider_) {
    if (assertNotHasOwnProperty(name, "service"), (isFunction(provider_) || isArray(provider_)) && (provider_ = providerInjector.instantiate(provider_)), !provider_.$get) throw $injectorMinErr("pget", "Provider ‘{0}‘ must define $get factory method.", name);
    return providerCache[name + providerSuffix] = provider_
}

注意这里的providerCache,它保存了所有调用provider方法的provider_

好了,好像说的有点远了,我们再回到初始化的代码

说完了publishExternalAPI的代码,我们再了看看angularInit方法,这个方法的作用就是先找到带angular项目标识的元素,然后调用bootstrap方法,我们重点来看看bootstrap方法,见344行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function bootstrap(element, modules) {
        var doBootstrap = function() {
            if (element = jqLite(element), element.injector()) {
                var tag = element[0] === document ? "document" : startingTag(element);
                throw ngMinErr("btstrpd", "App Already Bootstrapped with this Element ‘{0}‘", tag)
            }
            modules = modules || [], modules.unshift(["$provide",
                function($provide) {
                    $provide.value("$rootElement", element)
                }
            ]), modules.unshift("ng");
            var injector = createInjector(modules);
            return injector.invoke(["$rootScope", "$rootElement", "$compile", "$injector", "$animate",
                function(scope, element, compile, injector) {
                    scope.$apply(function() {
                        element.data("$injector", injector), compile(element)(scope)
                    })
                }
            ]), injector
        }, NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
        return window && !NG_DEFER_BOOTSTRAP.test(window.name) ? doBootstrap() : (window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ""), angular.resumeBootstrap = function(extraModules) {
            forEach(extraModules, function(module) {
                modules.push(module)
            }), doBootstrap()
        }, void 0)
    }

仔细看上面的代码,有一句比较关键var injector = createInjector(modules);,把要初始化的模块传进DI中,并返回一个依赖对象,这里的modules参数包含一个ng模块,一个定义$rootElement值的模块,一个业务对应的模块

现在我们重点分析createInjector的代码,为了方便查阅,把上面的di构造函数重新贴一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
function createInjector(modulesToLoad) {
        function supportObject(delegate) {
            return function(key, value) {
                return isObject(key) ? (forEach(key, reverseParams(delegate)), void 0) : delegate(key, value)
            }
        }
 
        function provider(name, provider_) {
            if (assertNotHasOwnProperty(name, "service"), (isFunction(provider_) || isArray(provider_)) && (provider_ = providerInjector.instantiate(provider_)), !provider_.$get) throw $injectorMinErr("pget", "Provider ‘{0}‘ must define $get factory method.", name);
            return providerCache[name + providerSuffix] = provider_
        }
 
        function factory(name, factoryFn) {
            return provider(name, {
                $get: factoryFn
            })
        }
 
        function service(name, constructor) {
            return factory(name, ["$injector",
                function($injector) {
                    return $injector.instantiate(constructor)
                }
            ])
        }
 
        function value(name, val) {
            return factory(name, valueFn(val))
        }
 
        function constant(name, value) {
            assertNotHasOwnProperty(name, "constant"), providerCache[name] = value, instanceCache[name] = value
        }
 
        function decorator(serviceName, decorFn) {
            var origProvider = providerInjector.get(serviceName + providerSuffix),
                orig$get = origProvider.$get;
            origProvider.$get = function() {
                var origInstance = instanceInjector.invoke(orig$get, origProvider);
                return instanceInjector.invoke(decorFn, null, {
                    $delegate: origInstance
                })
            }
        }
 
        function loadModules(modulesToLoad) {
            var moduleFn, invokeQueue, i, ii, runBlocks = [];
            return forEach(modulesToLoad, function(module) {
                if (!loadedModules.get(module)) {
                    loadedModules.put(module, !0);
                    try {
                        if (isString(module))
                            for (moduleFn = angularModule(module), runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks), invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; ii > i; i++) {
                                var invokeArgs = invokeQueue[i],
                                    provider = providerInjector.get(invokeArgs[0]);
                                provider[invokeArgs[1]].apply(provider, invokeArgs[2])
                            } else isFunction(module) ? runBlocks.push(providerInjector.invoke(module)) : isArray(module) ? runBlocks.push(providerInjector.invoke(module)) : assertArgFn(module, "module")
                    } catch (e) {
                        throw isArray(module) && (module = module[module.length - 1]), e.message && e.stack && -1 == e.stack.indexOf(e.message) && (e = e.message + "\n" + e.stack), $injectorMinErr("modulerr", "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e)
                    }
                }
            }),runBlocks
        }
 
        function createInternalInjector(cache, factory) {
            function getService(serviceName) {
                if (cache.hasOwnProperty(serviceName)) {
                    if (cache[serviceName] === INSTANTIATING) throw $injectorMinErr("cdep", "Circular dependency found: {0}", path.join(" <- "));
                    return cache[serviceName]
                }
                try {
                    return path.unshift(serviceName), cache[serviceName] = INSTANTIATING, cache[serviceName] = factory(serviceName)
                } finally {
                    path.shift()
                }
            }
 
            function invoke(fn, self, locals) {
                var length, i, key, args = [],
                    $inject = annotate(fn);
                for (i = 0, length = $inject.length; length > i; i++) {
                    if (key = $inject[i], "string" != typeof key) 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 (fn.$inject || (fn = fn[length]), self ? -1 : args.length) {
                    case 0:
                        return fn();
                    case 1:
                        return fn(args[0]);
                    case 2:
                        return fn(args[0], args[1]);
                    case 3:
                        return fn(args[0], args[1], args[2]);
                    case 4:
                        return fn(args[0], args[1], args[2], args[3]);
                    case 5:
                        return fn(args[0], args[1], args[2], args[3], args[4]);
                    case 6:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
                    case 7:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
                    case 8:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
                    case 9:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
                    case 10:
                        return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
                    default:
                        return fn.apply(self, args)
                }
            }
 
            function instantiate(Type, locals) {
                var instance, returnedValue, Constructor = function() {};
                return Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype, instance = new Constructor, returnedValue = invoke(Type, instance, locals), isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance
            }
            return {
                invoke: invoke,
                instantiate: instantiate,
                get: getService,
                annotate: annotate,
                has: function(name) {
                    return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name)
                }
            }
        }
        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)
            });
        return forEach(loadModules(modulesToLoad), function(fn) {
            instanceInjector.invoke(fn || noop)
        }), instanceInjector
    }

首先这个函数内部包含有创建项目服务的几个关键方法,比如provider,service,value,factory,其实内部调用的都是provider方法,而且所有的provider都必须包含一个$get属性,只不过没有$get属性的,内部实现都会主动增加一个$get属性,除了这些创建provider的方法外,还有一个内部核心的注入类,这个主要用来创建真正的实例用,并处理相关的依赖创建

这里有几个内部变量值得关注,providerCache这个会保存一个$provide对象,主要用来对外提供创建服务的方法,然后这个变量会保存所有已经注册的provider实倒,包含$get方法的,只是没有实例化;providerInjector变量是传递了providercache变量的内部di实例;instanceCache这个会保存所有已经实例化的provider;instanceInjector是用来真正实例化一个provider的.本身是一个内部di实例.

这里重点说下loadModules方法,因为angularjs就是依靠这个方法来加载所有的模块,以及模块依赖的provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function loadModules(modulesToLoad) {
            var moduleFn, invokeQueue, i, ii, runBlocks = [];
            return forEach(modulesToLoad, function(module) {
                if (!loadedModules.get(module)) {
                    loadedModules.put(module, !0);
                    try {
                        if (isString(module))
                            for (moduleFn = angularModule(module), runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks), invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; ii > i; i++) {
                                var invokeArgs = invokeQueue[i],
                                    provider = providerInjector.get(invokeArgs[0]);
                                provider[invokeArgs[1]].apply(provider, invokeArgs[2])
                            } else isFunction(module) ? runBlocks.push(providerInjector.invoke(module)) : isArray(module) ? runBlocks.push(providerInjector.invoke(module)) : assertArgFn(module, "module")
                    } catch (e) {
                        throw isArray(module) && (module = module[module.length - 1]), e.message && e.stack && -1 == e.stack.indexOf(e.message) && (e = e.message + "\n" + e.stack), $injectorMinErr("modulerr", "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e)
                    }
                }
            }),runBlocks
        }

这个方法的参数是一个数组,里面是的数据是在doBootstrap里定义的,上面有讲

这个方法依次加载模块数组里对应的provider,这里用到了上面提到的_invokeQueue数组,里面定义保存很多provider信息,注意这里的constant类型的provider会直接创建实例,跟别的provider不一样.

1
2
3
var invokeArgs = invokeQueue[i],
    provider = providerInjector.get(invokeArgs[0]);
    provider[invokeArgs[1]].apply(provider, invokeArgs[2])

这里就是利用保存的三个参数来依次利用第一个参数调用第二个参数,然后传递第三个参数

loadModules方法最后返回一个运行块代码,所以一般项目里的run方法会在模块加载完以及config方法调用完之后运行.

1
2
3
return forEach(loadModules(modulesToLoad), function(fn) {
           instanceInjector.invoke(fn || noop)
       }), instanceInjector

注意这里run方法代码在这里执行instanceInjector.invoke(fn || noop),一直觉的instanceInjector和providerInjector这两个变量的定义非常让人迷糊,嘿嘿,估计是google的人写代码非常节省空间吧,这两个变量都是内部DI实例,区别在于第二个参数,当要真正的实例化的时候,第二个参数负责真正的初始化providerCache里保证的provider,其实就是执行它的$get方法,然后把值保存到instanceCache中,以便保证单例使用.

1
2
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider)

这是instanceInjector变量第二个参数的函数体,先在providerCache里查找,然后把provider的$get方法传给instanceInjector的invoke,这个会真正的生成实例.

最后说下invoke的代码,这里会频繁用一个工具方法annotate,这个是获取一个函数的入参,并以数组形式返回,invoke会自动的检查要执行的函数的入参,假如已经生成实例的,则传给函数,否则先生成依赖的实例,最后执行函数

最后当所有的模块加载完成,并且run代码块也执行完成之后,接下来就是编译页面代码,给指令生成相应的link函数了

1
2
3
4
5
6
7
8
injector.invoke([‘$rootScope‘, ‘$rootElement‘, ‘$compile‘, ‘$injector‘, ‘$animate‘,
       function(scope, element, compile, injector, animate) {
        scope.$apply(function() {
          element.data(‘$injector‘, injector);
          compile(element)(scope);
        });
      }]
    );
这个会生成编译实例,通过编译实例去编译项目起始页,编译的核心是生成指令对应的link函数,有点类似后端的编译,先词法分析,用lex,然后语法分析,用parse,最后链接,生成link函数

总结

本篇主要是分析了angularjs的模块加载以及依赖注入这部分源码,以后还会分析编译以及作用域相关的源码,本文只是自己一点angularjs方面的心得,有错误希望大家提出来,一起改进改进.

AngularJS 源码分析1,布布扣,bubuko.com

AngularJS 源码分析1

上一篇:YII 集成jquery


下一篇:将Cocos2dX渲染到MFC窗口上