zoukankan      html  css  js  c++  java
  • Angular依赖注入详解

    Angular算是将后端开发工程化引入前端的先驱之一,而Dependency injection依赖注入(后面简称为DI)又是Angular内部运作的核心功能,所以要深入理解Angular有必要先理解这一核心概念。

    维基百科对依赖注入的解释

    在软件工程中,依赖注入是实现控制反转的一种软件设计模式,一个依赖是一个被其他对象(client)调用的对象(服务),注入则是将被依赖的对象(service)实例传递给依赖对象(client)的行为。将 被依赖的对象传给依赖者,而不需要依赖者自己去创建或查找所需对象是DI的基本原则。 依赖注入允许程序设计遵从依赖倒置原则(简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合) 调用者(client)只需知道服务的接口,具体服务的查找和创建由注入者(injector)负责处理并提供给client,这样就分离了服务和调用者的依赖,符合低耦合的程序设计原则。

    依赖注入中的角色

    从维基百科解释可知, DI中包含三个角色,调用者(client), 服务(service)和注入者 (injector),下面开始介绍本文的主题 Angular的依赖注入。

    Angular依赖注入分析

    先看看下面这段 hello,world代码 (注意:设置了严格模式或压缩混淆代码后 下面的代码不能正常工作,后面有解释)

    angular.module('myApp', [])
      .controller('Ctl', function ($scope, $log) {
        $scope.name = 'leonwgc';
        $log.log('hello,world');
      });

    上面这段代码就用到了angular的依赖注入,代码首先创建了一个myApp模块,然后在此模块中创建了Ctl控制器,创建控制器函数的第二个参数则是控制器的构造函数, 构造函数声明了对$scope和$log服务的依赖。 当构造函数执行时, 即可获得$scope和$log服务实例,进行操作。 从我们前面对DI的了解,$scope和$log是由注入器injector 提供,知道了injector的存在,我们直接从angular的源码中将其找出,如下:

    function createInternalInjector(cache, factory) {
        // 中间一段略去...
    
        // 调用client
        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;
    
          // 中间一段略去...
          // 遍历$inject数组调用getService获取服务....
    
          //开始执行client , args则是依赖的全部服务,injector都为我们创建好了
          return fn.apply(self, args);
        }
    
        // 中间一段略去...
    
        // 这里返回公开的injector对象 
        return {
          // 执行DI方法,比如上面的控制器函数
          // invoke方法首先就是调用annotate取得依赖
          // 然后调用get取得服务
          // 如果缓存中没有服务,get内部调用instantiate创建服务并缓存
          // 最后利用function.apply传入依赖并执行
          invoke: invoke,
          // 实例化(创建)服务
          instantiate: instantiate,
          // 获取服务(如果缓存中有,直接从缓存拿,没有则调用instantiate创建并放入缓存,下次直接从缓存拿)
          get: getService,
          // 获得依赖服务
          annotate: createInjector.$$annotate,
          // 检查缓存中是否包含服务
          has: function(name) {
            return providerCache.hasOwnProperty(name + providerSuffix)
             || cache.hasOwnProperty(name);
          }
        };
      }

    源码中查询依赖的源码如下:

    function annotate(fn, strictDi, name) {
      var $inject,
          fnText,
          argDecl,
          last;
    
      if (typeof fn === 'function') {
        // 如果我们直接给函数添加了$inject依赖
        // 则直接返回依赖,后面不做处理
        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...', name);
            }
            // 针对直接在构造函数中使用服务的情况
            // 使用function.toString() 然后正则匹配出依赖的对象
            // 所以上面例子如果混淆了代码就呵呵了
            // 最后存入$inject数组
            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);
              });
            });
          }
          //给构造函数添加$inject属性
          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;
    }

    看了上面的源码片段和解释,想必大家对angular的依赖注入有了整体的认识。

    下面是另外两种推荐的声明依赖的方式

    1. 数组注释 (推荐), js压缩混淆不会有影响。

    angular.module('myApp', [])
      .controller('Ctl', ['$scope', '$log', function ($scope, $log) {
      $scope.name = 'leonwgc';
      $log.log('hello,world');
    }]);

    2.$inject 属性 ,js压缩混淆不会有影响

    angular.module('myApp', [])
      .controller('Ctl', Ctrl);
    
    function Ctrl($scope, $log) {
      $scope.name = 'leonwgc';
      $log.log('hello,world');
    }
    
    // 给构造函数添加$inject属性,
    // $inject是一个数组,元素是依赖的服务名.
    Ctrl.$inject = ["$scope", "$log"];

    Angular引入了大量后端开发的概念,而前端同学可能还不熟悉,望本文能有所帮助。

  • 相关阅读:
    XMEGA IO口外部中断操作
    博客开通了
    cocos2d-x CCParticleSystem粒子系统
    获得web api 返回的Dataset 绑定到网格控件上
    DbHelperSQL—— 动软生成DbHelperSQL类
    Silverlight 调用 web api
    await运算符只能用于异步方法中。请考虑用async修饰符标记此方法,并将其返回类型更改为Task
    js如何获取url的参数
    cvbfbd
    【转】selenium及webdriver的原理
  • 原文地址:https://www.cnblogs.com/leonwang/p/5178551.html
Copyright © 2011-2022 走看看