breakinferno / breakinferno.github.io

0 stars 0 forks source link

angularjs 1.x -依赖注入 #10

Open breakinferno opened 6 years ago

breakinferno commented 6 years ago

依赖注入

什么是依赖注入

首先搞清楚几个概念:依赖、DI、IoC

依赖: 通俗的讲,一个对象需要完成某个动作或者功能,需要依靠另外一个对象提供功能或服务,则就是依赖

控制反转:(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低代码之间的耦合度。通俗的将,控制反转就是将创建依赖的对象的实例的权限交给第三方,而当前对象不关心创建的过程及逻辑,只需指定我们需要的依赖即可。

依赖注入:(Dependency Injection,简称DI)是IOC的一种实现方式,对象在指定了依赖之后,根据指定的依赖标志获取具体的依赖实例,注入到对象中即可,以此来达到控制范围的目的。

依赖注入的本质是设计模式的依赖倒置原则(DIP)。

我们知道一般来说,一个对象只能通过三种方法来得到它的依赖项目:

  1. 常见的在对象内部创建依赖项目并调用依赖,比如在A对象内部使用A.b = new B()这种方法。
  2. 将依赖作为一个全局变量来进行查找或引用
  3. 将依赖传递到需要它的地方,比如A的constructor(iB){this.b = iB}而非contructor(){this.b = new B()}

可以看出第三种就是依赖注入了。依赖注入的调用者不必知道依赖的创建细节,只需能够获取依赖并且能够使用该依赖即可。其他两种方式都会引起其他困难的挑战,例如污染全局作用域以及使隔离变得几乎不可能。

Angular依赖注入方式

总的来说有三种方式,angular的controller、service、factory、filter等都是通过依赖注入方式进行注入。

目前第二种方式最为常见,既避免压缩导致的错误,用于也无需刻意指定$inject属性,因此依赖过程更为透明。

实现

自己实现

var MyController = function($scope,$http){
        $scope.test = 1;
        $http.get('');
}

var inject = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {
        var arr = [];
        for (var i = 0 ; i < deps.length ; i++) {
            if (this.dependencies.hasOwnProperty(deps[i])) {
                arr.push(this.dependencies[deps[i]])
            }
        }
        return function(){
            func.apply(scope || {}, arr);
        }

    }
}

inject.register('$http', {'get':function(){console.log('get')}});
inject.register('$scope', {'test':''});
inject.register('$location', {'hash':function(){console.log('hash')}});

存在一些缺陷,比如参数位置不同就不行。

angular实现

var ARROW_ARG = /^([^(]+?)=>/;
var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');

function stringifyFn(fn) {
  return Function.prototype.toString.call(fn);
}
// 得到依赖项
function extractArgs(fn) {
  var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''),
      args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
  return args;
}

function anonFn(fn) {
  // For anonymous functions, showing at the very least the function signature can help in
  // debugging.
  var args = extractArgs(fn);
  if (args) {
    return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
  }
  return 'fn';
}

function annotate(fn, strictDi, name) {
  var $inject,
      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);
        }
        argDecl = extractArgs(fn);
        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;
}

function createInternalInjector(cache, factory) {
    // 查找依赖项所对应的对象
    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;
          cache[serviceName] = factory(serviceName, caller);
          return cache[serviceName];
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }

    // 执行时注入
    function injectionArgs(fn, locals, serviceName) {
      var args = [],
          $inject = createInjector.$$annotate(fn, strictDi, serviceName);

      for (var i = 0, length = $inject.length; i < length; i++) {
        var 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));
      }
      return args;
    }

    function isClass(func) {
      // Support: IE 9-11 only
      // IE 9-11 do not support classes and IE9 leaks with the code below.
      if (msie || typeof func !== 'function') {
        return false;
      }
      var result = func.$$ngIsClass;
      if (!isBoolean(result)) {
        result = func.$$ngIsClass = /^class\b/.test(stringifyFn(func));
      }
      return result;
    }
    // 调用
    function invoke(fn, self, locals, serviceName) {
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = injectionArgs(fn, locals, serviceName);
      if (isArray(fn)) {
        fn = fn[fn.length - 1];
      }

      if (!isClass(fn)) {
        // http://jsperf.com/angularjs-invoke-apply-vs-switch
        // #5388
        return fn.apply(self, args);
      } else {
        args.unshift(null);
        return new (Function.prototype.bind.apply(fn, args))();
      }
    }

    function instantiate(Type, locals, serviceName) {
      // Check if Type is annotated and use just the given function at n-1 as parameter
      // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
      var ctor = (isArray(Type) ? Type[Type.length - 1] : Type);
      var args = injectionArgs(Type, locals, serviceName);
      // Empty object at position 0 is ignored for invocation with `new`, but required.
      args.unshift(null);
      return new (Function.prototype.bind.apply(ctor, args))();
    }

    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
      }
    };
  }

更多的看这里

好处

DI的核心就是将依赖对象的创建交给第三方,带来的好处如下: