1)由於javascript主要是用在browser,而node.js是在server或者你的電腦上寫,因此就不會有window / document這兩個變量,取而代之的是global / process.
global和window同樣,也有不少內置的function和variables,好比setTimeout() / setInterval() / console..., 不過也有不少不一樣,這個後面會慢慢講;javascript
node.js裏很重要的一個概念就是modules
。除了一些core modules(好比events/http/fs/url...), 還能夠用require()來引進一些自定義的modules。html
好比:java
//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後面加上:node
//第一種方法 //modules.exports是一個object對象 modules.exports.counter = counter; modules.exports.adder = adder; modules.exports.pi = pi;
上面的方法意思是到了,可是比較複雜,咱們能夠用對象的方式來寫modules.exportsjquery
//第二種方法 modules.exports = { counter: counter, adder: adder, pi: pi }
還有一種辦法直接寫在上面的functions裏:web
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():json
//main.js var stuff = require('./stuff'); //能夠不用寫extension name,node本身會補上; //使用一下: console.log(stuff.counter(['hello', 'world']); console.log(stuff.adder(5, stuff.pi);
在node.js裏,有一個core module是event,event裏只有一個對象,就是EventEmitter, 主要用於封裝事件觸發和事件監聽器的功能(使用javascript裏的觀察者模式)。
若是用過jquery的話,對這個格式必定不陌生:api
button.on('click', function(){alert('clicked!');}
其實node裏的EventEmitter和jQuery的這個事件監聽函數的寫法也很相似:app
//首先引入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類主要是用來處理文件的,有很是多的方法,但在這個小教程裏只涉及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以前,首先咱們來想想爲何要有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的時候就會觸發。這樣傳遞數據會更快。
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都讀完
//和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); });
由於把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的使用辦法,只要用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');