Node.js学习
Node.js 学习笔记
我相信每个前端 er 肯定有一颗搞后端的心,当然我也不例外,下面是我学习 node 中遇到的一些问题以及解决方式,和一些小工具~
CPU 密集型和 IO 密集型
CPU 密集型也叫计算密集型,在程序中,大部分的时间都用来计算,CPU 的占用率很高,而 IO 操作所花费的时间很短,所以任务所需要的时间基本都是在 CPU 计算的时间。
IO 密集型指的是任务大部分的时间都是在等待 IO 的读写操作,而 CPU 的占用率不高。
所以 CPU 密集型程序适合 C 语言多线程,代码执行效率高,而 IO 密集型程序适合脚本语言,开发效率高,不需要太关注代码的执行效率。
使用 Node.js 更改文件名
// 引入fs文件模块
var fs = require('fs');
// src为需要改的文件名的上级文件夹名称
var src = 'icons';
//fs.readdir(path, callback)可以遍历文件夹,path为内容所在路径,callback接受两个参数,files是一个储存目录中所包含得到文件名的数组
fs.readdir(src, function(err, files) {
files.forEach(function(filename) {
var oldName = src + '/' + filename;
//将`_`替换成`-`,比如将`a_b`的文件名替换成`a-b`
var newName = src + '/' + filename.replace(/_/g, '-');
//重命名开始
fs.rename(oldName, newName, function(err) {
if (!err) {
console.log(filename + '下划线替换成功');
}
});
});
});
想学习 node,但是却不知道怎么学。在逛 v2ex 的时候看到过狼叔的个人经历,他说学习 nodejs 最好的方法就是每天看 10 个模块,虽然感觉很难,但是今天在研究 egg-bin 源码的时候想起了狼叔的话,所以今天就打算好好看下 egg-bin 的源码,不求每天看 10 个,希望今天能把 egg-bin 给看完~(事实证明,这也是很有难度的!!)
egg-bin
打开node_modules/egg-bin/bin/egg-bin.js
,发现调用了../index.js
,然后再细挖,就发现他是基于 common-bin 这个库的封装。是的,为什么说今天能看完 egg-bin 很难呢,因为他一层一层所依赖的库是很多的,如果你要完全的了解这个库的功能,那么它依赖的库的功能你也是必须要了解的。
所以我们来看看 common-bin 的源码,结果发现,它是基于 yargs 写的。我然后查了一下这两个库的作者,前者是阿里的,后者是一个国外的组织。然后我粗略的看了一下 yargs 的 api,还是蛮简单易懂的,所以我打算先把 common-bin 和 egg-bin 看完了再去学习 yargs。
common-bin
const DISPATCH = Symbol('Command#dispatch');
const PARSE = Symbol('Command#parse');
const COMMANDS = Symbol('Command#commands');
const VERSION = Symbol('Command#version');
class CommonBin {
constructor(rawArgv) {
/**
* original argument
* @type {Array}
*/
this.rawArgv = rawArgv || process.argv.slice(2);
debug('[%s] origin argument `%s`', this.constructor.name, this.rawArgv.join(' '));
/**
* yargs
* @type {Object}
*/
this.yargs = yargs(this.rawArgv);
/**
* helper function
* @type {Object}
*/
this.helper = helper;
/**
* parserOptions
* @type {Object}
* @property {Boolean} execArgv - whether extract `execArgv` to `context.execArgv`
* @property {Boolean} removeAlias - whether remove alias key from `argv`
* @property {Boolean} removeCamelCase - whether remove camel case key from `argv`
*/
this.parserOptions = {
execArgv: false,
removeAlias: false,
removeCamelCase: false,
};
// <commandName, Command>
this[COMMANDS] = new Map();
}
...
}
首先他创建了一个 CommonBin Class,将 yargs 导入,使用 load 传入文件夹目录,遍历里面的文件并使用文件名当作 command 的 name,并将 command name 和 file path 当作键值存进 map 中,注意这里他四个变量名(DISPATCH, PARSE, COMMANDS, VERSION)都使用了 Symbol 来避免命名冲突。
/**
* start point of bin process
*/
start() {
co(function* () {
// replace `--get-yargs-completions` to our KEY, so yargs will not block our DISPATCH
const index = this.rawArgv.indexOf('--get-yargs-completions');
if (index !== -1) {
// bash will request as `--get-yargs-completions my-git remote add`, so need to remove 2
this.rawArgv.splice(index, 2, `--AUTO_COMPLETIONS=${this.rawArgv.join(',')}`);
}
yield this[DISPATCH]();
}.bind(this)).catch(this.errorHandler.bind(this));
}
整个代码执行的起点就是调用 start 函数,使用 co 来依次执行 Generator,不过这里主要就是执行this[DISPATCH]
,而这个函数就是这个库的核心。
首先调用 yargs-parse 来解析传入的参数,不过 egg-bin 是没有传参数的。获取版本号,如果你的 command 中有副命令存在,比如egg-bin dev
中的 dev,因为 dev 中他还有自己独立的 options,所以如果副命令存在,this[DISPATCH]
会直接执行副命令的 js 并返回。如果不存在则依次调用之前遍历所获得的 COMMANDS,并调用 yargs.command 来传入 command。
之后判断context.argv.AUTO_COMPLETIONS
(默认是 null),如果为 true 则调用传入的参数并打印,不然则直接调用 run。这和 start 中的代码相对应:
// replace `--get-yargs-completions` to our KEY, so yargs will not block our DISPATCH
const index = this.rawArgv.indexOf('--get-yargs-completions');
if (index !== -1) {
// bash will request as `--get-yargs-completions my-git remote add`, so need to remove 2
this.rawArgv.splice(index, 2, `--AUTO_COMPLETIONS=${this.rawArgv.join(',')`);
}
就是为了执行自己的 DISPATCH,也就是里面的 run 函数。
if (context.argv.AUTO_COMPLETIONS) {
// slice to remove `--AUTO_COMPLETIONS=` which we append
this.yargs.getCompletion(this.rawArgv.slice(1), completions => {
// console.log('%s', completions)
completions.forEach(x => console.log(x));
});
} else {
// handle by self
yield this.helper.callFn(this.run, [ context ], this);
}
如果你在自己的副命令 js 中没有创建 run 函数,则就会默认执行yargs.showHelp()
函数。
helper.js 主要实现了部分 utils 函数,比如针对 bind 的 callFn 等等。
其实看里面的代码还是很清晰明了的,注释也全,代码格式也很工整,是一个很好的可以学习的模块~
我通过这个模块主要学习了 Class 的使用,yargs 的使用以及如果针对已有库进行更好的封装,我发现很多模块库的缩进都是 2 个 space,而我是不是年纪大了喜欢空大点呢,以后如果自己写模块了,也要好好适应 2 个 space 的格式啦。
debug
debug 是一个 js 的调试工具,node 和 web 端都可以使用,我这里主要研究 node 端的代码。
首先他会通过是否存在 process 来判断环境,然后引用相关的代码。
这两个平台下的代码都是基于 common.js,然后添加部分适配平台的函数,比如 useColors, formatArgs, log 等等。
让我们来看看比较重要的 common.js。
// setup函数,建立一个环境
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
Object.keys(env).forEach(function (key) {
createDebug[key] = env[key];
});
function createDebug(namespace) {
// 用来debug,log的函数
function debug () {}
// 用来删除实例的函数
function destory() {}
// 用来增强createDebug的函数
function extend() {}
// 用来启动debug
function enable() {}
// 用来警用debug
function disable() {}
// 用来判断是否启动
// 里面有names和skips两个数组,如果namespace在names中存在,说明可以启用
// 如果在skips,则直接跳过不启用
function enabled () {}
// 判断val是否是error,如果是则返回val.stack或者val.message
function coerce() {}
return debug
}
....
return createDebug;
}
module.exports = setup;
上面便是整体的代码,还有部分代码在各自的平台 js 文件下,各个函数的作用也很清晰明了,在这里我主要学到了怎么使用 node process.env 的参数,他这里使用 env.colors, depths, show_hidden 参数。
在 debug 函数中,它先是将传入的参数保存到 args 中,然后获取当前的时间戳以及上一个 debug 的时间戳并获得时间差。然后处理 args[0],判断是否要在 namespace 前添加时区时间还是在末尾添加时间差。最后便是处理 args 中的参数,并使用self.log || createDebug.log
打印出来。
通过这一个 module,我感觉我对程序设计的理解又深了一点,但是我感觉这些代码中还有可以优化的地方。
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
module.exports = require('./common')(exports);
var formatters = module.exports.formatters;
/**
* Map %o to `util.inspect()`, all on a single line.
*/
formatters.o = function(v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts).replace(/\s*\n\s*/g, ' ');
};
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
formatters.O = function(v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts);
};
比如这里完全就可以把 formatters 放在 exports 中,他为了覆盖 createDebug 的 formatters,但是其实在 common.js 中也只是给他初始化为{},所以直接赋值给 exports,然后删除 common.js 中 createDebug.formatters 的赋值便可。
如果有时间用 es6 的 class 重写一下我觉得应该蛮 cool 的~
统计 c#的 controllers 下 api 的个数
const fs = require('fs');
const ApiAllNums = 0;
fs.readdir('./Controllers', (err, files) => {
if (err) throw err;
files.forEach(file => {
const data = fs.readFileSync('./Controllers/' + file, 'utf-8');
const reg = data.match(/\[Route\(.*\)\]/g);
ApiAllNums += reg.length;
});
});
统计前端调用的 api 个数
const fs = require('fs');
const path = require('path');
let adminApi = new Set();
function readFileFn(dirPath) {
const dir = fs.readdirSync(dirPath);
dir.forEach(file => {
// 如果有要忽略的文件可以写在这里
if (file === 'require-2.1.14.min.js') return;
const filePath = path.join(dirPath, file);
const stats = fs.statSync(filePath);
const isFile = stats.isFile();
if (isFile) {
const data = fs.readFileSync(filePath, 'utf-8');
// 老项目中用的是ajax
// 可以根据自己使用的库来调整
const reg = data.match(/Ajax\({\s*url\:(.*?),/g);
reg &&
reg.length &&
reg.forEach(item => {
const api = item.match(/.*?url\:\s*['"](.*?)[?'"]/);
adminApi.add(api[1]);
});
} else {
readFileFn(filePath);
}
});
}
统计一个 js 中所有函数的行数
const fs = require('fs');
const path = require('path');
let fnLineCount = new Set();
function readFileFn(dirPath) {
const dir = fs.readdirSync(dirPath);
dir.forEach(file => {
const filePath = path.join(dirPath, file);
const stats = fs.statSync(filePath);
const isFile = stats.isFile();
if (isFile) {
const data = fs.readFileSync(filePath, 'utf-8');
// 这里要求js经过格式化过,不然就很难获取到相应的函数末尾
const reg = data.match(/([ ]*)function\s*.*?\s*\(\)\s*\{[\s\S]*?\n\1\}/g);
reg &&
reg.length &&
reg.forEach(item => {
const fnName = item.match(/function\s*(.*?)\s*\(/)[1] || 'noName';
const enterMatch = item.match(/\n/g);
const lineCount = enterMatch.length + 1;
fnLineCount.add(`${fnName}:${lineCount}`);
});
} else {
readFileFn(filePath);
}
});
}