Angularjs source code parsing: injector and angularjsinjector
Introduction
Injector is used for Automatic Parameter injection, for example
function fn ($http, $scope, aService) {}
Ng automatically transmits $ http, $ scope, and aService as parameters for execution at runtime.
In fact, it is easy to understand that injector has done two things.
- Cache those services and inject them as parameters later
- Analyze the parameter list and find the required parameter injection.
The following source code analyzes how to implement the above two things.
Structure
createInjector -> createInternalInjector return: instanceInjector
Therefore, createInjector () returns instanceInjector with the following structure:
{ invoke: invoke, instantiate: instantiate, get: getService, annotate: annotate, has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); }}
Source code analysis
1. createInjector
Function createInjector (modulesToLoad, strictDi) {strictDi = (strictDi = true); var INSTANTIATING ={}, providerSuffix = 'provider', path = [], loadedModules = new HashMap ([], true), // pre-configured $ provide for loadModules to call providerCache such as registration service ={$ provide: {provider: supportObject (provider ), factory: supportObject (factory), service: supportObject (service), value: supportObject (value), constant: supportObject (constant), decorator: decorator}, // providerInjector, instanceInjector: Two injection collectors // instanceInjector provides external services and other injections, and providerInjector provides providers internally to obtain providerInjector = (providerCache. $ injector = createInternalInjector (providerCache, function () {throw $ injectorMinErr ('unpr', "Unknown provider: {0}", path. join ('<-') ;}, strictDi), instanceCache ={}, instanceInjector = (instanceCache. $ injector = createInternalInjector (instanceCache, function (servicename) {var provider = providerInjector. get (servicename + providerSuffix); return instanceInjector. invoke (provider. $ get, provider, undefined, servicename) ;}, strictDi); // load module forEach (loadModules (modulesToLoad), function (fn) {instanceInjector. invoke (fn | noop) ;}); return instanceInjector ;}
2. $ provide
$provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator}
2.1 supportObject
The method is used for packaging. The method before packaging accepts two parameters (key, value). The encapsulated method supports the input of object parameters, that is, multiple key-> value.
function supportObject(delegate) { return function(key, value) { if (isObject(key)) { forEach(key, reverseParams(delegate)); } else { return delegate(key, value); } };}
2.2 provider
Review the usage of provider, service, and factory
App. factory ('servicename', function () {return {getName: function () {}, setName: function () {}}); app. service ('servicename', function () {this. getName = function () {} this. setName = function () {}}); app. provider ('servicename', function ($ httpProvider) {// inject $ httpProvider this. $ get = function () {return {getName: function () {}, setName: function () {}}}); app. provider ('servicename', {$ get: function () {}}); Function provider (name, provider _) {assertNotHasOwnProperty (name, 'service '); // when provider _ Is fn or array, other providers can be injected into the parameter. // because providerInjector. other dependent providers can be passed in when instantiate (provider _) // This is also the provider and service, where the factory method is different 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) {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 ));}
Finally, it summarizes the implementation of the provider and caches the provider to providerCache for calling.
Unlike other constant implementations, the constant implementations are saved to providerCache and instanceCache respectively, so that both providers and services can be injected.
function constant(name, value) { assertNotHasOwnProperty(name, 'constant'); providerCache[name] = value; instanceCache[name] = value;}
2.3 review loadModules
Function runInvokeQueue (queue) {var I, ii; for (I = 0, ii = queue. length; I <ii; I ++) {var invokeArgs = queue [I], provider = providerInjector. get (invokeArgs [0]); // The format of the stored queue, such as [$ provide, factory, arguments] // is replaced, $ provide. factory. apply ($ provide, arguments); // call $ provid's factory, service, and other provider [invokeArgs [1]. apply (provider, invokeArgs [2]) ;}}
2.4 decorator
Example:
module.config(function($provide) { $provide.decorator('Mail', function($delegate) { $delegate.addCC = function(cc) { this.cc.push(cc); }; return $delegate; });})
The example shows that the input parameter $ delegate is the original service instance, and you can add methods to the instance, that is, the so-called decorator.
Source code:
Function decorator (serviceName, decorFn) {var origProvider = providerInjector. get (serviceName + providerSuffix), orig $ get = origProvider. $ get; origProvider. $ get = function () {// use the provider obtained above to generate the required service instance and inject it to the parameter list var origInstance = instanceInjector with $ delegate. invoke (orig $ get, origProvider); return instanceInjector. invoke (decorFn, null, {$ delegate: origInstance });};}
3. createInternalInjector
3.1 Overall Structure
// Obtain from the cache. If not, call the factory for creation. For details, refer to getService resolution.
Function createInternalInjector (cache, factory) {function getService (serviceName) {} function invoke (fn, self, locals, serviceName) {} function instantiate (Type, locals, serviceName) {} return {// execute fn, with the parameter injection function invoke: invoke, // instantiate fn, instantiate: instantiate can be injected with parameters, // get provider or service get: getService, // obtain the parameter list of the method for injection. annotate: annotate, // check whether provider or service has: function (name) {return providerCache exists. hasOwnProperty (name + providerSuffix) | cache. hasOwnProperty (name );}};}
3.2 annotate
Obtain the fn parameter list
// type1function fn (a, b, c) -> ['a', 'b', 'c']// type2['a', 'b', fn] -> ['a', 'b']// type3function fn () {}fn.$inject = ['a', 'c']-> ['a', 'c']
Source code:
Function annotate (fn, strictDi, name) {var $ inject, fnText, argDecl, last; if (typeof fn = 'function') {if (! ($ Inject = fn. $ inject) {$ inject = []; if (fn. length) {// if (strictDi) {if (! IsString (name) |! Name) {name = fn. name | anonFn (fn);} throw $ injectorMinErr ('strictdi ',' {0} is not using explicit annotation and cannot be invoked in strict mode', name );} // remove the annotation fnText = fn. toString (). replace (STRIP_COMMENTS, ''); // select all parameters fn (a, B, c, d)-> 'a, B, c, d' argDecl = fnText. match (FN_ARGS); // split it into array 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 ;}
3.3 getService
// When the service is not in the cache, enter else. First cache [serviceName] = INSTANTIATING for a flag // because the following call factory (serviceName ), actually it is a recursive call // function (servicename) {// var provider = providerInjector. get (servicename + providerSuffix); // return instanceInjector. invoke (provider. $ get, provider, undefined, servicename); //} // instanceInjector. invoke (provider. $ get will get the parameters to be injected and then inject // After marking, you can determine whether there is a circular dependency on function getService (serviceName) {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);} catch (err) {if (cache [serviceName] === INSTANTIATING) {delete cache [serviceName];} throw err;} finally {path. shift ();}}}
3.4 invoke
Function invoke (fn, self, locals, serviceName) {if (typeof locals = 'string') {serviceName = locals; locals = null;} var args = [], // obtain the parameter list $ inject = 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 ', 'encrect injection token! Expected service name as string, got {0} ', key);} // locals priority args. push (locals & locals. hasOwnProperty (key )? Locals [key]: getService (key);} if (isArray (fn) {fn = fn [length];} return fn. apply (self, args );}
3.5 instantiate
Function instantiate (Type, locals, serviceName) {var Constructor = function () {}, instance, returnedValue; // when type is array, obtain the final parameter, for example: ['$ Window', function ($ win) {}] Constructor. prototype = (isArray (Type )? Type [Type. length-1]: Type ). prototype; instance = new Constructor (); // call invoke to execute the Type method returnedValue = invoke (Type, instance, locals, serviceName); return isObject (returnedValue) | isFunction (returnedValue )? ReturnedValue: instance ;}
Instantiate is used to instantiate Type. During the instantiation process, parameters can be automatically input to the constructor.