详解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 相关文章推荐
javascript 年月日联动实现核心代码
Dec 21 Javascript
一个简单的js渐显(fadeIn)渐隐(fadeOut)类
Jun 19 Javascript
颜色选择器 Color Picker,IE,Firefox,Opera,Safar
Nov 25 Javascript
JavaScript操作XML 使用百度RSS作为新闻源示例
Feb 17 Javascript
JS实现可调整倒计时间代码分享
Aug 18 Javascript
jQuery实现的Tab滑动选项卡及图片切换(多种效果)小结
Sep 14 Javascript
bootstrap警告框使用方法解析
Jan 13 Javascript
JavaScript ES6箭头函数使用指南
Dec 30 Javascript
微信小程序websocket实现即时聊天功能
May 21 Javascript
Vue+Typescript中在Vue上挂载axios使用时报错问题
Aug 07 Javascript
微信小程序实现聊天室
Aug 21 Javascript
vue+elementUI实现表格列的显示与隐藏
Apr 13 Vue.js
详解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
使用GROUP BY的时候如何统计记录条数 COUNT(*) DISTINCT
2011/04/23 PHP
php操作xml入门之xml标签的属性分析
2015/01/23 PHP
PHP接口类(interface)的定义、特点和应用示例
2020/05/18 PHP
PHP内存溢出优化代码详解
2021/02/26 PHP
js跨域和ajax 跨域问题的实现思路
2009/09/05 Javascript
关于innerHTML后丢失动态绑定的EVENT问题解决方法
2013/05/19 Javascript
jquery实现显示已选用户
2014/07/21 Javascript
js事件监听机制(事件捕获)总结
2014/08/08 Javascript
javascript获取四位数字或者字母的随机数
2015/01/09 Javascript
javaScript给元素添加多个class的简单实现
2016/07/20 Javascript
聊一聊JS中的prototype
2016/09/29 Javascript
微信小程序 chooseImage选择图片或者拍照
2017/04/07 Javascript
详解Angular 4.x NgIf 的用法
2017/05/22 Javascript
angularjs实现table增加tr的方法
2018/02/27 Javascript
JS实现字符串去重及数组去重的方法示例
2018/04/21 Javascript
[42:39]老党炸弹人试玩视频
2014/09/03 DOTA
[51:17]VGJ.T vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
Python生成短uuid的方法实例详解
2018/05/29 Python
python3去掉string中的标点符号方法
2019/01/22 Python
python如何以表格形式打印输出的方法示例
2019/06/21 Python
对Django 中request.get和request.post的区别详解
2019/08/12 Python
Django2 连接MySQL及model测试实例分析
2019/12/10 Python
基于Python实现扑克牌面试题
2019/12/11 Python
pytorch ImageFolder的覆写实例
2020/02/20 Python
python-sys.stdout作为默认函数参数的实现
2020/02/21 Python
HTML5 Web存储方式的localStorage和sessionStorage进行数据本地存储案例应用
2012/12/09 HTML / CSS
面向对象设计的原则是什么
2013/02/13 面试题
党建示范点实施方案
2014/03/12 职场文书
预备党员的自我评价
2014/03/12 职场文书
2014年群众路线党员自我评议
2014/09/24 职场文书
社保代办委托书怎么写
2014/10/06 职场文书
创先争优宣传标语
2014/10/08 职场文书
见习报告的格式
2014/10/31 职场文书
辣妈辣妹观后感
2015/06/10 职场文书
从事会计工作年限证明
2015/06/23 职场文书
高中生社会实践心得体会
2016/01/14 职场文书