1. 程式人生 > >Angular2 依賴注入之裝飾器

Angular2 依賴注入之裝飾器

下面會從 Angular 的原始碼分析依賴注入中的的裝飾器,基於 [email protected]

Angular 依賴注入過程主要有下面幾個重要的部分組成:

  • Inject 和 Injectable 裝飾器
  • 解析提供商,構造注入器
  • 獲取例項化物件

Angular 依賴注入中的一些重要的概念:

Provider :
提供商,下面就是一個Proviver,一共有5種構造提供商的方式:TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider

{ provide: Logger, useClass: Logger }

Token :
令牌,提供商中的第一個引數就是Token,在查詢依賴時作為 key 使用。

Injector :
注入器,用於解析依賴和建立物件。

Example

class Engine {
  start() {
    console.log('engine start');
  }
}

class Car {
  engine: Engine;
  constructor(@Inject(Engine) engine) {
    this.engine = engine;
  }
  open() {
    this.engine.start();
    console.log('car open'
); } } let inj = ReflectiveInjector.resolveAndCreate([ Car, Engine ]); let car = inj.get(Car); car.open();

1. 裝飾器

下面主要講 Inject 和 Injectable 兩個裝飾器。

1.1 Inject 裝飾器

構造器中的@Inject()指明瞭需要注入的物件型別,這裡使用TypeScript的裝飾器模式。

Inject裝飾器會將 構造器 => [依賴0, 依賴1 …] 這種 Map 資料結構儲存在全域性變數中,而不是裝飾的物件上。而且只是儲存了這種資料關係,並不會實際建立物件。,Injector 會從前面儲存的數

簡單地說就是,Inject 裝飾器在 window[‘__core-js_shared__’] 這個物件中儲存了一個 Map = { Car() => [ Engine() ] }, 在後面在解析 Provider 的時候,Injector 就是從這裡尋找物件的依賴。

當然實際還要複雜的多。

下面將一步步介紹 Inject 注入器做了哪些事,看原始碼:

在上面的程式碼編譯成 JS 檔案時,可以看到如下程式碼,__param() 函式傳入 paramIndex 和 decorator 兩個引數,其中 decorator 由 metadata_1.Inject() 得到,這個函式先放到後面。 __decorate 需要四個引數,因為裝飾器可以用在函式或屬性上面,因此需要對不同的目標作不同的處理。

在本例中只傳入了2個引數,分別是 decorators 和 target ( Car ), 因此 r = target. 後面判斷是否存在Reflect這個物件,因為這個物件是 es7 中的提案,一般瀏覽器中不存在,需要使用 polyfills 的方式引入。如果不存在,則對 decorators 中的每一個值執行 d(r),那麼 d(r) 這個方法到底做了什麼呢?

decorators 中的每一個 decorator 都是 __param() 的處理結果,而它的返回值是一個匿名函式,因此 d(r) 實際執行了這個匿名函式,匿名函式裡面又執行了 decorator(). decorator是什麼函式呢?

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, 
      r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, 
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") 
      r = Reflect.decorate(decorators, target, key, desc);
    else 
      for (var i = decorators.length - 1; i >= 0; i--) 
        if (d = decorators[i]) 
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) {
      decorator(target, key, paramIndex);
    }
  };
var metadata_1 = require("../packages/core/src/di/metadata");

//省略中間程式碼
//...

Car = __decorate([
  __param(0, metadata_1.Inject(Engine))
], Car);

先看 metadata_1.Inject() 這個函式,在 metadata.ts 中 Inject 返回了 makeParamDecorator() 的結果

export const Inject: InjectDecorator = 
    makeParamDecorator('Inject', [['token', undefined]]);

在 makeParamDecorator() 這個函式中定義了一個 ParamDecoratorFactory 工廠方法,並將 ParamDecoratorFactory 方法返回。 因此 metadata_1.Inject(Engine) = ParamDecoratorFactory(Engine), 並返回了一個 ParamDecorator() 函式。 所以上面的 decorator(target, key, paramIndex) = ParamDecorator (Car, undefined, 0);

