详解Angular 4.x Injector


Posted in Javascript onMay 04, 2017

在介绍 Angular Injector (注入器) 之前,我们先要了解 Dependency Injection,即依赖注入的概念。

依赖注入允许程序设计遵从依赖倒置原则 (简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户端与实现模块间的耦合) 调用者只需知道服务的接口,具体服务的查找和创建由注入器 (Injector) 负责处理并提供给调用者,这样就分离了服务和调用者的依赖,符合低耦合的程序设计原则。

从上述的内容可知,依赖注入中包含三种角色:调用者、服务和注入器 (Injector)。现在我们开始介绍 Injector,在 Angular 中 Injector (注入器) 用来管理服务对象的创建和获取。接下来我们先来看一下 Injector 抽象类:

Injector 抽象类

// angular2\packages\core\src\di\injector.ts
export abstract class Injector {
 static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
 static NULL: Injector = new _NullInjector();

 /**
 * 用于根据给定的Token从注入器中获取相应的对象。
 * 如果没有找到相应的对象,将返回notFoundValue设置的值。若notFoundValue的值与
 * _THROW_IF_NOT_FOUND相等,则会抛出异常。
 */
 abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
}

const _THROW_IF_NOT_FOUND = new Object();

Injector 抽象类中定义了一个 get() 抽象方法,该方法用于根据给定的 Token 从注入器中获取相应的对象,每个Injector 抽象类的子类都必须实现该方法。在 Angular 中常见的 Injector 抽象类子类有:

  1. _NullInjector
  2. ReflectiveInjector

下面我们来依次介绍它们:

_NullInjector 类

_NullInjector 类的实例用于表示空的注入器。

// angular2\packages\core\src\di\injector.ts
class _NullInjector implements Injector {
 get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
 if (notFoundValue === _THROW_IF_NOT_FOUND) {
  throw new Error(`No provider for ${stringify(token)}!`);
 }
 return notFoundValue;
 }
}

ReflectiveInjector 抽象类

ReflectiveInjector 表示一个依赖注入容器,用于实例化对象和解析依赖。

ReflectiveInjector 使用示例

@Injectable()
class Engine {}

@Injectable()
class Car {
 constructor(public engine:Engine) {}
}

var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
var car = injector.get(Car);
expect(car instanceof Car).toBe(true);
expect(car.engine instanceof Engine).toBe(true);

上面示例中,我们通过调用 ReflectiveInjector 抽象类的 resolveAndCreate() 方法,创建注入器。然后通过调用注入器的 get() 方法,获取 Token 对应的对象。该抽象类除了 resolveAndCreate() 静态方法外,还含有以下静态方法:

  1. resolve() - 解析 Provider 列表为 ResolvedReflectiveProvider 列表
  2. fromResolvedProviders() - 基于 ResolvedReflectiveProvider 列表创建 ReflectiveInjector 对象

接下来我们来分析上述的静态方法:

resolveAndCreate()

static resolveAndCreate(providers: Provider[], parent?: Injector): ReflectiveInjector {
 const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
 return ReflectiveInjector.fromResolvedProviders(ResolvedReflectiveProviders, parent);
}

从上面代码中,我们可以看出 resolveAndCreate() 方法内部是通过调用 ReflectiveInjector.resolve() 方法和 ReflectiveInjector.fromResolvedProviders() 方法来创建 ReflectiveInjector 对象。

resolve()

该方法用于把 Provider 数组解析为 ResolvedReflectiveProvider 数组。

static resolve(providers: Provider[]): ResolvedReflectiveProvider[] {
 return resolveReflectiveProviders(providers);
}

resolve() 使用示例

@Injectable()
class Engine {}

@Injectable()
class Car {
 constructor(public engine:Engine) {}
}

var providers = ReflectiveInjector.resolve([Car, [[Engine]]]);
expect(providers.length).toEqual(2);

expect(providers[0] instanceof ResolvedReflectiveProvider).toBe(true);
expect(providers[0].key.displayName).toBe("Car");
expect(providers[1].key.displayName).toBe("Engine");

resolve() 解析图示

详解Angular 4.x Injector 

Provider 类型

export type Provider =
 TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];

