牛顿曾说:如果说我看得比他人更远些,那是因为我站在伟人的肩膀上。

浏览优质框架库的源码,能学到不少,更有甚者基于此发明了更优良的,大抵就是如此。

midwayjs 曾经忘了是怎么意识它的了。印象中是个 nodejs 的优质框架,看了介绍,很厉害!!!

Midway - 一个面向未来的云端一体 Node.js 框架

<img src="/img/bVcR2g3" alt="midwayjs">

好不好用,用过才晓得。一边浏览应用文档,一边建个本地我的项目感受一下。用着真的是很“高效”呢。

midway 外围 ”依赖注入“ 代码写法

默认应用 egg 作为下层框架(反对是 express, koa),这么创立我的项目:

$ npm -v# 如果是 npm v6$ npm init midway --type=web hello_koa# 如果是 npm v7$ npm init midway -- --type=web hello_koa

应用:

controller/api.ts

import { Inject, Controller, Get, Provide, Query } from '@midwayjs/decorator';import { Context } from 'egg';import { UserService } from '../service/user';@Provide()@Controller('/api')export class APIController {  @Inject()  ctx: Context;  @Inject()  userService: UserService;  @Get('/get_user')  async getUser(@Query() uid) {    const user = await this.userService.getUser({ uid });    return { success: true, message: 'OK', data: user };  }}

service/user.js

import { Provide } from '@midwayjs/decorator';import { IUserOptions } from '../interface';@Provide()export class UserService {  async getUser(options: IUserOptions) {    return {      uid: options.uid,      username: 'mockedName',      phone: '12345678901',      email: 'xxx.xxx@xxx.com',    };  }}

书写 node 我的项目,controller、service、router 这些都必不可少。然而当初只须要向下面那样,即可。很疾速的就定义好一个get申请了:localhost:7001/api/get_user。不再须要解决 router、controller、service 间接的绑定映射,也不须要初始化这个 Class(new),而后将实例放在须要调用的中央。

轻轻通知你,以下代码(下面的代码革新),只有在src目录下,不论文件是什么名字,都能够通过 localhost:7001/api/get_user 拜访到呢。

import { Inject, Controller, Get, Provide, Query } from '@midwayjs/decorator';@Provide()export class UserService {  async getUser(options) {    return {      uid: options.uid,      username: 'mockedName',      phone: '123456789022',      email: 'xxx.xxx@xxx.com',    };  }}@Provide()@Controller('/api')export class APIController {  @Inject()  userService: UserService;  @Get('/get_user')  async getUser(@Query() uid: string) {    const user = await this.userService.getUser({ uid });    return { success: true, message: 'OK', data: user };  }}

很神奇吧。外表上看和平时写的代码不一样的中央,就只有装璜器:@Controller@Get@Provide,@Inject。那么是这些装璜器背地做了什么,比方轻轻实例化并进行了实例的绑定?

持续浏览文档,晓得是通过依赖注入实现的。依赖注入装璜器和规定如下:

依赖注入装璜器作用:

@Provide 装璜器的作用:

  1. 这个 Class,被依赖注入容器托管,会主动被实例化(new)
  2. 这个 Class,能够被其余在容器中的 Class 注入

而对应的 @Inject 装璜器,作用为:

  1. 在依赖注入容器中,找到对应的属性名,并赋值为对应的实例化对象

@Provide 和 @Inject 装璜器是有参数的,并且他们是成对呈现。
@Inject 的类中,必须有 @Provide 才会失效。

依赖注入约定:

@Provide@Inject 装璜器是有可选参数的,并且他们是成对呈现。

默认状况下:

  1. @Provide 取 类名的驼峰字符串 作为依赖注入标识符
  2. @Inject 依据 规定 获取 key

规定如下:

  1. 如果装璜器蕴含参数,则以 参数字符串 作为 key
  2. 如果没有参数,标注的 TS 类型为 Class,则将类 @Provide 的 key 作为 key
  3. 如果没有参数,标注的 TS 类型为非 Class,则将 属性名 作为 key

依赖注入的代码写法,能缩小不少代码量,日常开发十分高效。那么依赖注入是如何实现的呢?值得摸索一下!

