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
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 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-configure $provide for call registration 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 injections externally, and providerInjector provides provider 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)); // Loading 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
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
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 parameters// Because when providerInjector.instantiate(provider_) can be passed into other providers that depend on // This is also the difference between provider and service and factory methods 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 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 = queue.length; i < ii; i++) { var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); // The format of the queue is stored in [$provide, factory, arguments] // After replacement, $provide.factory.apply($provide, arguments); // It is to call $provid 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; });})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 = providerInjector.get(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 = instanceInjector.invoke(orig$get, origProvider); return instanceInjector.invoke(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, invoke: invoke, // Instantize fn, instantiate: instantiate, // Get the provider or service get: getService, // Get the parameter list of the method for injection annotate: annotate, // Confirm whether it contains provider or service has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } };}3.2 annotate
Get the parameter list of fn
// 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 comment fnText = fn.toString().replace(STRIP_COMMENTS, ''); // Select all parameters fn(a,b,c,d) -> 'a,b,c,d' argDecl = fnText.match(FN_ARGS); // Split 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 there is no service in the cache, enter else, cache[serviceName] = INSTANTIATING to make a mark // Because next call factory(serviceName), it is actually 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 out the parameters that need to be injected and inject // Therefore, after marking it, you can determine whether there is a circular dependency 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 = [], // Get 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', 'Incorrect 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, get the last parameter such as: ['$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;}The function of instantiate is to instantiate the Type, and parameters can be automatically passed into the constructor during the instantiation process.