AngularJS-2.의존 주입

21062 단어 AngularJS
AngularJS의 강력한 점은 주입에 의존하는 것이다.bootstrap을 호출할 때createInjector를 호출해서 주입기를 만듭니다.이 메서드의 코드는 다음과 같이 간단합니다.
function createInjector(modulesToLoad, strictDi) {
    strictDi = (strictDi === true);
    var INSTANTIATING = {},
        providerSuffix = 'Provider',
        path = [],
        loadedModules = new HashMap([], true),
        providerCache = {
            // ... ...
        },
        providerInjector = (providerCache.$injector =
            createInternalInjector(providerCache, function(serviceName, caller) {
                // ... ...
            })),
        instanceCache = {},
        instanceInjector = (instanceCache.$injector =
            createInternalInjector(instanceCache, function(serviceName, caller) {
                // ... ...
            }));

    forEach(loadModules(modulesToLoad), function(fn) {
        if (fn) instanceInjector.invoke(fn);
    });
    return instanceInjector;
    function supportObject(delegate) {}
    function provider(name, provider_) {}
    function enforceReturnValue(name, factory) {}
    function factory(name, factoryFn, enforce) {}
    function service(name, constructor) {}
    function value(name, val) {}
    function constant(name, value) {}
    function decorator(serviceName, decorFn) {}
    function loadModules(modulesToLoad) {}
    function createInternalInjector(cache, factory) {
        function getService(serviceName, caller) {}
        function invoke(fn, self, locals, serviceName) {}
        function instantiate(Type, locals, serviceName) {}
        return {
            // ... ...
        };
    }
}

이 함수는 비교적 길고 논리가 복잡하기 때문에 여기서 단락을 나누어 분석한다.

1.createInjector

strictDi = (strictDi === true);
var INSTANTIATING = {},
    providerSuffix = 'Provider',
    path = [],
    loadedModules = new HashMap([], true),
    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(serviceName, caller) {
            if (angular.isString(caller)) {
                path.push(caller);
            }
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' ));
        })),
    instanceCache = {},
    instanceInjector = (instanceCache.$injector =
        createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
        }));

forEach(loadModules(modulesToLoad), function(fn) {
    if (fn) instanceInjector.invoke(fn);
});

return instanceInjector;

