零、开始之前

1、 首先解释一下node.js是什么?

2、node.js和javascript有什么不同?

1)因为javascript主要是用在browser,而node.js是在server或者你的电脑上写,所以就不会有window / document这两个变量,取而代之的是global / process.
global和window一样,也有很多内置的function和variables,比如setTimeout() / setInterval() / console..., 不过也有很多不同,这个后面会慢慢讲;

一、Modules—— require() & exports

node.js里很重要的一个概念就是modules。除了一些core modules(比如events/http/fs/url...), 还可以用require()来引进一些自定义的modules。

比如:

//stuff.js
var counter = function(arr){
    return 'There are '+ arr.length+ ' arguments.'
   };

var adder = function(a,b){
    return `the sum is ${a+b};`
   };

var pi = 3.1415926;

如果要在其他的file里用这个counter function应该怎么做呢?
在stuff.js后面加上:

//第一种方法
//modules.exports是一个object对象
modules.exports.counter = counter;
modules.exports.adder = adder;
modules.exports.pi = pi;

上面的方法意思是到了,但是比较复杂,我们可以用对象的方式来写modules.exports

//第二种方法
modules.exports = {
    counter: counter,
    adder: adder,
    pi: pi
  }

还有一种办法直接写在上面的functions里:

modules.exports.counter = function(arr){
    return 'There are '+ arr.length+ ' arguments.'
   };

modules.exports.adder = function(a,b){
    return `the sum is ${a+b};`
   };

modules.exports.pi = 3.1415926;

然后要引用的时候使用require():

//main.js