// ApiService
export interface TypeProvider extends Type<any> {}

// { provide: ApiService, useClass: ApiService } 
export interface ClassProvider {
 // 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken的实例或字符串
 provide: any; 
 useClass: Type<any>;
 // 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
 multi?: boolean; 
}
 
// { provide: 'API_URL', useValue: 'http://my.api.com/v1' }
export interface ValueProvider {
 provide: any;
 useValue: any;
 multi?: boolean;
}
 
// { provide: 'ApiServiceAlias', useExisting: ApiService } 
export interface ExistingProvider {
 provide: any;
 useExisting: any;
 multi?: boolean;
}
 
// { provide: APP_INITIALIZER, useFactory: configFactory, deps: [AppConfig], multi: true }
export interface FactoryProvider {
 provide: any;
 useFactory: Function;
 deps?: any[]; // 用于设置工厂函数的依赖对象
 multi?: boolean;
}

ResolvedReflectiveProvider 接口

export interface ResolvedReflectiveProvider {
 // 唯一的对象用来从ReflectiveInjector中获取对象
 key: ReflectiveKey;
 // 工厂函数用于创建key相关的依赖对象 
 resolvedFactories: ResolvedReflectiveFactory[];
 // 标识当前的provider是否为multi-provider
 multiProvider: boolean;
}

ResolvedReflectiveFactory 类

export class ResolvedReflectiveFactory {
 constructor(
  public factory: Function,
  public dependencies: ReflectiveDependency[]) {}
}

ReflectiveDependency 类

export class ReflectiveDependency {
 constructor(
  public key: ReflectiveKey, 
  public optional: boolean, 
  public visibility: Self|SkipSelf|null) {}

 static fromKey(key: ReflectiveKey): ReflectiveDependency {
 return new ReflectiveDependency(key, false, null);
 }
}

ReflectiveKey 类

ReflectiveKey 对象中包含两个属性:系统范围内唯一的id 和 token。系统范围内唯一的id,允许注入器以更高效的方式存储已创建的对象。另外我们不能手动的创建 ReflectiveKey,当 ReflectiveInjector 对象解析 providers 的时候会自动创建 ReflectiveKey 对象。

export class ReflectiveKey {
 constructor(public token: Object, public id: number) {
 if (!token) {
  throw new Error('Token must be defined!');
 }
 }
 
 // 返回序列化的token
 get displayName(): string { return stringify(this.token); }

 // 获取token对应的ReflectiveKey
 static get(token: Object): ReflectiveKey {
 return _globalKeyRegistry.get(resolveForwardRef(token));
 }

 // 获取系统中已注册ReflectiveKey的个数
 static get numberOfKeys(): number { return _globalKeyRegistry.numberOfKeys; }
}

const _globalKeyRegistry = new KeyRegistry(); // 创建Key仓库

export class KeyRegistry {
 private _allKeys = new Map<Object, ReflectiveKey>();

 /**
 * 若token是ReflectiveKey类的实例,则直接返回。若_allKeys对象中包含token属性
 * 则返回token对应的ReflectiveKey对象。否则创建一个新的ReflectiveKey对象,并
 * 保存到_allKeys对象中
 */
 get(token: Object): ReflectiveKey {
 if (token instanceof ReflectiveKey) return token;

 if (this._allKeys.has(token)) {
  return this._allKeys.get(token) !;
 }

 const newKey = new ReflectiveKey(token, ReflectiveKey.numberOfKeys);
 this._allKeys.set(token, newKey);
 return newKey;
 }

 // 获取已保存ReflectiveKey的个数
 get numberOfKeys(): number { return this._allKeys.size; }
}

分析完 resolve() 方法的输入参数和返回类型,我们来看一下该方法内部的具体实现:

export function resolveReflectiveProviders(providers: Provider[])
 : ResolvedReflectiveProvider[] {
  const normalized = _normalizeProviders(providers, []); // 步骤一
  const resolved = normalized.map(resolveReflectiveProvider); // 步骤二
  const resolvedProviderMap = mergeResolvedReflectiveProviders(resolved, new Map()); // 步骤三
  return Array.from(resolvedProviderMap.values()); // 步骤四
}

步骤一 —— 规范化Provider

