zoukankan      html  css  js  c++  java
  • 我的angularjs源码学习之旅2——依赖注入

      依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题。简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时候再参数中添加这些依赖对象。

      理解很简单,我们以一个例子说明

    var $name = "chua",$age = 26;
    function myInfo($name,$age){
      alert($name + ":" + $age );
    };
    
    //普通情况下应该执行
    myInfo($name,$age);//"chua:26"
    //实现依赖注入以后
    myInfo();//本人希望打印的结果是"chua:26",但是毫无疑问在没有实现依赖注入之前是不好使的

    那么怎么样实现依赖注入呢?


      首先,需要有一个函数来接管myInfo的函数定义,不然我们没法拿到myInfo的依赖对象名称加以保存。

    function injector(fn){
        //处理fn,保存依赖对象
        ...
        return function(){
          fn.apply({},xxx);//xxx是处理过后的参数
        }
    }
    
    //myInfo的定义变成
    var myInfo = injector(function ($name,$age){
      alert($name + ":" + $age );
    });
    myInfo();

      injector函数是怎么获取到myInfo的依赖对象$name、$age的?我们可以通过传入参数的函数fn.toString()来看,

    //fn.toString()的结果为下面的字符串
    "function myInfo($name,$age){
      alert($name + ":" + $age );
    }"

      参考angular的处理:从字符串中读取函数myInfo的参数还是能读取。

      STRIP_COMMENTS =/((//.*$)|(/*[sS]*?*/))/gm,
      FN_ARGS = /^[^(]*(s*([^)]*))/m,
      FN_ARG =/^s*(S+?)s*$/;
      function injector(fn){
        //处理fn,保存依赖对象
        var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
        var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
        var args = argDecl[1].split(",");//["$name","$age"]
        for(var i = 0; i < args.length; i++){
          args[i] = args[i].replace(FN_ARG,"$1");
        }
        ... 
    return function(){ fn.apply({},xxx);//xxx是处理过后的参数 } }

      但是有这个还不够,我们虽然拿到了要依赖的对象名称,但是我们并没有给这些名称指定对应的对象。所以应当有一个给这些要依赖的对象注册的过程。

      function injector(fn){
        ...
        var args = argDecl[1].split(",");
        for(var i = 0; i < args.length; i++){
          args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
        }    
        return function(){
          fn.apply({},args);
        }
      }
      injector.cache = {};
      injector.register = function(key,resource){
        injector.cache[key] = resource;
      }
    //先注册要依赖的对象 injector.register(
    "$name",$name); injector.register("$age",$age);

      这个时候我们可以使用injector来声明函数了

      var myInfo = injector(function($name,$age){
        alert($name + ":" + $age );
      });
    //没有任何参数,得到期望的结果 myInfo();//chua:26

      只要是已经注册过的对象就多可以注入到使用injector函数来声明的函数中使用。完整的测试代码如下

    <script>
      var $name = "chua",
      $age = 26,
      STRIP_COMMENTS =/((//.*$)|(/*[sS]*?*/))/gm,
      FN_ARGS = /^[^(]*(s*([^)]*))/m,
      FN_ARG =/^s*(S+?)s*$/;
      function injector(fn){
        //处理fn,保存依赖对象
        var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
        var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
        var args = argDecl[1].split(",");//["$name","$age"]
        for(var i = 0; i < args.length; i++){
          args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
        }
        return function(){
          fn.apply({},args);
        }
      }
      injector.cache = {};
      injector.register = function(key,resource){
        injector.cache[key] = resource;
      }
      //先注册要依赖的对象
      injector.register("$name",$name);
      injector.register("$age",$age);
    
    
      //下面这个奇迹出现的时刻
      var myInfo = injector(function($name,$age){
        alert($name + ":" + $age );
      });
      myInfo();//chua:26
    </script>
    View Code

       

      好了简单的实现了一个依赖注入。但是依赖注入有一个问题,依赖注入是基于依赖对象的名称字符串的

    injector(function($name,$age){...});

      那么上面的代码通过一般的压缩工具压缩以后会变成

    a(function(b,c){...});

      已经无法找到依赖对象是"$name"/"$age"对应的对象了。

      

      所以angular想了几个办法。 

      angular简单注入方式为

    myModule.controller('myCtrl',function($scope) {... });

      数组注释法

     myModule.controller('myCtrl', ['$scope', function($scope) {... }]);

       显式注入法

    function myCtrl(){...};
    myCtrl.$inject = ["$scope"];
    myModule.controller('myCtrl',myCtrl);

       有一个第三方的插件ng-min,它可以帮你将以简单方式注入的代码自动转换成数组注释的方式,即能满足你写简洁代码的愿望,又能避免压缩出错问题。ng-min地址:https://github.com/btford/ngmin

      

    angular实现依赖注入的方式


      应该说,js的依赖注入实现方式都是类似的。angular实现的依赖注入对象结构如下

    injector = {
      annotate: function annotate(fn, strictDi, name),
      get: function getService(serviceName, caller),
      has: function(name),
      instantiate: function  instantiate(Type, locals, serviceName),
      invoke: function invoke(fn, self, locals, serviceName)
    }

    1.需要有注入的对象(依赖的对象)保存的地方。

      不知道还记不记得我的angular源码学习之旅1中提到angularModule = setupModuleLoader(window)给angular添加了模块声明(添加模块)、加载(获取模块)的方法angular.module。而在创建注入对象的时候(createInjector函数中)有这么一段代码

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

      其中loadModules(modulesToLoad)就是去加载依赖模块,在loadModules函数中真正去加载模块的是这段代码

            if (isString(module)) {
              moduleFn = angularModule(module);
              ...
            }

      angularModule即angular.module。前面也分析过这些模块实际都保存在一个外部变量modules

      

      所以通过angular.module方法就是通过模块名去modules上添加或取出其中的模块。

      

    2.获取依赖注入列表(即要依赖的对象的名称)

      获取方式也是通过fn.toString()来获取的。angular的实现是injector.annotate,函数体为

    function annotate(fn, strictDi, name) {
      var $inject,
          fnText,
          argDecl,
          last;
    
      if (typeof fn === 'function') {
        if (!($inject = fn.$inject)) {
          $inject = [];
          if (fn.length) {
            ...
            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)) {
        ...
      } else {
        ...
      }
      return $inject;
    }

      只是应为angular接受多种注入方式所以处理代码有多个分支,而且也只是保存了依赖对象名称,并没有返回执行函数,执行函数是统一的injector.invoke。

      先前我们自己的实现执行是直接调用的myInfo();angular的执行函数是injector.invoke,函数体如下

        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];
            ...
    //获取依赖对象
    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); }

       好了angularjs的依赖注入实现分析到此结束。更详细的细节可以断点跟踪。

  • 相关阅读:
    什么是回归测试?
    单元测试、集成测试、系统测试的侧重点是什么?
    一个测试工程师应具备那些素质?
    你所了解的的软件测试类型都有哪些,简单介绍一下。
    Python
    爬虫-urllib3模块的使用
    爬图片(附,源码)
    MySQL存储引擎:MyISAM和InnoDB的区别
    员工管理系统
    MySQL锁(二)表锁:为什么给小表加字段会导致整个库挂掉?
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/angular-dependency-injection-in.html
Copyright © 2011-2022 走看看