详解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的25个步骤 千倍级效率提升
Feb 11 Javascript
简介JavaScript中substring()方法的使用
Jun 06 Javascript
javascript页面倒计时实例
Jul 25 Javascript
配置Grunt的Task时通配符支持和动态生成文件名问题
Sep 06 Javascript
window.location.reload 刷新使用分析(去对话框)
Nov 11 Javascript
关于在Servelet中如何获取当前时间的操作方法
Jun 28 Javascript
JavaScript队列、优先队列与循环队列
Nov 14 Javascript
实例解析angularjs的filter过滤器
Dec 14 Javascript
jQuery轻松实现无缝轮播效果
Mar 22 jQuery
浅谈node.js 命令行工具(cli)
May 10 Javascript
详解Vue.directive 自定义指令
Mar 27 Javascript
解决vuex数据页面刷新后初始化操作
Jul 26 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
十大感人催泪爱情动漫 第一名至今不忍在看第二遍
2020/03/04 日漫
PHP similar_text 字符串的相似性比较函数
2010/05/26 PHP
使用PHP实现Mysql读写分离
2013/06/28 PHP
深入浅析PHP的session反序列化漏洞问题
2017/06/15 PHP
JavaScript页面刷新与弹出窗口问题的解决方法
2010/03/02 Javascript
通过js获取div的background-image属性
2013/10/15 Javascript
js实现的点击div区域外隐藏div区域
2014/06/30 Javascript
JavaScript 异常处理 详解
2015/02/06 Javascript
javascript获取文档坐标和视口坐标
2015/05/26 Javascript
jQuery页面刷新(局部、全部)问题分析
2016/01/09 Javascript
微信小程序开发经验总结(推荐)
2017/01/11 Javascript
Vue基本使用之对象提供的属性功能
2019/04/30 Javascript
nodejs中实现修改用户路由功能
2019/05/24 NodeJs
ES6中字符串的使用方法扩展
2019/06/04 Javascript
利用python获得时间的实例说明
2013/03/25 Python
在Django框架中设置语言偏好的教程
2015/07/27 Python
pip matplotlib报错equired packages can not be built解决
2018/01/06 Python
基于Python3.6+splinter实现自动抢火车票
2018/09/25 Python
Python装饰器语法糖
2019/01/02 Python
Python批量生成特定尺寸图片及图画任意文字的实例
2019/01/30 Python
django 框架实现的用户注册、登录、退出功能示例
2019/11/28 Python
np.newaxis 实现为 numpy.ndarray(多维数组)增加一个轴
2019/11/30 Python
css3动画 小球滚动 js控制动画暂停
2019/11/29 HTML / CSS
Grid 宫格常用布局的实现
2020/01/10 HTML / CSS
伦敦最著名的老字号百货公司:Selfridges(塞尔福里奇百货)
2016/07/25 全球购物
Too Faced官网:美国知名彩妆品牌
2017/03/07 全球购物
.NET初级开发工程师面试题
2014/04/18 面试题
好家长事迹材料
2014/01/23 职场文书
计算机维护专业推荐信
2014/02/27 职场文书
颂军魂爱军营演讲稿
2014/09/13 职场文书
2014年最新学校运动会广播稿
2014/09/17 职场文书
医院领导班子四风对照检查材料
2014/09/27 职场文书
劳资员岗位职责
2015/02/13 职场文书
win11高清晰音频管理器在哪里?win11找不到高清晰音频管理器解决办法
2022/04/08 数码科技
微软官方消息,在 2023 年 4 月 11 日之后微软将不再为 Office 2013 和 Skype for Business 2015 提供安全更新
2022/04/21 数码科技
Linux中sftp常用命令整理
2022/06/28 Servers