const normalized = _normalizeProviders(providers, []);

// 规范化Providers
function _normalizeProviders(providers: Provider[], res: Provider[]): Provider[] {
 providers.forEach(b => {
  // providers: [Type] => providers: [{provide: Type, useClass: Type }]
  if (b instanceof Type) { 
   res.push({provide: b, useClass: b});
  } else if (b && typeof b == 'object' && (b as any).provide !== undefined) {
   res.push(b as NormalizedProvider);
  } else if (b instanceof Array) { // 若b是数组,则递归调用_normalizeProviders()方法
   _normalizeProviders(b, res);
  } else {
   throw invalidProviderError(b);
  }
 });
 return res;
}

interface NormalizedProvider extends TypeProvider, ValueProvider, ClassProvider, 
  ExistingProvider, FactoryProvider {}

步骤二 —— 转化NormalizedProvider为ResolvedReflectiveProvider

const resolved = normalized.map(resolveReflectiveProvider);

// 解析NormalizedProvider为ResolvedReflectiveProvider
function resolveReflectiveProvider(provider: NormalizedProvider): ResolvedReflectiveProvider {
 return new ResolvedReflectiveProvider_(
   ReflectiveKey.get(provider.provide), [resolveReflectiveFactory(provider)],
   provider.multi || false);
}

// 用于创建已解析的Provider实例
export class ResolvedReflectiveProvider_ implements ResolvedReflectiveProvider {
 constructor(
   public key: ReflectiveKey, 
   public resolvedFactories: ResolvedReflectiveFactory[],
   public multiProvider: boolean) {}

 get resolvedFactory(): ResolvedReflectiveFactory { return this.resolvedFactories[0]; }
}

// 解析NormalizedProvider对象,创建ResolvedReflectiveFactory对象
function resolveReflectiveFactory(provider: NormalizedProvider): ResolvedReflectiveFactory {
 let factoryFn: Function;
 let resolvedDeps: ReflectiveDependency[];
 if (provider.useClass) {
  // { provide: ApiService, useClass: ApiService } 
  const useClass = resolveForwardRef(provider.useClass);
  factoryFn = reflector.factory(useClass);
  resolvedDeps = _dependenciesFor(useClass);
 } else if (provider.useExisting) {
  // { provide: 'ApiServiceAlias', useExisting: ApiService } 
  factoryFn = (aliasInstance: any) => aliasInstance;
  resolvedDeps = [ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting))];
 } else if (provider.useFactory) {
  // { provide: APP_INITIALIZER, useFactory: configFactory, deps: [AppConfig], 
  //   multi: true }
  factoryFn = provider.useFactory;
  resolvedDeps = constructDependencies(provider.useFactory, provider.deps);
 } else {
  // { provide: 'API_URL', useValue: 'http://my.api.com/v1' }
  factoryFn = () => provider.useValue;
  // const _EMPTY_LIST: any[] = [];
  resolvedDeps = _EMPTY_LIST;
 }
 return new ResolvedReflectiveFactory(factoryFn, resolvedDeps);
}

步骤三 —— 合并已解析的Provider

const resolvedProviderMap = mergeResolvedReflectiveProviders(resolved, new Map());

export function mergeResolvedReflectiveProviders(
  providers: ResolvedReflectiveProvider[],
  normalizedProvidersMap: Map<number, ResolvedReflectiveProvider>):
  Map<number, ResolvedReflectiveProvider> {
  for (let i = 0; i < providers.length; i++) {
  const provider = providers[i];
   // 从normalizedProvidersMap对象中获取key.id对应的ResolvedReflectiveProvider对象
  const existing = normalizedProvidersMap.get(provider.key.id);
  if (existing) {
    // 如果当前的provider不是multi provider,则抛出异常
   if (provider.multiProvider !== existing.multiProvider) {
    throw mixingMultiProvidersWithRegularProvidersError(existing, provider);
   }
   // 如果当前的provider是multi provider,则把当前provider的resolvedFactories
   // 列表中的每一项添加到已存在的provider对象的resolvedFactories列表中。
   if (provider.multiProvider) {
    for (let j = 0; j < provider.resolvedFactories.length; j++) {
     existing.resolvedFactories.push(provider.resolvedFactories[j]);
    }
   } else { 
    // 如果当前的provider不是multi provider,则覆盖已存在的provider
    normalizedProvidersMap.set(provider.key.id, provider);
   }
  } else {
   let resolvedProvider: ResolvedReflectiveProvider;
   // 如果当前的provider是multi provider,则创建一个新的ResolvedReflectiveProvider对象
   if (provider.multiProvider) {
    resolvedProvider = new ResolvedReflectiveProvider_(
      provider.key, provider.resolvedFactories.slice(), provider.multiProvider);
   } else {
    resolvedProvider = provider;
   }
   // 在normalizedProvidersMap中保存已解析的ResolvedReflectiveProvider对象
   normalizedProvidersMap.set(provider.key.id, resolvedProvider);
  }
 }
 return normalizedProvidersMap;
}

