入口

封装透传util

在开发util时,可能部分会由其他库组成,将其proxy。参考vue-cli/index.js at dev · brizer/vue-cli

该方案依赖node commonjs规范。

通过入口文件,将一些模块集合到exports上,统一暴露出去:

[
  'env',
  'exit',
  'ipc',
  'logger',
  'module',
  'object',
  'openBrowser',
  'pluginResolution',
  'launch',
  'request',
  'spinner',
  'validate'
].forEach(m => {
  Object.assign(exports, require(`./lib/${m}`))
})

exports.chalk = require('chalk')
exports.execa = require('execa')

也可以参考umi的方式,一个基于三方包的util库,集合内置一起,从而控制版本:

// 一个基于第三方包的util库,集合体
import address from 'address';
import chalk from 'chalk';
import spawn from 'cross-spawn';
import * as chokidar from 'chokidar';
import clipboardy from 'clipboardy';
import createDebug, { Debugger } from 'debug';
import deepmerge from 'deepmerge';
import execa from 'execa';
import lodash from 'lodash';
import glob from 'glob';
import portfinder from 'portfinder';
import got from 'got';
import resolve from 'resolve';
import yargs from 'yargs';
import mkdirp from 'mkdirp';
import pkgUp from 'pkg-up';
import Mustache from 'mustache';
import signale from 'signale';
import rimraf from 'rimraf';
import yParser from 'yargs-parser';
import * as t from '@babel/types';
import * as parser from '@babel/parser';
import * as traverse from '@babel/traverse';
import semver from 'semver';

export { spawn };
export { semver };
export { address };
export { chalk };
export { default as cheerio } from './cheerio/cheerio';
export { clipboardy };
export { chokidar };
export { createDebug, Debugger };
export { deepmerge };
export { execa };
export { lodash };
export { glob };
export { got };
export { portfinder };
export { pkgUp };
export { resolve };
export { yargs };
export { mkdirp };
export { Mustache };
export { rimraf };
export { yParser };
export { t };
export { parser };
export { traverse };
export { signale };
// 再暴露一些自己封装的util
export * from './ssr';
export * from './routes';
export { default as compatESModuleRequire } from './compatESModuleRequire/compatESModuleRequire';
export { default as mergeConfig } from './mergeConfig/mergeConfig';
export { default as isLernaPackage } from './isLernaPackage/isLernaPackage';
export { default as getFile } from './getFile/getFile';
export { default as winPath } from './winPath/winPath';
export { default as winEOL, isWindows } from './winEOL/winEOL';
export { default as parseRequireDeps } from './parseRequireDeps/parseRequireDeps';
export { default as BabelRegister } from './BabelRegister/BabelRegister';
export { default as Generator } from './Generator/Generator';
export { default as randomColor } from './randomColor/randomColor';
export { default as delay } from './delay/delay';
export { default as cleanRequireCache } from './cleanRequireCache/cleanRequireCache';
export * from './types';

有时候需要合并多个库的导出一起导出,可以参考****-core系列。 umi导出

参考node-fs-extra

入参与默认参数合并

简单的,通过和一个初始定义值合并:

  constructor(options: CallappOptions) {
    const defaultOptions = { timeout: 2000 };
    this.options = Object.assign(defaultOptions, options);
  }

复杂版,在合并后一一检查:

class Metric {
	constructor(config, defaults = {}) {
		if (!isObject(config)) {
			throw new TypeError('constructor expected a config object');
		}
		//给构造函数入参的合并
		Object.assign(
			this,
			{
				labelNames: [],
				registers: [globalRegistry],
				aggregator: 'sum',
			},
			defaults,
			config,
		);
		if (!this.registers) {
			// in case config.registers is `undefined`
			this.registers = [globalRegistry];
		}
		if (!this.help) {
			throw new Error('Missing mandatory help parameter');
		}
		if (!this.name) {
			throw new Error('Missing mandatory name parameter');
		}
		if (!validateMetricName(this.name)) {
			throw new Error('Invalid metric name');
		}
		if (!validateLabelName(this.labelNames)) {
			throw new Error('Invalid label name');
		}
		if (this.collect && typeof this.collect !== 'function') {
			throw new Error('Optional "collect" parameter must be a function');
		}

		this.reset();

		for (const register of this.registers) {
			register.registerMetric(this);
		}
	}

	reset() {
		/* abstract */
	}
}

入参保证参数浅拷贝

对于数组入参可以通过[...arr]:

/**
 * 在数组中随机生选择 n 个元素生成新的数组
 * 基于Fisher–Yates shuffle 洗牌算法
 * @param arr - 原始数组
 * @param n - 新数组元素个数
 * @returns 随机新数组
 */
function sampleSize([...arr], n = 1) {
  let m = arr.length;
  while (m) {
    // eslint-disable-next-line
    const i = Math.floor(Math.random() * m--);
    // eslint-disable-next-line
    [arr[m], arr[i]] = [arr[i], arr[m]];
  }
  return arr.slice(0, n);
}

对象也是一样的通过{...obj}

获取形参个数

函数实例.length就是形参的个数,arguments.length是实参的个数,但是一般可以通过剩余参考来解决。

看一个形参的使用场景:



//下面介绍创建函数柯里化的通用方法 
function curry(fn, ...args) {
  //fn.length是形参的个啥
  if (args.length >= fn.length) {
    return fn(...args);
  }

  return function(...args2) {
    return curry(fn, ...args, ...args2);
  };
}
//使用方法如下:
function add(num1,num2){
  return num1+num2; 
}
var curriedAdd = curry(add); 
console.log(curriedAdd(3,5));//8 
var curriedAddB = curry(add,5); 
console.log(curriedAddB(10));//15