var stuff = require('./stuff'); //可以不用写extension name,node自己会补上;
//使用一下:
console.log(stuff.counter(['hello', 'world']);
console.log(stuff.adder(5, stuff.pi);

二、EventEmitter类

在node.js里,有一个core module是event,event里只有一个对象,就是EventEmitter, 主要用于封装事件触发和事件监听器的功能(使用javascript里的观察者模式)。
如果用过jquery的话,对这个格式一定不陌生:

button.on('click', function(){alert('clicked!');}

其实node里的EventEmitter和jQuery的这个事件监听函数的写法也很类似:

//首先引入EventEmitter对象
//EventEmitter是一个constructor,可以使用原型继承
var eventEmitter = require('event').EventEmitter;
var event = new eventEmitter();
//绑定事件
event.on('data', function(str){
    console.log('something here: '+ str.toString());
   };
//触发事件
//前一个param是event的名字,后一个是callback函数需要的变量
event.emit('data', 'Tom wants sth to eat.');

前面提到EventEmitter其实是一个constructor,可以通过prototype继承;
我们现在用util这个module来实现node里的inherits:

var event = require('event');
var util = require('util');

//创造一个person的constructor,一会来继承eventEmitter;
var Person = function(name){
    this.name = name;
   };
//util.inherits用来继承,注意两个params的顺序;
util.inherits(Person, event.EventEmitter);

//现在我们来new两个person实例出来:
var james = new Person('james');
var ellen = new Person('ellen');
//放在一个叫people的array里:
var people = [james, ellen];
//现在用eventEmitter里的on方法绑定事件;
//因为Person实例已经继承,所以可以使用
people.forEach(function(person){
  person.on('speak', function(str){
    console.log(`${person} says ${str}`);
  });
});
//触发事件
james.emit('speak', 'good morning!');
ellen.emit('speak', 'good evening!');

三、fs (file system) 类

fs类主要是用来处理文件的,有非常多的方法,但在这个小教程里只涉及read和write两项。
首先我们来区分一下“同步”和“异步”的概念:
“同步”—— blocking; 解析器会一步一步地解析你的code,如果前面那行没操作完就不会操作后面那行;
“异步”——non-blocking;解析器解析完这行代码之前,不会妨碍到后面代码的进行,在异步处理完后通过callback函数对结果进行处理,因此,异步的性能比同步会很多。

//node中同步read和write的写法:
var fs = require('fs');
//同步是'readFileSync'
var readMe = fs.readFileSync('input.txt', 'utf8');
fs.write('output.txt', readMe);

//异步:
//err是出现的错误,data是readFile后读取到的数据
fs.readFile('input.txt', 'utf8', function(err, data){
    if(err){
        console.error(err);
     }else{
        fs.writeFile('output.txt', data);
     }

可以看到,运用fs的callback函数,可以直接在readFile里写writeFile, 不需要重新定义变量。

再写一个fs中的建立目录和移除目录的方法,同样有同步和异步两种方式:

var fs = require('fs');

//make a directory sync
fs.mkdirSync('stuff');
//delete a directory sync
fs.rmdirSync('stuff');
//make a directory async
fs.mkdir('stuff', function(){
    fs.readFile('readMe.txt', 'utf8', function(err,data){
        fs.writeFile('./stuff/writeMe/txt', data);
    });
});
//remove a directory async
//you must first remove all files in this directory, then you can delete this folder
fs.unlink('./stuff/writeMe.txt', function(){
    fs.rmdir('stuff');
})

四、Stream类

在解释stream之前,首先我们来想一想为什么要有readable stream和writable stream呢? 明明fs类里就有readFile和writeFile的方法啊,何必还要再添加stream来找麻烦呢?

要知道这两个之间的不同,让我们来理一下"buffer"的概念。
buffer的介绍一般是这样:

在Node.js中,Buffer类是随Node内核一起发布的核心库。Buffer库为Node.js带来了一种存储原始数据的方法,可以让Nodejs处理二进制数据,每当需要在Nodejs中处理I/O操作中移动的数据时,就有可能使用Buffer库。

简单来说,buffer就是一个处理二进制数据的缓冲模块,那和stream又有什么关系呢?
可以看下下面的图:
图片描述
这张图里,可以把很大的一块data进行肢解,然后储存到buffer中,形成一个个的相对比较小的data chunk;
图片描述
在这张图里,chunk one by one向前传递就成了stream。

所以回到我们最初的问题,用readable stream和fs里的readFile有什么不同?

readFile是等一个file全部读完之后才fire callback函数,进行下一步动作;而stream是通过把file里的数据分成很多很多个小的chunk, 每次callback函数感知到data chunk的时候就会触发。这样传递数据会更快。

1、Readable stream

var fs  = require('fs');
var myReadStream = fs.createReadStream(__dirname + '/input.txt', 'utf8'); 
//这里的‘utf8'如果不加的话会解码成二进制(因为buffer)

myReadStream.on('data', function(chunk){
    console.log('new chunk received');
    console.log(chunk);
});
//stream也继承eventEmitter
//每次感知到data chunk就会触发,不需要等到整个file都读完

2、Writable stream

//和readable stream类似
//会创造出来一个output.txt在目录下
var fs  = require('fs');
var myReadStream = fs.createReadStream(__dirname + '/input.txt', 'utf8');
var myWriteStream = fs.createWriteStream(__dirname + '/output.txt');

myReadStream.on('data', function(chunk){
    console.log('new chunk received');
    myWriteStream.write(chunk);
});

3、pipe

因为把readable stream转成writable stream在node.js里非常常见,所以有一个更elegant的办法就是用pipe:

var fs = require('fs');
var myReadStream = fs.createReadStream(__dirname + '/input.txt', 'utf8');
var myWriteStream = fs.createWriteStream(__dirname + '/output.txt');
//pipe只能从可读流出发,不能从可写流出发
myReadStream.pipe(myWriteStream);

如果加上web server就可以这么写:

var fs  = require('fs');
var http = require('http');

var server = http.createServer(function(req, res){
    console.log('request was made: '+ req.url);
    res.writeHead(200, {'Content-Type': 'text/plain'});
    var myReadStream = fs.createReadStream(__dirname + '/input.txt', 'utf8');
    myReadStream.pipe(res);
    //response obj is writable 
});

server.listen(3000);
console.log('listen to port 3000');

如果要传递的数据是html格式的话:

var fs  = require('fs');
var http = require('http');

var server = http.createServer(function(req, res){
    console.log('request was made: '+ req.url);
    //html格式也可以用stream传递,用pipe把可读流转成可写流
    res.writeHead(200, {'Content-Type': 'text/html'});
    var myReadStream = fs.createReadStream(__dirname + '/index.html', 'utf8');
    myReadStream.pipe(res);
    //response obj is writable 
});

server.listen(3000);
console.log('listen to port 3000');

如果传递的数据是json的话:

//不使用stream,直接在res.end()里面传递
//但是要注意的是,end()里面只接受string,不能把object直接放进去
var fs  = require('fs');
var http = require('http');

var server = http.createServer(function(req, res){
    console.log('request was made: '+ req.url);
    res.writeHead(200, {'Content-Type': 'application/json'});
    var myobj = {
        name: 'Ryu',
        job: 'ninja',
        age: 29
    };
    res.end(JSON.stringify(myobj));
});

server.listen(3001);
console.log('listen to port 3001');

五、Router

介绍一个很简单的router的使用办法,只要用if判断req.url,然后用stream写入即可:

var http = require('http');
var fs = require('fs');

var server = http.createServer(function(req,res){
    console.log('request was made: '+ req.url);
    if(req.url === '/home' || req.url === '/'){
        res.writeHead(200, {'Content-Type' : 'text/html'});
//用fs的pipe把readable stream改成writable stream
        fs.createReadStream(__dirname +'/index.html').pipe(res);
    }else if(req.url === '/sample'){
        res.writeHead(200, {'Content-Type': 'text/html'});
        fs.createReadStream(__dirname + '/sample.html').pipe(res);
    }else if(req.url === '/api/ninjas'){
        var ninjas = [{name: 'ryu', age: 29}, {name: 'yoshi',age:32 }];
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify(ninjas));
    }
//其实还可以再写一个404 page
});

server.listen(3000);
console.log('now listening to port 3000');

yisha0307
323 声望59 粉丝