步骤四 —— 生成ResolvedReflectiveProvider[]

// resolvedProviderMap的values,创建ResolvedReflectiveProvider[]
Array.from(resolvedProviderMap.values());

/**
 * 基于一个类似数组或可迭代对象创建一个新的数组实例
 * 
 * arrayLike:转换成真实数组的类数组对象或可遍历对象。
 * mapFn(可选):如果指定了该参数,则最后生成的数组会经过该函数的加工处理后再返回。
 * thisArg(可选):执行mapFn函数时this的值。
 */
Array.from(arrayLike[, mapFn[, thisArg]])

fromResolvedProviders()

该方法用于基于已解析的 providers 创建注入器。

static fromResolvedProviders(providers: ResolvedReflectiveProvider[], parent?: Injector):
 ReflectiveInjector {
  return new ReflectiveInjector_(providers, parent);
}

fromResolvedProviders() 使用示例

@Injectable()
class Engine {}

@Injectable()
class Car {
 constructor(public engine:Engine) {}
}

var providers = ReflectiveInjector.resolve([Car, Engine]);
var injector = ReflectiveInjector.fromResolvedProviders(providers);
expect(injector.get(Car) instanceof Car).toBe(true);

了解完 fromResolvedProviders() 方法的使用方式,接下来我们来重点分析一下 ReflectiveInjector_ 类。

ReflectiveInjector_ 类

ReflectiveInjector_ 类的属性

// 构造次数
_constructionCounter: number = 0;

// ResolvedReflectiveProvider列表
 public _providers: ResolvedReflectiveProvider[];

// 父级注入器
 public _parent: Injector|null;

// ReflectiveKey id列表
 keyIds: number[];

// 依赖对象列表
 objs: any[];

ReflectiveInjector_ 构造函数

export class ReflectiveInjector_ implements ReflectiveInjector {
 constructor(_providers: ResolvedReflectiveProvider[], _parent?: Injector) {
   this._providers = _providers;
   // 设置父级注入器
   this._parent = _parent || null;

   const len = _providers.length;

   this.keyIds = new Array(len);
   this.objs = new Array(len);

   // 初始化keyIds列表和objs对象列表
   for (let i = 0; i < len; i++) {
    this.keyIds[i] = _providers[i].key.id;
    this.objs[i] = UNDEFINED;
   }
 }
}

const UNDEFINED = new Object();

ReflectiveInjector_ 类的方法

ReflectiveInjector_ 类中的方法较多,我们只分析其中比较重要的方法,首先先根据方法的实现的功能进行分类:

  1. 用于创建ReflectiveInjector注入器
  2. 用于获取对象
  3. 用于创建对象
  4. 用于获取工厂函数依赖对象

用于创建ReflectiveInjector注入器

// 基于Provider列表并创建子注入器
resolveAndCreateChild(providers: Provider[]): ReflectiveInjector {
  const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
  return this.createChildFromResolved(ResolvedReflectiveProviders);
}

// 基于已解析的ResolvedReflectiveProvider列表,创建子注入器
createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector {
  const inj = new ReflectiveInjector_(providers);
  inj._parent = this;
  return inj;
}

用于获取对象

// 获取当前注入器的父级注入器
get parent(): Injector|null { return this._parent; }

// 获取token对应的依赖对象
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
  return this._getByKey(ReflectiveKey.get(token), null, notFoundValue);
}