export function makeParamDecorator(
    name: string, props: ([string, any] | {[name: string]: any})[], parentClass?: any): any {
  const metaCtor = makeMetadataCtor(props);
  function ParamDecoratorFactory(...args: any[]): any {
    if (this instanceof ParamDecoratorFactory) {
      metaCtor.apply(this, args);
      return this;
    }
    const annotationInstance = new (<any>ParamDecoratorFactory)(...args);

    (<any>ParamDecorator).annotation = annotationInstance;
    return ParamDecorator;

    function ParamDecorator(cls: any, unusedKey: any, index: number): any {
      const parameters: (any[] | null)[] = Reflect.getOwnMetadata('parameters', cls) || [];

      // there might be gaps if some in between parameters do not have annotations.
      // we pad with nulls.
      while (parameters.length <= index) {
        parameters.push(null);
      }

      parameters[index] = parameters[index] || [];
      parameters[index] !.push(annotationInstance);

      Reflect.defineMetadata('parameters', parameters, cls);
      return cls;
    }
  }
  if (parentClass) {
    ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
  }
  ParamDecoratorFactory.prototype.toString = () => `@${name}`;
  (<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
  return ParamDecoratorFactory;
}

接下來看 ParamDecorator(), 首先通過 Reflect.Reflect() 獲取引數parameters,然後將 ParamDecoratorFactory 的一個例項(內部儲存了 Engine 資訊)放到引數數組裡面。

Reflect.getOwnMetadata() 這個函式會獲取物件上所儲存的元資料資訊,本質是一個 Map 物件。 如果沒有找到便建立一個空的 Map. 其中 Reflect 靜態方法所儲存的資訊在全域性物件 window[‘__core-js_shared__’] 中,並不是存在註解所對應的物件中。直接在控制檯輸入可以看到相應的資訊。

window['__core-js_shared__'] 

下面是一個實際的資料結構,具體為什麼要搞這麼複雜的結構我也是懵逼的…
可能還要看看 Reflect 中的實現了…

angular di

這樣就可以將一個物件 (這裡是 Car ) 和多個 ParamDecoratorFactory 例項 (這裡是Engine依賴) 關聯在一起。而 ParamDecoratorFactory 例項中包含了依賴的資訊。後面在尋找依賴,建立物件就是從 Reflect 中獲取資訊的。

構造 ParamDecoratorFactory 時使用了 makeMetadataCtor() 函式,這個函式傳入引數 [[‘token’, undefined]] 就是將 token : Engine 這條屬性賦值給 ParamDecoratorFactory 例項。

function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any {
  return function ctor(...args: any[]) {
    props.forEach((prop, i) => {
      const argVal = args[i];
      if (Array.isArray(prop)) {
        // plain parameter
        this[prop[0]] = argVal === undefined ? prop[1] : argVal;
      } else {
        for (const propName in prop) {
          this[propName] =
              argVal && argVal.hasOwnProperty(propName) ? argVal[propName] : prop[propName];
        }
      }
    });
  };
}

1.2 Injectable 裝飾器

Inject 是用來修飾屬性的,而 Injectable 則是用於修飾建構函式的。我們一般在定義一個服務的時候會用以下的方式。注意這裡的建構函式,沒有使用 @Inject(Engine),但是使用了 @injectable 裝飾類。

@Injectable()
class Car {
  engine: Engine;

  constructor(engine:Engine) {
    this.engine = engine;
  }

  open() {
    this.engine.start();
    console.log('car open');
  }
}

Angular 官方文件裡面說了如果一個服務沒有依賴其他的服務,即構造器中的引數為空,Injectable 註解是可有可無的,但是如果有依賴,必須加上 @Injectable()。

實際上使用了@Injectable()裝飾的類,其建構函式中的引數就可以省略@Inject()。

使用 @Injectable 註解的類會 TypeScript 會編譯成以下程式碼。和上面不同的是,這裡把依賴資訊放在 “design:paramtypes” 中,而前面則是放在 “parameters” 裡面。

var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Car = __decorate([
  metadata_1.Injectable(),
  __metadata("design:paramtypes", [Engine])
], Car);

在實際查詢依賴資訊時,有下面的程式碼。 依賴解析時分別獲取 ‘parameters’ 和 ‘design:paramtypes’ 這兩個的元資料。所以 @Injectable() 裝飾器也能儲存依賴資訊。

// API for metadata created by invoking the decorators.
    if (this._reflect != null && this._reflect.getOwnMetadata != null) {
      const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
      const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
      if (paramTypes || paramAnnotations) {
        return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
      }
    }

相關推薦

Angular2 依賴注入裝飾

下面會從 Angular 的原始碼分析依賴注入中的的裝飾器,基於 [email protected] Angular 依賴注入過程主要有下面幾個重要的部分組成: Inject 和 Injectable 裝飾器 解析提供商,構造注入器 獲取例項化物

Angular2 依賴注入例項化過程

這裡會介紹 Angular 的注入器和例項化過程,基於 [email protected] Angular 依賴注入過程主要有下面幾個重要的部分組成: Inject 和 Injectable 裝飾器 解析提供商,構造注入器 獲取例項化物件

1Python進階強化訓練裝飾使用技巧進階

黃金分割 解決方案 return 技巧 原函數 Python進階強化訓練之裝飾器使用技巧進階如何使用函數裝飾器?實際案例某些時候我們想為多個函數,統一添加某種功能,比如記時統計、記錄日誌、緩存運算結果等等。我們不想在每個函數內一一添加完全相同的代碼,有什麽好的解決方案呢?解決方案定義裝飾奇

python3學習裝飾

python#定義裝飾器,outer參數是函數,返回也是函數 #作用:在函數執行前和執行後分別附加額外功能 def outer(func): def inner(*args, **kwargs): print("log") func(*args, **kwargs)

設計模式入門裝飾模式Decorator

gravity 減少 都是 一個人 額外 不同的 這也 sys 裝飾器模式 //裝飾模式定義:動態地給一個對象加入一些額外的職責。 //就添加功能來說,裝飾模式比生成子類更為靈活 //這也提現了面向對象設計中的一條基本原則,即:盡量使用對象組合。而不是對象繼承//Com

python 裝飾

文本 pre gin 針對 aps 這樣的 ora str string 由於函數也是一個對象,而且函數對象可以被賦值給變量,所以,通過變量也能調用該函數。 >>> def now(): ... print(‘2015-3-25‘) ... >

java設計模式 裝飾模式

rac 都在 通過 div 過濾 一個人 創建 展開 out 裝飾器模式 裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。 這種類型的設計模式屬於結構型模式,它是作為現有的類的一個包裝。 這種模式創建了一個裝飾類,用來包

java設計模式 裝飾模式

食物 implement super map 結束 同時 ring 接口 包裝 適AT java設計模式之 裝飾器模式 裝飾器模式 裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。 這種類型的設計模式

python基礎-函數裝飾、叠代與生成器

內部 class 叠代 code res 裝飾器 div 基礎 foo 1. 函數嵌套 1.1 函數嵌套調用   函數的嵌套調用:在調用一個函數的過程中,又調用了其他函數 def bar(): print("from in the bar.") def foo(

python全棧開發從入門到放棄裝飾函數

def return app 不改變 art sdl 兩個 time() 必須 1、函數名可以當作函數的參數 1 import time 2 def timmer(func): 3 #函數名可以當做函數的參數 4 def inner(): 5

Python學習裝飾進階

放大 python學習 else pen 裝飾 dap style pri aaa 函數知識回顧: 函數的參數分為:實參和形參。 實參:調用函數的時候傳入的參數; 形參:分為3種(位置參數、默認參數、動態傳參) 位置參數:必須傳值 def aaa(a,b): pr

java裝飾模式

args pattern lte auto eight pro 簡單的 add con Decorator Pattern(裝飾器模式),定義:Attach additional responsibilities to an object dynamically. Deco

Selenium2+python-unittest裝飾(@classmethod)

selenium self selenium2 def tex 驗證 drive sts ttr 原文地址:http://www.cnblogs.com/yoyoketang/p/6685416.html 前言 前面講到unittest裏面setUp可以在每次執行用例前執行

Selenium2+python自動化55-unittest裝飾(@classmethod)【轉載】

con 關閉 ext 元素 實現 bdr nav expec 裏的 本篇轉自博客:上海-悠悠 原文地址:http://www.cnblogs.com/yoyoketang/tag/unittest/ 前言 前面講到unittest裏面setUp可以在每次執行用例前執行,這樣

python魔術方法裝飾

裝飾器 描述器 三個魔術方法:__get__()__set__()__delete__()object.__get__(self,實例名,owner) #owner = 屬主 ,instance = 屬主類owner的實例object.__set__(self,實例名,value)object.

python3裝飾

程序 功能 我們 welcom 理解 def python3 繼續 通過 1、裝飾器 裝飾器本質上是一個python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務

Python----裝飾

-s 展示 裝飾 參數 nbsp func start 接下來 pytho 作用:   在不改變函數源代碼的前提下,給函數增加新的功能。 裝飾器1,本質上是一個函數2,功能---裝飾其他函數(為其他函數添加其他功能)3,不能修改被裝飾函數的源代碼4,不能修改被裝飾函數的調用

函數裝飾

python ret from object 全局 rst 技術 print app ---恢復內容開始--- 一。函數名(學名:第一類對象) 函數名本質上就是函數的內存地址。通俗點就是特殊的變量. def my(): print(666)print(my)

Python成長路【第五篇】:Python基礎裝飾

brush urn 新功能 clas 現在 hide rom 接收 調用 一、什麽是裝飾器 裝飾:裝飾既修飾,意指為其他函數添加新功能 器:器既函數 裝飾器定義:本質就是函數,功能是為其他函數添加新功能 二、裝飾器需要遵循的原則 1、不能修改裝飾器的源代碼(開放封閉原則)

Python小程序練習二裝飾小例子

現實 none align style args ldap .net dad 現在 Python小程序練習二之裝飾器小例子 裝飾器: 裝飾器實際上就是為了給某程序增添功能,但該程序已經上線或已經被使用,那麽就不能大批量的修改源代碼,這樣是不科學的也是不現實的