보시다시피 일부 변수의 정의가forEach에서loadModules를 실행하고 모듈을 순서대로 불러오고 마지막으로instanceInjector로 돌아갑니다.
다음은 정의된 변수에 대한 설명입니다.
  • loadedModules: 하나의 해시 테이블로 대상의 글씨체가 해시 테이블인 것과 달리 문자열을 키 값으로 지원할 뿐만 아니라 대상도 키 값으로 지원한다
  • providerCache: 그중의supportObject는 수식 함수로 함수의 대상 호출을 지원합니다. 예를 들어:function f(key,val) {} 처리를 거친 후supportObject(f) ({key1: val1, key2: val2})
  • 2. createInternalInjector


    이 방법은 캐치(대상)와factory(함수) 두 개의 매개 변수를 수신하는 주사기를 만들고 최종 반환 값은 다음과 같다.
    {
        invoke: invoke,
        instantiate: instantiate,
        get: getService,
        annotate: createInjector.$$annotate,
        has: function(name) {
            return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
        }
    }

    2.1getService

    function getService(serviceName, caller) {
        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, caller);
            } catch (err) {
                if (cache[serviceName] === INSTANTIATING) {
                    delete cache[serviceName];
                }
                throw err;
            } finally {
                path.shift();
            }
        }
    }

    이 방법의 주요 사고방식은 서비스Name에 대응하는 서비스가 있는지 캐치에서 확인하고 있으면 바로 되돌려줍니다. 그렇지 않으면factory를 호출해서 하나를 만들고 캐치에 캐시합니다.

    2.2annotate


    이 방법은 주로 주입을 추정하는 데 쓰인다.예를 들어 다음 코드는 다음과 같습니다.
    angular.module('MyModule', [])
        .factory('service', function() {
            return {
                greeting: 'hello world'
            };
        })
        .controller('ctrl', ['$scope', '$injector', function($scope, $injector) {
            $injector.invoke(function(service) {
                console.log(service.greeting);
            });
        }]);

    $injector를 호출하고 있습니다.invoke에서 서비스를 주입했습니다.AngularJS는 주로 함수의 toString() 방법을 통해 이 기능을 수행합니다.관련 소스는 다음과 같습니다.
    var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
    var FN_ARG_SPLIT = /,/;
    var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
    var $injectorMinErr = minErr('$injector');
    
    function anonFn(fn) {
        // For anonymous functions, showing at the very least the function signature can help in
        // debugging.
        var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
            args = fnText.match(FN_ARGS);
        if (args) {
            return 'function(' + (args[1] || '').replace(/[\s\r
    ]+/
    , ' ') + ')'; } return 'fn'; } 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); } 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; }

    소스 코드를 통해 다음을 확인할 수 있습니다.
  • fn이 함수라면 toString () 을 통해 문자열 형식을 얻어 매개 변수를 그룹 $inject에 넣기
  • 만약 fn이 하나의 수조, 즉 ['서비스', function(서비스) {}] 형식이라면 이 수조의 마지막 항목을 제거하고 나머지 부분은 $inject 수조에 넣는다. 예를 들어
  • angular.module('MyModule', [])
        .factory('service', function() {
            return {
                greeting: 'hello world'
            };
        })
        .controller('ctrl', ['$scope', '$injector', function($scope, $injector) {
            console.log($injector.annotate(function(arg1, arg2, arg3) {}));
            // ["arg1", "arg2", "arg3"]
    
            console.log($injector.annotate(['service', function(arg1) {}]));
            // ["service"]
        }]);

    2.3invoke


    소스는 다음과 같습니다.
    function invoke(fn, self, locals, serviceName) {
        if (typeof locals === 'string') {
            serviceName = locals;
            locals = null;
        }
    
        var args = [],
            $inject = createInjector.$$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);
            }
            args.push(
                locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName)
            );
        }
        if (isArray(fn)) {
            fn = fn[length];
        }
    
        // http://jsperf.com/angularjs-invoke-apply-vs-switch
        // #5388
        return fn.apply(self, args);
    }

    아이디어는 다음과 같습니다.
  • annotate 방법을 통해 주입해야 할 파라미터를 얻기;
  • locals나 get Service를 통해 주입할 매개 변수의 실례를 순서대로 얻어args 그룹에 넣기;
  • 만약에 fn이 하나의 수조, 즉 ['서비스', function(서비스) {}] 형식이라면 수조 중 마지막 항목을 실행 함수 fn으로 취한다.
  • 매개 변수args로 fn을 실행합니다.

  • 3.loadModules


    이 방법은 주로 모듈을 로드하는 데 사용되며 소스는 다음과 같습니다.
    function loadModules(modulesToLoad) {
        var runBlocks = [],
            moduleFn;
        forEach(modulesToLoad, function(module) {
            if (loadedModules.get(module)) return;
            loadedModules.put(module, true);
    
            function runInvokeQueue(queue) {
                var i, ii;
                for (i = 0, ii = queue.length; i < ii; i++) {
                    var invokeArgs = queue[i],
                        provider = providerInjector.get(invokeArgs[0]);
    
                    provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
                }
            }
    
            try {
                if (isString(module)) {
                    moduleFn = angularModule(module);
                    runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
                    runInvokeQueue(moduleFn._invokeQueue);
                    runInvokeQueue(moduleFn._configBlocks);
                } else if (isFunction(module)) {
                    runBlocks.push(providerInjector.invoke(module));
                } else if (isArray(module)) {
                    runBlocks.push(providerInjector.invoke(module));
                } else {
                    assertArgFn(module, 'module');
                }
            } catch (e) {
                if (isArray(module)) {
                    module = module[module.length - 1];
                }
                if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
                    // Safari & FF's stack traces don't contain error.message content
                    // unlike those of Chrome and IE
                    // So if stack doesn't contain message, we create a new string that contains both.
                    // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
                    /* jshint -W022 */
                    e = e.message + '
    '
    + e.stack; } throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:
    {1}"
    , module, e.stack || e.message || e); } }); return runBlocks; }

    함수 loadModules의 역할은 배열 modulesToLoad의 각 항목에 대해 각각 로드하는 것입니다.
  • 모듈이 문자열이라면 우선angular를 통과한다.module(module Name)에서 모듈 대상을 꺼내서 의존 모듈을 불러오고 모든 모듈과 그 자체의runBlocks를 runBlocks 그룹에 놓고 순서대로 호출invokeQueue와configBlocks
  • 모듈이 함수나 그룹이면providerInjector를 실행합니다.invoke(module)를 사용하고runBlocks 그룹에 결과를 넣기
  • runInvokeQueue는 내부 방법으로 매개 변수가 하나의 모듈의invokeQueue 또는configBlocks 배열.

    요약:


    angularJS 내부에 두 개의 캐시를 설치했습니다.

    좋은 웹페이지 즐겨찾기