// 根据ReflectiveKey及visibility可见性,获取对应的依赖对象
private _getByKey(key: ReflectiveKey, visibility: Self|SkipSelf|null, notFoundValue: any): any {
  // const INJECTOR_KEY = ReflectiveKey.get(Injector); 
  if (key === INJECTOR_KEY) {
   return this;
  }

  // 判断该依赖对象是否使用@Self装饰器定义,表示从本级注入器获取依赖对象
  if (visibility instanceof Self) {
   return this._getByKeySelf(key, notFoundValue);

  } else {
   // 使用默认的方式获取依赖对象
   return this._getByKeyDefault(key, notFoundValue, visibility);
  }
}

// 从本级注入器获取依赖对象
 _getByKeySelf(key: ReflectiveKey, notFoundValue: any): any {
  const obj = this._getObjByKeyId(key.id);
  return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, notFoundValue);
}

// 使用默认的方式获取依赖对象
_getByKeyDefault(key: ReflectiveKey, notFoundValue: any, 
  visibility: Self|SkipSelf|null): any {
  let inj: Injector|null;

  // 判断该依赖对象是否使用@SkipSelf装饰器定义,表示不从本级注入器获取依赖对象
  if (visibility instanceof SkipSelf) {
   inj = this._parent;
  } else {
   inj = this;
  }

  // 从本级注入器获取依赖对象,若本级获取不到,则从父级注入器中查找
  while (inj instanceof ReflectiveInjector_) {
   const inj_ = <ReflectiveInjector_>inj;
   const obj = inj_._getObjByKeyId(key.id);
   if (obj !== UNDEFINED) return obj;
   inj = inj_._parent;
  }
  if (inj !== null) {
   return inj.get(key.token, notFoundValue);
  } else {
   return this._throwOrNull(key, notFoundValue);
  }
}

// 获取keyId对应的对象,如依赖对象未创建,则调用_new()方法创建一个,然后保存到
// this.objs对象列表中
private _getObjByKeyId(keyId: number): any {
  for (let i = 0; i < this.keyIds.length; i++) {
   if (this.keyIds[i] === keyId) {
    // const UNDEFINED = new Object();
    if (this.objs[i] === UNDEFINED) {
     this.objs[i] = this._new(this._providers[i]);
    }
    return this.objs[i];
   }
  }
  return UNDEFINED;
}

用于创建对象

// 创建依赖对象
_new(provider: ResolvedReflectiveProvider): any {
  // 判断是否存在循环依赖
  if (this._constructionCounter++ > this._getMaxNumberOfObjects()) {
   throw cyclicDependencyError(this, provider.key);
  }
  return this._instantiateProvider(provider);
}

// 获取最大的对象个数
private _getMaxNumberOfObjects(): number { return this.objs.length; }

// 根据已解析的provider创建依赖对象。若是multi provider则,循环创建multi provider对象。 
private _instantiateProvider(provider: ResolvedReflectiveProvider): any {
  if (provider.multiProvider) {
   const res = new Array(provider.resolvedFactories.length);
   for (let i = 0; i < provider.resolvedFactories.length; ++i) {
    res[i] = this._instantiate(provider, provider.resolvedFactories[i]);
   }
   return res;
  } else {
   return this._instantiate(provider, provider.resolvedFactories[0]);
  }
}

// 根据已解析的provider和已解析的工厂创建依赖对象
private _instantiate(
   provider: ResolvedReflectiveProvider,
   ResolvedReflectiveFactory: ResolvedReflectiveFactory): any {
  // 获取对象工厂函数
  const factory = ResolvedReflectiveFactory.factory;

  // 获取工厂函数所依赖的对象列表
  let deps: any[];
  try {
   deps = ResolvedReflectiveFactory.dependencies
       .map(dep => this._getByReflectiveDependency(dep));
  } catch (e) {
   if (e.addKey) {
    e.addKey(this, provider.key);
   }
   throw e;
  }

  // 调用对象工厂函数创建依赖对象
  let obj: any;
  try {
   obj = factory(...deps);
  } catch (e) {
   throw instantiationError(this, e, e.stack, provider.key);
  }
  return obj;
 }

用于获取工厂函数依赖对象