依赖注入原理。文章中提供了一篇扩大浏览的文章:[这一次,教你从零开始写一个 IoC 容器](https://mp.weixin.qq.com/s/g0...

看了下面的文档,大抵是:创立个容器,在适合的机会,扫描文件,收集有@Provide的类,在@Inject的中央进行实例化绑定。

了解依赖注入

看之前,了解下:

依赖注入解决的问题是:解耦。

<img src="/img/bVcR2g4" alt="ioc">

(图是用excalidraw画的,简略的图用着还是挺不便的)

如图所示,思考下:此时若C实例化须要一个参数,则须要从A始终传递到C,造成了强耦合。而借助了 IOC 思维后,就可解耦,升高依赖。

思维就是:间接传递对象,对象的属性和办法的更改,对象的所有操作外部本人消化。内部要改对象,必须调用对象提供的办法。函数传参的时候就是这么写的。

传递的对象,如果是类,那就是实例化后的对象。在须要应用的中央能通过对象间接拜访到。如果应用的也是类,能够再将实例化的对象绑定一次。

这个过程能够有不同的实现计划:

比方 midwayjs 是通过 @Provide、@Inject 先标注模块之间的依赖关系,再通过加载程序的时候扫描 @Provide 收集模块(要用的模块A,被应用的模块B)最初通过 @Inject 将模块B实例化后绑定到模块A上,模块A就能够间接应用了。

比方 koa 的 use 就是绑定插件到app上,app就能够间接应用插件了,插件的操作本人消化。可浏览从前端中的IOC理念了解koa中的app.use()

过程中须要治理收集到的模块,就会波及到容器,用容器收集到一块,方便管理,也不便读写。

midway 依赖注入局部源码解读

偶合之下,搜文档midwayjs文档,找到了之前版本的 midwayjs,看到外面对于依赖注入的阐明:默认应用 injection 这个包来做依赖注入, 这个包也是 MidwayJs 团队依据业界已有的实现而产出的自研产品,它除了常见的依赖了注入之外,还满足了 Midway 本身的一些非凡需要。强烈推荐浏览文档了解。

一开始应该是这样设计的,前面把它融入到 midwayjs(2.x) 中了。大部分代码都是统一的,外围是一样的。大部分代码文件比对了解如下:

  1. midwayjs/packages/core/src/context/ vs injection/src/ 的ioc容器

ioc容器是实现依赖注入的要害。

IoC 容器就像是一个对象池,治理着每个对象实例的信息(Class Definition),所以用户无需关怀什么时候创立,当用户心愿拿到对象的实例 (Object Instance) 时,能够间接拿到依赖对象的实例,容器会 主动将所有依赖的对象都主动实例化。次要有以下几种,别离解决不同的逻辑。

  • AppliationContext 根底容器,提供了根底的减少定义和依据定义获取对象实例的能力
  • MidwayContainer 用的最多的容器,做了下层封装,通过 bind 函数可能不便的生成类定义,midway 从此类开始扩大
  • RequestContext 用于申请链路上的容器,会主动销毁对象并依赖另一个容器创立实例。

其中ApplicationContext是基类,而MidwayContainerRequestContext继承于它。

  1. midwayjs/packages/core/src/definitions vs injection/src/base
    依赖注入的外围实现,加载对象的class,同步、异步创建对象实例化,对象的属性绑定等。
  2. midwayjs/packages/decorator/src/annotation vs injection/src/annotation
    蕴含装璜器 provide.ts、inject.ts 的实现,在midwayjs中是有一个装璜器治理类DecoratorManager, 用来治理midwayjs的所有装璜器。

@provide() 的作用是简化绑定,能被 IoC 容器主动扫描,并绑定定义到容器上,对应的逻辑是 绑定对象定义(ObjectDefinition.ts)。

@inject() 的作用是将容器中的定义实例化成一个对象,并且绑定到属性中,这样,在调用的时候就能够拜访到该属性。

留神,注入的机会为结构器(new)之后,所以在构造方法(constructor)中是无奈获取注入的属性的,如果要获取注入的内容,能够应用 结构器注入

父类的属性应用 @inject() 装璜器装璜,子类实例会失去装璜后的属性。

其中查找类的原型应用 reflect-metadata 仓库的 OrdinaryGetPrototypeOf 办法,应用 recursiveGetPrototypeOf 办法以数组模式返回该类的所有原型。

function recursiveGetPrototypeOf(target: any): any[] {  const properties = [];  let parent = ordinaryGetPrototypeOf(target);  while (parent !== null) {    properties.push(parent);    parent = ordinaryGetPrototypeOf(parent);  }  return properties;}
  1. mideayjs/packages/core/src/context/managedResolverFactory.ts vs injection/src/factory/common/managedResolverFactory.ts
    次要定义了一个解析工厂类:ManagedResolverFactory,用来创建对象(同步create和异步createAsync),解析对象的参数,生命周期钩子事件,如创立单例初始化完结事件,遍历依赖树判断是否循环依赖。

其余阐明:

  1. 基准测试

injection 的基准测试是用 inversify 这个比拟驰名的 ioc 容器库做测试的。而前面的 midwayjs 中曾经放弃了,间接用它本人的逻辑。

  1. 作用域:

Singleton 单例,全局惟一(过程级别)
Request 默认,申请作用域,生命周期随着申请链路,在申请链路上惟一,申请完结立刻销毁
Prototype 原型作用域,每次调用都会反复创立一个新的对象。

在这三种作用域中,midway 的默认作用域为 申请作用域

基于 TypeScript 的管制反转、依赖注入了解及简略实现

了解了原理,也看了源码,实现个简略的。

只有能实现后能像利用 injection 解耦的案例一样,能通过c.a获取到类A的属性和办法,就示意示意根本实现了依赖注入。

// 应用 IoCimport {Container} from 'injection';import {A} from './A';import {B} from './B';const container = new Container();container.bind(A);container.bind(B);class C {  constructor() {    this.a = container.get('a');    this.b = container.get('b');  }}

补充下前置常识,Reflect-metadata

  • es7:reflect
  • github:reflect-metadata

Reflect Metadata是ES7的一项提案,次要用于在申明阶段增加和读取元数据,TypeScript 1.5+反对该性能。

元数据能够被视为无关类和类的某些属性的描述性信息,实质上不会影响类的行为,然而你能够设置一些预约义的数据到类,并依据元数据对类进行某些操作。

Reflect Metadata的用法非常简单,首先须要装置该 reflect-metadata 库:

npm i reflect-metadata --save

而后在 tsconfig.jsonemitDecoratorMetadata 须要中配置 true

而后,咱们能够应用 Reflect.defineMetadata 和 定义并获取元数据 Reflect.getMetadata ,例如:

import 'reflect-metadata';const CLASS_KEY = 'ioc:key';function ClassDecorator() {  return function (target: any) {    Reflect.defineMetadata(CLASS_KEY, {      metaData: 'metaData',    }, target);    return target;  }}@ClassDecorator()class D {  constructor(){}}console.log(Reflect.getMetadata(ClASS_KEY, D)); // => {metaData: 'metaData'}

应用 Reflect ,咱们能够标记任何类,并将非凡操作利用于标记化的类。

应用:

src/ioc/demo/a.ts

import { Provider } from "../provider"; // 需实现import { Inject } from "../inject"; // 需实现import B from './b'import C from './c'@Provider('a')export default class A {  @Inject()  private b: B  @Inject()  c: C  print () {    this.c.print()  }}

src/ioc/demo/b.ts

import { Provider } from '../provider' // 需实现@Provider('b', [10])export default class B {  n: number  constructor (n: number) {    this.n = n  }}

src/ioc/demo/c.ts

import { Provider } from '../provider' // 需实现@Provider()export default class C {  print () {    console.log('hello')  }}

应用就可和 midwayjs 统一。能够看到不再有手动实例化,且能够主动解决要注册的类,且要注入的属性。而且实例都由类自身保护,更改的话,不须要改其余文件。

src/ioc/index.ts

import { Container } from './container' // 治理 元信息import { load } from './load' // 程序加载,负责扫描,@Provide、@Inject相应的实例化及绑定解决export default function () {  const container = new Container()  const path = './src/ioc/demo'  load(container, path)  const a:any = container.get('a')  console.log(a); // A => {b: B {n: 10}}  a.c.print() // hello}

因为简略版的并未将容器和路由绑定,所以这么拜访了

实现:

因为在程序启动时,须要晓得哪些类须要注册到容器中,所以须要在定义的类的元数据后附加一些非凡标记,这样就能够通过扫描辨认进去。用装璜器Provider来对须要注册的类进行标记,被标记的类能被其余类应用。

src/ioc/provider.ts

import 'reflect-metadata'import * as camelcase from 'camelcase'export const class_key = 'ioc:tagged_class'// Provider 装璜的类,表明是要注册到Ioc容器中export function Provider (identifier?: string, args?: Array<any>) {  return function (target: any) {    // 驼峰命名,这个的目标是,注解的时候退出不传,就用类名的驼峰式    identifier = identifier ?? camelcase(target.name)    Reflect.defineMetadata(class_key, {      id: identifier, // key,用来注册Ioc容器      args: args || [] // 实例化所需参数    }, target)    return target  }}

须要晓得类的哪些属性须要被注入,因而定义Inject装璜器来标记。

src/ioc/inject.ts

// 将绑定的类注入到什么中央import 'reflect-metadata'export const props_key = 'ioc:inject_props'export function Inject () {  return function (target: any, targetKey: string) {    // 注入对象    const annotationTarget = target.constructor    let props = {}    // 同一个类,多个属性注入类    if (Reflect.hasOwnMetadata(props_key, annotationTarget)) {      props = Reflect.getMetadata(props_key, annotationTarget)    }    props[targetKey] = {      value: targetKey    }    Reflect.defineMetadata(props_key, props, annotationTarget)  }}

容器必须具备两个性能,即注册实例并获取它们。很天然会想到 Map ,可用于实现一个简略的容器:

src/ioc/container.ts

import 'reflect-metadata'import { props_key } from './inject'export class Container {  bindMap = new Map()  // 绑定类信息  bind(identifier: string, registerClass: any, constructorArgs: any[]) {    this.bindMap.set(identifier, {registerClass, constructorArgs})  }  // 获取实例,将实例绑定到须要注入的对象上  get<T>(identifier: string): T {    const target = this.bindMap.get(identifier)    if (target) {      const { registerClass, constructorArgs } = target      // 等价于 const instance = new A([...constructorArgs]) // 假如 registerClass 为定义的类 A      // 对象实例化的另一种形式,new 前面须要跟大写的类名,而上面的形式能够不必,能够把一个类赋值给一个变量,通过变量实例化类      const instance = Reflect.construct(registerClass, constructorArgs)      const props = Reflect.getMetadata(props_key, registerClass)      for (let prop in props) {        const identifier = props[prop].value        // 递归获取 injected object        instance[prop] = this.get(identifier)      }      return instance    }  }}

对于 Reflect.construct(target, args, newTarget): 办法的行为有点像 new 操作符 构造函数,相当于运行 new target(...args)。

var obj = new Foo(...args);var obj = Reflect.construct(Foo, args);

在启动时扫描所有文件,获取文件导出的所有类,而后依据元数据进行绑定。假如没有嵌套目录,实现如下:

src/ioc/load.ts

import * as fs from 'fs'import { resolve } from 'path'import { class_key } from './provider'// 启动时扫描所有文件,获取定义的类,依据元数据进行绑定/** * 单层目录扫描实现 * @param container: the global Ioc container */export function load(container, path) {  const list = fs.readdirSync(path)  for (const file of list) {    if (/\.ts$/.test(file)) {      const exports = require(resolve(path, file))      for (const m in exports) {        const module = exports[m]        if (typeof module === 'function') {          const metadata = Reflect.getMetadata(class_key, module)          // register          if (metadata) {            container.bind(metadata.id, module, metadata.args)          }        }      }    }  }}

在下面的简略版的根底上,实现 api-get 解决

能像一开始介绍的 midwayjs 应用形式统一。

次要是减少装璜器 @Controller@Get@Query,及相应的解决,具体看上面实现。

应用

src/reqIoc/demo/a.ts

import { Provider } from "../provider";import { Inject } from "../inject";import { Controller } from '../Controller'import { Get, Query } from '../request'import B from './b'@Provider()@Controller('/api')export class A {  @Inject()  b: B;  @Get('/b')  printB(@Query() id, @Query() name) {    const bProps:any = this.b.getProps(id, name);    bProps.className = 'b'    return { success: true, message: 'OK', data: bProps };  }  @Get('/c')  printC(@Query() id) {    const bProps:any = this.b.getProps(id);    bProps.className = 'c'    return { success: true, message: 'OK', data: bProps };  }}

src/reqIoc/demo/b.ts

import { Provider } from '../provider'@Provider()export default class B {  getProps (id?: string, name?: string) {    return {      id: id || 'mock',      name: name || 'mock',    };  }}

能通过浏览器 http://localhost:3000/api/b?id=12&name=n 看到以下数据

{  success: true,  message: "OK",  data: {    id: "12",    name: "n",    className: "b"  }}

和下面的简略版统一,须要初始化扫描,进行数据的解决。不一样的是,没有了数据的获取响应,只有扫描。数据的获取响应通过接口方式出现。

reqIoc/frame.ts

import { Container } from './container'import { load } from './load'export default function (ctx) {  const container = new Container()  const path = './src/reqIoc/demo'  load(container, path, ctx)}

实现

在根底版上,次要减少了三个装璜器@Controller@Get@Query,原有装璜器@Provider@Inject代码逻辑不变。

对于实现,想看源码的可参考:

  • @Controller: midwayjs/packages/decorator/web/controller.ts
  • @Get: midwayjs/packages/decorator/web/paramMapping.ts
  • @Query: midwayjs/packages/decorator/web/requestMapping.ts

src/reqIoc/controller.ts

import 'reflect-metadata'export const class_key = 'ioc:controller_class'export function Controller (prefix = '/') {  return function (target: any) {    const props = {      prefix    }    Reflect.defineMetadata(class_key, props, target)    return target  }}

次要就是存一下前缀,比方/api

src/reqIoc/request.ts

// 将绑定的类注入到什么中央import 'reflect-metadata'export const props_key = 'ioc:request_method'export const params_key = 'ioc:request_method_params'// 装璜的是类办法,target:类,targetKey: 类的办法名export function Get (path?: string) {  return function (target: any, targetKey: string) {    // 注入对象    const annotationTarget = target.constructor    let props = []    // 同一个类,多个办法    if (Reflect.hasOwnMetadata(props_key, annotationTarget)) {      props = Reflect.getMetadata(props_key, annotationTarget)    }    const routerName = path ?? ''    props.push({      method: 'GET',      routerName,      fn: targetKey    })    Reflect.defineMetadata(props_key, props, annotationTarget)  }}// 装璜的是类办法的入参,index 代表第几个参数export function Query () {  return function (target: any, targetKey: string, index: number) {    // 注入对象    const annotationTarget = target.constructor    const fn = target[targetKey]    // 函数的参数    const args = getParamNames(fn)    // 拿到绑定的参数名;index    let paramName = ''    if (fn.length === args.length && index < fn.length) {      paramName = args[index]    }    let props = {}    // 同一个类,多个办法    if (Reflect.hasOwnMetadata(params_key, annotationTarget)) {      props = Reflect.getMetadata(params_key, annotationTarget)    }    // 同一办法,多个参数    const paramNames = props[targetKey] || []    paramNames.push({type: 'query', index, paramName})    props[targetKey] = paramNames    Reflect.defineMetadata(params_key, props, annotationTarget)  }}const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;/** * get parameter from function * @param func  */export function getParamNames(func): string[] {  const fnStr = func.toString().replace(STRIP_COMMENTS)  let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).split(',').map(content => content.trim().replace(/\s?=.*$/, ''))  if (result.length === 1 && result[0] === '') {    result = []  }  return result}

reqIoc/container.ts

bindReq(key: string, list: any) {  this.bindMap.set(key, list)}getReq(key: string) {  return this.bindMap.get(key)}

在原有代码中减少以上办法。

load.ts

import * as fs from 'fs'import { resolve } from 'path'import { class_key } from './provider'import { class_key as controller_class_key } from './controller'import { props_key, params_key } from './request'const req_mthods_key = 'req_methods'const joinSymbol = '_|_'// 启动时扫描所有文件,获取定义的类,依据元数据进行绑定/** * 单层目录扫描实现 * @param container: the global Ioc container * @param path: 扫描门路 * @param ctx: 上下文,没有用框架,所以 ctx = {req, res}。而 req、res 是 server.on('request', function (req, res) {} */export function load(container, path, ctx) {  const list = fs.readdirSync(path)  for (const file of list) {    if (/\.ts$/.test(file)) {      const exports = require(resolve(path, file))      for (const m in exports) {        const module = exports[m]        if (typeof module === 'function') {          const metadata = Reflect.getMetadata(class_key, module)          // register          if (metadata) {            container.bind(metadata.id, module, metadata.args)            // 下面的代码逻辑是根底版,上面的是新增的            // 先收集 Controller 上的 prefix 信息,申请办法的绑定函数 Get,函数对应的参数 Query            const controllerMetadata = Reflect.getMetadata(controller_class_key, module)            if (controllerMetadata) {              const reqMethodMetadata = Reflect.getMetadata(props_key, module)              if (reqMethodMetadata) {                // 只须要存储信息,不须要额定的操作。简略起见,把所有申请信息都放到一个对象中了,不便后续依据接口申请及入参进行判断响应                const methods = container.getReq(req_mthods_key) || {};                const reqMethodParamsMetadata = Reflect.getMetadata(params_key, module)                // 将收集到的信息整顿放到容器中                reqMethodMetadata.forEach(item => {                  // 残缺的申请门路                  const path = controllerMetadata.prefix + item.routerName                  // 用申请办法和残缺门路作为 key                  methods[item.method + joinSymbol + path] = {                    id: metadata.id, // Controll 类                    fn: item.fn, // Get 办法                    args: reqMethodParamsMetadata ? reqMethodParamsMetadata[item.fn] || [] : [] // Get 办法 Query 参数                  }                })                container.bindReq(req_mthods_key, methods)              }            }          }        }      }    }  }  // 将所有申请数据拿进去,依据申请办法及入参进行解决响应  const reqMethods = container.getReq(req_mthods_key)  if (reqMethods) {    // ctx.req.url /api/c?id=12    const [urlPath, query] = ctx.req.url.split('?')    // key: 申请办法 + 门路    const methodUrl = ctx.req.method + joinSymbol + urlPath    // 依据 key 取出数据    const reqMethodData = reqMethods[methodUrl]    if (reqMethodData) {      const {id, fn, args} = reqMethodData      let fnQueryParams = []      // 办法有参数      if (args.length) {        // 将查问字符串转换为对象        const queryObj = queryParams(query)        // 这儿先依据参数在函数中的地位进行排序,这儿只解决了 Query 的状况, 再依据参数名从查问对象中取出数据        fnQueryParams = args.sort((a, b) => a.index - b.index).filter(item => item.type === 'query').map(item => queryObj[item.paramName])      }      // 调用办法,获取数据,进行响应      const res = container.get(id)[fn](...fnQueryParams)      ctx.res.end(JSON.stringify(res))    }  }}function queryParams (searchStr: string = '') {  const reg = /([^?&=]+)=([^?&=]*)/g;  const obj = {}  searchStr.replace(reg, function (rs, $1, $2) {    var name = decodeURIComponent($1);    var val = decodeURIComponent($2);    val = String(val);    obj[name] = val;    return rs;  });  return obj}

能够看到,几个装璜器,有很多代码是反复的,可形象。因而源码中是有个装璜器类。

为了简略起见,我只是把申请相干的数据简略的收集整理存储。所以用了一个 Container 容器。而源码是有一个继承 Container 的 RequestContainer 进行解决。

并且源码局部对于数据的扫描,思考到各种状况,很简单。扫描感兴趣的可看看midwayjs/packages/web/src/base.ts

集体github对应代码实现node-ts-sample-ioc

插曲

看项源码的时候,第一看 readme 文档,不用说大家都晓得。那么第二去看什么呢?

我的习惯是去看 package.json。外面信息要害信息不少呢。比方依赖哪些库,依据库能猜到我的项目里有些什么性能(前提是你晓得这个库及库的作用)。

遇到不晓得的库,去理解一下,兴许日后会用到,也能更好的理解我的项目在做什么。上面是我新意识的一些库(列举):

lernajs

Lerna 是一个优化应用 git 和 npm 治理多包存储库的工作流工具,用于治理具备多个包的 JavaScript 我的项目。

将大型代码库拆分为独立的版本包对于代码共享十分有用。 然而,代码库比拟大了,子库比拟多,子库之间有依赖,治理子库就会比拟麻烦且难以追踪(一个库的版本改了,依赖的库也须要变更),测试也不易。

lerna 能解决下面的问题,而且能够缩小包的安装时间,包占用的存储空间。毕竟对立治理了,只须要一份(即便多个子库有反复的),否则每个子库都是独自的一个npm包,须要独自装置、存储空间。

Lerna 仓库是什么样子?

如下所示的目录构造:

my-lerna-repo/  package.json  packages/    package-1/      package.json    package-2/      package.json

Lerna 能做什么?

Lerna 中的两个次要命令是 lerna bootstrap 和 lerna publish。 bootstrap 将把 repo 中的依赖关系链接在一起。 publish 将有助于公布软件包更新。

理解更多:

  • github:lerna
  • lerna: 最佳实际

这个库,对开发大型框架库是十分有用的,平时业务代码开发用不到。简略理解下就好,等真正有机会用到的时候再深刻也不迟。

benchmark.js

A robust benchmarking library that supports high-resolution timers & returns statistically significant results. As seen on jsPerf.

一个弱小的基准测试库,反对高分辨率计时器并返回具备统计意义的后果。

基准测试是一种测试代码性能的办法, 同时也能够用来辨认某段代码的CPU或者内存效率问题. 许多开发人员会用基准测试来测试不同的并发模式, 或者用基准测试来辅助配置工作池的数量, 以保障能最大化零碎的吞吐量.

Benchmark.js应用与JSLitmus相似的技术:咱们在while循环中运行提取的代码(模式A),反复执行直到达到最小工夫(模式B),而后反复整个过程以产生具备统计意义的显着性后果。

理解更多:

  • github:benchmark
  • javascript-benchmarking,值得一读,如何统计代码的运行工夫,到微秒,跨浏览器。
  • 应用Benchmark.js和jsPerf剖析代码性能

如果是开源的或面向C端的我的项目,对性能有高要求的,这个库将会十分有用呢。

inversify.js

A powerful and lightweight inversion of control(IOC) container for JavaScript & Node.js apps powered by TypeScript.

inversify 是一个弱小且轻量级的的基于 typescript 的 IOC 容器框架,反对 js 和 nodejs。

InversifyJS的开发具备四个次要指标:

  1. 容许JavaScript开发人员编写合乎SOLID准则的代码。
  2. 促成并激励恪守最佳OOP和IoC常规。
  3. 尽可能减少运行时开销。
  4. 提供最新的开发教训。

理解更多:

  • github:inversify
  • 解读 IoC 框架 InversifyJS
  • InversifyJS 中文文档

如果你的代码模块较多,且彼此之间存在强依赖,无妨思考尝试一下采纳依赖注入的形式,借用这个库实现后续逻辑,当然也能够仿 midwayjs 一样本人实现。

总结

断断续续的看的源码,文章也是断断续续写的,写的不是很好。不过总的来说学到了很多呢:

  • 利用装璜器做一些事,能够借助Reflect-metadata.js库来更好的治理类的元数据。实例化对象Reflect.construct
  • 库拆包git及npm版本依赖等治理,能够借助lerna.js
  • 代码性能基准测试,能够借助benchmark.js库。浏览器对微秒工夫的解决
  • 代码解耦,能够借助依赖注入容器及管制反转实现的原理,参考Inversify.jsmidway.jsinjection.js

其余:

理论往往是在简略版的根底之上思考各种细节、边界、形象、交融等之后,n个迭代之后的实现,而且后续会不断完善的。

我只是看了一部分我想看的代码,很多细节都没细看(比方web-expressweb-koa),也有一些齐全没看(比方packages-serverless)。目前精力有限,兴许前面须要用到了或者有空的时候会回过头来再看。

做对的事,并把事做对