最近在整理笔记,翻出了以前钢接触node时的一些学习代码。现在再看当时的自己,真是天真的可笑。不过所谓人生,也许正是成长和领悟的过程吧。这些笔记就当作纪念,勿忘初心吧。
1. 取命令行参数
当我们在终端中使用node执行程序的时候,我们可以通过一个process的全局变量来了解命令行的详细情况。
假设我们需要一个终端程序用来计算一系列数字的和。如果你之前写过bash,就知道我们应该写 一个类似add.sh的程序,然后使用bash add.sh 1 2 3来计算1,2和3的合。在bash中,我们可以轻松的取出该命令行的后续参数。
使用node可以实现同样的命令行函数。我们可以通过process全局变量来了解命令行的详情。process.argv则包含了命令行输入的所有参数。如果我们将如下语句写入一个add.js 中:
console.log(process.argv);
当我们执行node add.js 1 2 3的时候,该程序便会打印一个包含所有输入命令行参数的数组:
[ 'node', '/path/to/your/program.js', '1', '2', '3' ]
于是我们就可以轻松取出传入该程序需要的参数,然后求合了:
var sum = 0;
for (var i = 2; i < process.argv.length; i++) {*
sum += Number(process.argv[i]);*
}
console.log(sum);
这里Number将string显式转换为数字,否则+会变成string连接。
2. 同步文件操作
这里同步操作指的是等到文件读取完了再执行后续指令,后面我们还要看到异步操作。
在node中进行文件操作都需要fs模块,该模块有一个方法readFileSync用来同步读取文件。注意该方法的返回值是一个特殊的Buffer对象,并不是直接的string。这里多加一层Buffer抽象主要是为了解决文件的编码问题,毕竟不是所有的文件都是可以转换为string的文本。
但如果我们只读取文本文件,则可以使用buffer.toString()将其转换为string,然后就可以方便的使用默认的string方法进行操作了。
Ref
下面我们来写一个简单的命令行程序来计算文本文件中的换行符的数量。使用方法为node newline.js path-to-file
。
命令行的最后一个参数是文件的路径,结合前面的知识,我们知道取出这个路径的可以利用process.argv:
var path = process.argv[2];
然后就可以读取文件了:
var content = fs.readFileSync(path);
再转换成string,再使用string的split方法按换行符分割为array。这里需要注意,由于最后一行是没有换行的,所以得到的数组长度必须减一。
var numberOfNewline = content.toString().split('\n').length - 1;
3. 异步文件操作
如果只用同步方法,那node其实和其他后端技术是差不多的。但node最吸引人的地方就是天生的异步特性,也就是说在文件读取这样耗时较长的操作时,我们不需到等到读取完成就可以先做其他事情;文件操作完成后,node会用某种方式通知我们,然后再针对操作结果执行下一步指令。node的通知方式可以有多种,但一开始我们先从最老,也是最基本的callback开始吧。
跟上一个例子类似,异步文件读取依然需要从process.argv中取得命令行传入的文件路径。不同的时我们这里使用fs.readFile来处理读文件操作。上一个例子我们直接将path传入readFileSync,然后等文件读完了再做后续操作。异步的readFile则不同,因为不需要等待就能继续执行,因此需要先定义一个通知读取完成,并处理读取结果的callback:
fs.readFile(path, options, callback)
这里的options可以指定文件的编码方式,如果这里传入utf8就不再需要前面的toString方法了。这里的callback是一个标准的js函数,接受两个参数err和data(node的惯例一般都是err在前,数据再后)。于是整个程序变成:
var fs = require('fs');
fs.readFile(process.argv[2], function(err, data) {
if (err) throw err;
var nbOfNewline = data.toString().split('\n').length - 1;
console.log(nbOfNewline);
});
4. 文件夹操作
node也提供了针对文件夹的操作。这里以读取文件夹为例,演示异步情况下得读取情况。
fs.readdir是fs模块中提供的用于读取目录的异步方法,接受目录路径和callback函数为参数。
fs.readdir(path, callback)
形式和readFile类似,这里的callback依然接受两个参数,一个err和一个文件夹内所有文件名的array。
fs.readdir(dir, function(err, files) {
if (err) throw err;
//对files进行操作
});
5. 模块化
node实现模块化的方式CommonJS已经渐渐取代AMD成为标准的模块化形式了,连ES6中的原生模块化语法也大量参考了这种实现方法。
这种实现方式的基本思想是将一个模块定义在一个函数中,然后将该函数暴露给module.exports对象,随后就可以在需要引用该模块的地方使用require引入了。
在写模块的时候需要注意一些node的惯例写法。比如在写foo(callback)形式的模块时,错误处理需要符合以下形式:
function bar(callback) {
foo(function (err, data) {
//出错,提前返回callback
if (err) return callback(err);
// 无错误,执行callback
callback(null, data);
});
};
注意callback的执行方式,出错时提前返回callback和错误信息,无错误则callback的第一参数为null,第二个为实际数据。这也符合我们前面常见的readDir(err, files)的形式,定义相同的callback:
function callback(err, files) {
if (err) throw err;
//no error, files processing
}
6. HTTP请求
说了半天好像完全没有提到node主要是用来处理后台服务请求的,这里我们就先来看最基本的http请求。
node中处理所有的http请求都需要引入http模块。如果你稍微了解一点http的话,会发现node的http方法还是很直观的。下面让我们从get这个最基础的请求讲起。
http.get(url, callback)用来处理get请求,第一个参数是请求的url,第二个是请求的详细处理函数。按常规来说callback应该有两个参数:err和数据,但http的callback比较特殊,只有一个response参数。get在node中是作为stream来处理的,也就是说node不是整个读取完所有数据在进行处理,而是按块(chunk)读取并传给response对象的。于是我们不能简单的只用一个参数来引用所有数据,而必须对数据读取的事情进行监听,以便在每个块读取完成后及时进行处理。
最重要的三个stream事件是data,error和end,分别表示一块数据准备好了,读取出错和整个数据读取完成。注册监听事件和js中监听DOM事件类似:
response.on(‘data’, callback(data) {});
于是一个最基础的http get处理可以写成:
http.get(url, function(response) {
response.setEncoding('utf8');
response.on('data', console.log);
response.on('error', console.error);
});
这里用setEncoding(‘utf8’)来制定数据编码,方便console演示。
7. HTTP数据集合处理
你可能会说,我不想依次处理chunk,有没有办法等到数据集合全部读取完成后,再一次性进行处理呢?
当然可以,一个简单的做法就是监听end事件,在数据读取完成触发end后再对前面的接收的数据块依次处理。还有更简洁的做法,需要一个外部模块来帮助我们完成这个任务。
response.pipe可以不停歇地将数据写入指定的目标,我们使用一个外部模块来作为接受目标来处理这些数据。一般比较流行的有bl和concat-stream两个模块,这里以bl为例:
var bl = require('bl');
var http = require('http');
http.get(process.argv[2], function(response) {
response.pipe(bl(function(err, data) {
if (err) console.error(err);
var str = data.toString();
console.log(str.length);
console.log(str);
}));
});
只要简单使用 response.pipe(bl(function(err, data) {})就可以完成数据集合的处理了。