您的位置:首页 > Web前端 > AngularJS

Angular2 依赖注入之装饰器

2017-04-20 21:31 357 查看
下面会从 Angular 的源码分析依赖注入中的的装饰器,基于 angular@4.1.0-beta.1

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 中的实现了…



这样就可以将一个对象 (这里是 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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息