Introduction
Injector is used to perform automatic parameter injection, for example
function fn ($http, $scope, aService) { }
When ng runs, $http, $scope, aService will be automatically passed in as parameters for execution.
It's actually easy to figure it out, injector did 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
So createInjector() returns instanceInjector, the structure is as follows:
{ invoke: invoke, instantiate: instantiate, get: getService, annotate: annotate, has: function(name) { return (name + providerSuffix) || (name); } }
Source code analysis
1. createInjector
function createInjector(modulesToLoad, strictDi) { strictDi = (strictDi === true); var INSTANTIATING = {}, providerSuffix = 'Provider', path = [], loadedModules = new HashMap([], true), // Pre-configure $provide, for call to register service in loadModules, etc. providerCache = { $provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator } }, // providerInjector, instanceInjector Two injectors // instanceInjector provides service and other injection to the outside, providerInjector provides provider to obtain internally providerInjector = (providerCache.$injector = createInternalInjector(providerCache, function() { throw $injectorMinErr('unpr', "Unknown provider: {0}", (' <- ')); }, strictDi)), instanceCache = {}, instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) { var provider = (servicename + providerSuffix); return (provider.$get, provider, undefined, servicename); }, strictDi)); // Load the module forEach(loadModules(modulesToLoad), function(fn) { (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
For packaging methods, the method before packaging accepts two parameters (key, value). The packaged method can support the object parameters passing in, that is, multiple keys -> values.
function supportObject(delegate) { return function(key, value) { if (isObject(key)) { forEach(key, reverseParams(delegate)); } else { return delegate(key, value); } }; }
2.2 provider
Review how provider, service and factory are used
('serviceName', function(){ return { getName: function(){}, setName: function(){} } }); ('serviceName', function(){ = function() {} = function() {} }); ('serviceName', function($httpProvider){ // Inject $httpProvider this.$get = function() { return { getName: function(){}, setName: function(){} }; } }); ('serviceName', { $get: function () {} }); function provider(name, provider_) { assertNotHasOwnProperty(name, 'service'); // When provider_ is fn or array, other providers can be injected into parameters // Because (provider_) can be passed in other dependent providers when (provider_) // This is also the difference between provider and service and factory methods if (isFunction(provider_) || isArray(provider_)) { provider_ = (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 $(constructor); }]); } function value(name, val) { return factory(name, valueFn(val)); }
Finally summarized to the implementation of the provider, cache the provider to the providerCache for call
What is different from the others is the implementation of constant, which is saved in providerCache and instanceCache respectively, so that both the provider and the service 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 = ; i < ii; i++) { var invokeArgs = queue[i], provider = (invokeArgs[0]); // The format of the queue that is stored in [$provide, factory, arguments] // After replacement, $($provide, arguments); // It is to call $provid's factory, service, etc. provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } }
2.4 decorator
Example:
(function($provide) { $('Mail', function($delegate) { $ = function(cc) { (cc); }; return $delegate; }); })
Using the example, it can be seen that the passed parameter $delegate is the original service instance, and you need to add a method to this instance, which is the so-called decorator
Source code:
function decorator(serviceName, decorFn) { var origProvider = (serviceName + providerSuffix), orig$get = origProvider.$get; origProvider.$get = function() { // Generate the required service instance through the provider obtained above, and inject it into the parameter list with $delegate var origInstance = (orig$get, origProvider); return (decorFn, null, {$delegate: origInstance}); }; }
3. createInternalInjector
3.1 Overall structure
// Get it from cache, if not, call factory to create it. See getService analysis for details
function createInternalInjector(cache, factory) { function getService(serviceName) { } function invoke(fn, self, locals, serviceName){ } function instantiate(Type, locals, serviceName) { } return { // Execute fn, with parameter injection function invoke: invoke, // Instantiate fn, parameter injection can be used instantiate: instantiate, // Get the provider or service get: getService, // Get the parameter list of the method for injection annotate: annotate, // Confirm whether it contains a provider or service has: function(name) { return (name + providerSuffix) || (name); } }; }
3.2 annotate
Get the parameter list of fn
// type1 function fn (a, b, c) -> ['a', 'b', 'c'] // type2 ['a', 'b', fn] -> ['a', 'b'] // type3 function 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 () { // In strict mode, or throw error if (strictDi) { if (!isString(name) || !name) { name = || anonFn(fn); } throw $injectorMinErr('strictdi', '{0} is not using explicit annotation and cannot be invoked in strict mode', name); } // Remove comments fnText = ().replace(STRIP_COMMENTS, ''); // Select all parameters fn(a,b,c,d) -> 'a,b,c,d' argDecl = (FN_ARGS); // Divide into array forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ (FN_ARG, function(all, underscore, name){ $(name); }); }); } fn.$inject = $inject; } } else if (isArray(fn)) { last = - 1; assertArgFn(fn[last], 'fn'); $inject = (0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; }
3.3 getService
// When there is no service in cache, enter else, cache[serviceName] = INSTANTIATING to make a mark// Because the next call to factory(serviceName) is actually a recursive call// function(servicename) { // var provider = (servicename + providerSuffix); // return (provider.$get, provider, undefined, servicename); // } // (provider.$get will get the parameters that need to be injected and then inject it// Therefore, after marking it, you can determine whether there is a circular dependencyfunction getService(serviceName) { if ((serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', serviceName + ' <- ' + (' <- ')); } return cache[serviceName]; } else { try { (serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { (); } } }
3.4 invoke
function invoke(fn, self, locals, serviceName){ if (typeof locals === 'string') { serviceName = locals; locals = null; } var args = [], // Get the parameter list $inject = annotate(fn, strictDi, serviceName), length, i, key; for(i = 0, 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); } // locals are preferred ( locals && (key) ? locals[key] : getService(key) ); } if (isArray(fn)) { fn = fn[length]; } return (self, args); }
3.5 instantiate
function instantiate(Type, locals, serviceName) { var Constructor = function() {}, instance, returnedValue; // When type is array, get the last parameter such as: ['$window', function($win){}] = (isArray(Type) ? Type[ - 1] : Type).prototype; instance = new Constructor(); // Call invoke to execute Type method returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; }
The function of instantiate is to instantiate the Type, and parameters can be automatically passed into the constructor during the instantiation process.