// 若通过@Optional装饰器定义该依赖对象,表示该依赖对象是可选的,当获取不到时返回null。
private _getByReflectiveDependency(dep: ReflectiveDependency): any {
  return this._getByKey(dep.key, dep.visibility, dep.optional ? null : THROW_IF_NOT_FOUND);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于JQuery的一句话搞定手风琴菜单
Sep 14 Javascript
javascript读写XML实现广告轮换(兼容IE、FF)
Aug 09 Javascript
jquery操作下拉列表、文本框、复选框、单选框集合(收藏)
Jan 08 Javascript
深入剖析JavaScript中的枚举功能
Mar 06 Javascript
jQuery点击输入框显示验证码图片
May 19 Javascript
bootstrap实现每隔5秒自动轮播效果
Dec 20 Javascript
JS去除字符串中空格的方法
Feb 14 Javascript
在bootstrap中实现轮播图实例代码
Jun 11 Javascript
微信小程序收藏功能的实现代码
Jun 12 Javascript
微信小程序实现自定义picker选择器弹窗内容
May 26 Javascript
微信小程序实现星级评价效果
Dec 28 Javascript
JS实现页面侧边栏效果探究
Jan 08 Javascript
详解Vue中使用v-for语句抛出错误的解决方案
May 04 #Javascript
详解Node.js串行化流程控制
May 04 #Javascript
纯原生js实现贪吃蛇游戏
Apr 16 #Javascript
js调用刷新界面的几种方式
May 03 #Javascript
JavaScript中双向数据绑定详解
May 03 #Javascript
Js实现中国公民身份证号码有效性验证实例代码
May 03 #Javascript
Vue原理剖析 实现双向绑定MVVM
May 03 #Javascript
You might like
php IP及IP段进行访问限制的代码
2008/12/17 PHP
php 获取本地IP代码
2013/06/23 PHP
CI(CodeIgniter)模型用法实例分析
2016/01/20 PHP
juqery 学习之三 选择器 层级 基本
2010/11/25 Javascript
DOM_window对象属性之--clipboardData对象操作代码
2011/02/03 Javascript
jQuery实现用方向键控制层的上下左右移动
2013/01/13 Javascript
js修改input的type属性问题探讨
2013/10/12 Javascript
AngularJS iframe跨域打开内容时报错误的解决办法
2015/01/26 Javascript
JS实现网页游戏中滑块响应鼠标点击移动效果
2015/10/19 Javascript
全面了解JavaScript对象进阶
2016/07/19 Javascript
angularJS之$http:与服务器交互示例
2017/03/17 Javascript
package.json文件配置详解
2017/06/15 Javascript
微信小程序class封装http代码实例
2019/08/24 Javascript
基于vue实现图片验证码倒计时60s功能
2019/12/10 Javascript
js如何验证密码强度
2020/03/18 Javascript
微信小程序仿通讯录功能
2020/04/09 Javascript
vue 子组件watch监听不到prop的解决
2020/08/09 Javascript
python获取当前时间对应unix时间戳的方法
2015/05/15 Python
Python基于Tkinter实现的记事本实例
2015/06/17 Python
Python中subprocess的简单使用示例
2015/07/28 Python
浅谈python 四种数值类型(int,long,float,complex)
2016/06/08 Python
python正则表达式之对号入座篇
2018/07/24 Python
python组合无重复三位数的实例
2018/11/13 Python
python实现多进程通信实例分析
2019/09/01 Python
python实现WebSocket服务端过程解析
2019/10/18 Python
Python urlencode和unquote函数使用实例解析
2020/03/31 Python
详解Python中的路径问题
2020/09/02 Python
利用CSS3实现动态的二级三级菜单效果实例源码
2017/01/04 HTML / CSS
毕业生多媒体设计求职信
2013/10/12 职场文书
写自荐信有哪些不宜?
2013/10/17 职场文书
感恩教育月活动总结
2014/07/07 职场文书
基层党员对照检查材料
2014/08/25 职场文书
合理化建议书
2015/02/04 职场文书
尼克胡哲观后感
2015/06/08 职场文书
2015初中团委工作总结
2015/07/28 职场文书
2019年工作总结范文
2019/05/21 职场文书