一坝橡、模塊
1.
在Node環(huán)境中,一個(gè).js文件就稱(chēng)之為一個(gè)模塊(module)涂滴。
2.
模塊化的優(yōu)點(diǎn):提高代碼的可維護(hù)性友酱、可重用性高、可以避免函數(shù)名和變量名沖突柔纵。
3.
模塊化示例
// hello.js
'use strict';
var str = "Hello ";
function greet(name) {
console.log(str + name + '!');
}
module.exports = greet;
// main.js
'use strict';
var greet = require('./hello'); // 引入hello模塊
var str = 'Andy';
greet(str); // Hello Andy!
① 這種模塊加載機(jī)制被稱(chēng)為CommonJS規(guī)范缔杉。
② 每個(gè).js文件都是一個(gè)模塊。
③ 內(nèi)部使用的變量名和函數(shù)名不沖突搁料,比如hello.js和main.js 中的 str變量或详,互不影響。
④ 模塊中對(duì)外暴露輸出變量(可以為任意對(duì)象郭计,函數(shù)霸琴,數(shù)組等) module.exports = variable; 一個(gè)模塊引用其他模塊暴露的變量 var ref = require('module_name');
4.
模塊化實(shí)現(xiàn)的原理
瀏覽器中,大量使用全局變量可不好昭伸。如果你在a.js中使用了全局變量s梧乘,那么,在b.js中也使用全局變量s庐杨,將造成沖突选调,b.js中對(duì)s賦值會(huì)改變a.js的運(yùn)行邏輯。
Node.js 如何實(shí)現(xiàn)此模塊化的呢灵份?
// Node.js 加載hello.js之后仁堪,包裝一下,變成如下進(jìn)行執(zhí)行
(function() {
var str = "Hello ";
function greet(name) {
console.log(str + name + '!');
}
module.exports = greet;
})();
/*
* 實(shí)現(xiàn) module.exports
*/
// 準(zhǔn)備module對(duì)象
var module = {
id: 'a',
exports: {}
};
var load = function(module) {
// 讀取 hello.js代碼
function greet(name) {
console.log('Hello ' + name + '!');
}
module.exports = greet;
// hello.js 代碼結(jié)束
return module.exports;
};
var exported = load(module);
// 保存module
save(module, exported);
① 全局變量str經(jīng)過(guò)包裝之后變成匿名函數(shù)內(nèi)部的局部變量填渠。如若Node.js繼續(xù)加載其他模塊弦聂,模塊中的變量str也互不干擾。
② Node.js在加載js文件之前先準(zhǔn)備一個(gè)變量module氛什,并將其傳入加載load函數(shù)莺葫,最終返回module.exports。
5.
module.exports vs exports
/*
* module.exports
*/
// a.js
function hello() {
console.log('Hello world!');
}
function greet(name) {
console.log('Hello ' + name);
}
module.exports = {
hello: hello,
greet: greet
};
// b.js
var foo = require('./a');
foo.hello(); // Hello world!
foo.greet('Andy'); // Hello Andy
/*
* exports
*/
// a.js
function hello() {
console.log('Hello world!');
}
function greet(name) {
console.log('Hello ' + name);
}
exports.hello = hello;
exports.greet = greet;
// b.js
var foo = require('./a');
foo.hello(); // Hello world!
foo.greet('Andy'); // Hello Andy
/*
* Node 加載機(jī)制:
*/
//Node 把待加載的js文件放入一個(gè)包裝函數(shù)load中執(zhí)行枪眉。在執(zhí)行這個(gè)load()函數(shù)之前捺檬,Node準(zhǔn)備了module變量:
// module變量
var module = {
id: 'hello',
exports: {}
}
// load函數(shù)最終返回module.exports
var load = function(exports, module) {
// hello.js 的文件內(nèi)容
...
// load函數(shù)返回
return module.exports;
}
var exported = load(module.exports, module);
默認(rèn)情況下,Node準(zhǔn)備的exports變量和module.exports變量實(shí)際上是同一個(gè)變量瑰谜,并且初始化為空對(duì)象{}
exports.foo = function() { return 'foo';};
exports.bar = function() {return 'bar';};
也可寫(xiě)成
module.exports.foo = function() { return 'foo';};
module.exports.bar = function() {return 'bar';};
如若輸出函數(shù)或者數(shù)組
module.exports = function() { return 'foo';};
exports = module.exports = {};
① 如若輸出鍵值對(duì)象{}欺冀,可以利用exports這個(gè)已存在的空對(duì)象{},并繼續(xù)添加新的鍵值萨脑;
② 如若輸出一個(gè)函數(shù)或數(shù)組隐轩,必須直接對(duì)module.exports對(duì)象賦值;
③所以建議使用module.exports = xxx 方式輸出模塊變量渤早。
④ exports 是指向的 module.exports 的引用职车。
⑤ require() 返回的是 module.exports 而不是 exports。
二鹊杖、基本模塊
1.
fs - 文件系統(tǒng)模塊悴灵,負(fù)責(zé)讀寫(xiě)文件
① 異步讀文件
// 讀取文本文件
'use strict';
var fs = require('fs');
fs.readFile('demo.txt', 'utf-8', function(err, data) {
if (err) { // err 正常為null,異常時(shí)為錯(cuò)誤對(duì)象
console.log(err);
} else { // data 讀取到的String骂蓖,異常時(shí)為undefined
console.log(data);
}
});
// 讀取圖片文件
'use strict';
var fs = require('fs');
fs.readFile('demo.png', function(err, data) {
if (err) {
console.log(err);
} else {
console.log(data); // 一個(gè)Buffer對(duì)象(一個(gè)包含零個(gè)或任意個(gè)字節(jié)的數(shù)組)
console.log(data.length + ' bytes');
}
});
Buffer對(duì)象可以和String相互轉(zhuǎn)換
// Buffer -> String
var text = data.toString('utf-8');
// String -> Buffer
var buf = Buffer.from(text, 'utf-8');
② 同步讀文件
'use strict';
var fs = require('fs');
// 同步讀取文件不接受回掉函數(shù)
var data = fs.readFileSync('demo.txt', 'utf-8');
console.log(data);
如若同步讀取文件發(fā)生錯(cuò)誤积瞒,用try...catch捕獲錯(cuò)誤
'use strict';
try {
var data = fs.readFileSync('demo.txt', 'utf-8');
console.log(data);
} catch (err) {
console.log(err);
}
③ 異步寫(xiě)文件
'use strict';
var fs = require('fs');
var data = 'Hello, world!';
fs.writeFile('output.txt', data, function(err) {
if (err) {
console.log(err);
} else {
console.log('ok');
}
});
- 如若文件不存在,則會(huì)自動(dòng)創(chuàng)建一個(gè)
- 如若傳入的數(shù)據(jù)為String登下,默認(rèn)按UTF-8寫(xiě)入文件茫孔,如若是Buffer對(duì)象,則寫(xiě)入的為二進(jìn)制文件被芳。
- 回調(diào)函數(shù)只關(guān)心是否寫(xiě)入成功缰贝,所以只需一個(gè)err參數(shù)即可。
④ 同步寫(xiě)文件
'use strict';
var fs = require('fs');
var data = 'Hello world!';
fs.writeFileSync('output.txt', data);
⑤ stat -- 返回文件或目錄的詳細(xì)信息畔濒,比如大小剩晴、創(chuàng)建時(shí)間等
'use strict';
var fs = require('fs');
fs.stat('demo.txt', function(err, stat) {
if (err) {
console.log(err);
} else {
// 是否是文件
console.log('isFile: ' + stat.isFile());
// 是否是 目錄
console.log('isDirectory: ' + stat.isDirectory());
if (stat.isFile()) {
console.log('size: ' + stat.size); // 文件大小
console.log('birth time: ' + stat.birthtime); // 創(chuàng)建時(shí)間
console.log('modified time: ' + stat.mtime); // 修改時(shí)間
}
}
});
⑥ statSync
'use strict';
var fs = require('fs');
var stat = fs.statSync('demo.txt');
console.log('isFile: ' + stat.isFile());
// 是否是 目錄
console.log('isDirectory: ' + stat.isDirectory());
if (stat.isFile()) {
console.log('size: ' + stat.size); // 文件大小
console.log('birth time: ' + stat.birthtime); // 創(chuàng)建時(shí)間
console.log('modified time: ' + stat.mtime); // 修改時(shí)間
}
2.
stream - 僅在服務(wù)區(qū)端可用的模塊,目的支持“流”這種數(shù)據(jù)結(jié)構(gòu)
① 從文件流讀取文本內(nèi)容
/*
* 從文本流讀取文本內(nèi)容
*/
'use strict';
var fs = require('fs');
var rs = fs.createReadStream('demo.txt', 'utf-8');
// data 事件流表示流數(shù)據(jù)可以讀取
rs.on('data', function(chunk) {
console.log('data event');
console.log(chunk);
});
// end 事件表示這個(gè)流已到末尾侵状,無(wú)數(shù)據(jù)可讀
rs.on('end', function() {
console.log('end event');
});
// error 事件表示出錯(cuò)
rs.on('error', function(err) {
console.log('Error: ' + err);
});
② 以流形式寫(xiě)入文件
/*
* 以流的形式寫(xiě)入文件
*/
'use strict';
var fs = require('fs');
// 寫(xiě)入文本內(nèi)容
var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream寫(xiě)入文本數(shù)據(jù)\n');
ws1.write('END');
ws1.end();
// 寫(xiě)入二進(jìn)制數(shù)據(jù)
var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream寫(xiě)入二進(jìn)制數(shù)據(jù)\n', 'utf-8'));
ws2.write(new Buffer('END'));
ws2.end();
注:若寫(xiě)入的文件不存在赞弥,則會(huì)創(chuàng)建一個(gè)新的文件。
③ pipe
'use strict';
var fs = require('fs');
var rs = fs.createReadStream('demo.txt');
var ws = fs.createWriteStream('copied.txt');
rs.pipe(ws);
pipe()把一個(gè)文件流和另一個(gè)文件流串起來(lái)壹将,源文件的所有數(shù)據(jù)就自動(dòng)寫(xiě)入到目標(biāo)文件中嗤攻。實(shí)際為一個(gè)復(fù)制文件的過(guò)程
默認(rèn)情況,Readable數(shù)據(jù)流數(shù)據(jù)讀取完畢之后诽俯,end事件觸發(fā)后妇菱,將自動(dòng)關(guān)閉Writable數(shù)據(jù)流。
readable.pipe(writable, { end: false }); // 禁止自動(dòng)關(guān)閉Writable流
3.
http
① HTTP服務(wù)器
/*
* 創(chuàng)建http服務(wù)
*/
'use strict';
var http = require('http');
// 創(chuàng)建http server
var server = http.createServer(function(request,response) {
// 回調(diào)函數(shù)接收request和response對(duì)象
// 獲得HTTP請(qǐng)求的method和url
console.log(request.method + ':' + request.url);
// 將HTTP響應(yīng)200寫(xiě)入response暴区,同時(shí)設(shè)置Content-Type:text/html;
response.writeHead(200, {'content-Type': 'text/html'});
// 將HTTP響應(yīng)的HTML內(nèi)容寫(xiě)入response
response.end('<h1>Hello world!</h1>');
});
// 監(jiān)聽(tīng)8090端口
server.listen(8090);
console.log('Server is running at htpp://127.0.0.1:8090');
啟動(dòng)服務(wù)之后闯团,在瀏覽器中輸入http://127.0.0.1:8090之后
② 文件服務(wù)器
/*
* 文件服務(wù)器
*/
'use strict';
var
fs = require('fs'),
url = require('url'),
path = require('path'),
http = require('http');
// 從命令行參數(shù)獲取root目錄,默認(rèn)是當(dāng)前目錄
var root = path.resolve(process.argv[2] || '.');
console.log('Static root dir: ' + root);
// 創(chuàng)建服務(wù)器
var server = http.createServer(function(request, response) {
// 獲得URL的path
var pathname = url.parse(request.url).pathname;
// 獲得對(duì)應(yīng)的本地文件路徑
var filepath = path.join(root, pathname);
// 獲取文件狀態(tài)
fs.stat(filepath, function(err, stats) {
if(!err && stats.isFile()) {
// 沒(méi)有出錯(cuò)并且文件存在
console.log('200' + request.url);
// 發(fā)送200響應(yīng)
response.writeHead(200);
// 將文件流導(dǎo)向response;
fs.createReadStream(filepath).pipe(response);
} else {
// 出錯(cuò)了或者文件不存在
console.log('404' + request.url);
// 發(fā)送404響應(yīng)
response.writeHead(404);
response.end('404 Not Found');
}
});
});
server.listen(8090);
console.log('Server is running at http://127.0.0.1:8090');
在命令行運(yùn)行 node server.js /path/dir; /path/dir 為本地一個(gè)有效目錄仙粱,然后在瀏覽器中輸入 http://127.0.0.1:8090/index.html房交,本地目錄有此文件index.html,服務(wù)器就可以把文件內(nèi)容發(fā)送給瀏覽器伐割。
4.
crypto - 提供通用的加密和哈希算法
①Hash
將長(zhǎng)度不固定的消息作為輸入候味,通過(guò)運(yùn)行hash函數(shù)刃唤,生成固定長(zhǎng)度的輸出,這段輸出就叫做摘要白群。過(guò)程不可逆尚胞,即輸入固定的情況產(chǎn)生固定的輸出,但知道輸出的情況無(wú)法反推輸入帜慢。
常見(jiàn)摘要算法:
- MD5: 128 位
- SHA1: 160位
- SHA256: 256位
- SHA512: 512位
/*
* MD5:128位
*/
const crypto = require('crypto');
const hash = crypto.createHash('md5');
// 可以多次調(diào)用update(); hash.update()方法就是將字符串相加
hash.update('Hello, world!');
hash.update('Hello, nodejs!');
console.log(hash.digest('hex')); // hash.digest()將字符串加密返回
/*
* SHA-1 :160 位
*/
const crypto = require('crypto');
const hash = crypto.createHash('sha1');
// 可以多次調(diào)用update(); hash.update()方法就是將字符串相加
hash.update('Hello, world!');
hash.update('Hello, nodejs!');
console.log(hash.digest('hex')); // hash.digest()將字符串加密返回
同理?yè)Q成 把參數(shù)換成 sha256笼裳、sha512即可。
② Hmac
/*
* Hmac
*/
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'secret-key'); // 需要一個(gè)密鑰
hmac.update('Hello, world!');
hmac.update('Hello, nodejs!');
console.log(hmac.digest('hex'));
- 可以理解為帶密鑰的hash函數(shù)粱玲。
- 只要密鑰發(fā)生了變化躬柬,即使同樣的輸入,輸出也不同抽减。
③ 對(duì)稱(chēng)加密
加密和解密都用同一個(gè)密鑰允青,常見(jiàn)的對(duì)稱(chēng)加密算法有:DES、3DES胯甩、AES昧廷、Blowfish、RC5偎箫、IDEA
/*
* AES
*/
const crypto = require('crypto');
// 加密
function aesEncrypt(data, key) {
const cipher = crypto.createCipher('aes192', key);
var crypted = cipher.update(data, 'utf-8', 'hex');
console.log("cc" + crypted);
crypted += cipher.final('hex');
return crypted;
}
// 解密
function aesDecrypt(encrypted, key) {
const decipher = crypto.createDecipher('aes192', key);
var decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8'); // decipher.final() 返回加密的內(nèi)容
return decrypted;
}
var data = 'Hello, this is a secret message!';
var key = 'Password!';
var encrypted = aesEncrypt(data, key);
var decrypted = aesDecrypt(encrypted, key);
console.log('Plain text: ' + data);
console.log('Encrypted text: ' + encrypted);
console.log('Decrypted text:' + decrypted);
三木柬、Web開(kāi)發(fā)
1.
koa
① 入門(mén)示例
/*
* Koa2
*/
// 導(dǎo)入的為一個(gè)class
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
app.listen(3000);
console.log('app started at port 3000');
- 參數(shù)ctx是由koa傳入的封裝了request和response的變量;
- next是koa傳入的將要處理的下一個(gè)異步函數(shù)淹办;
- 關(guān)鍵字async和await眉枕,可以把一個(gè)function變?yōu)楫惒侥J剑?/li>
- async標(biāo)記的函數(shù)稱(chēng)為異步函數(shù),在異步函數(shù)中怜森,可以用await調(diào)用另一個(gè)異步函數(shù)
- ctx.url相當(dāng)于ctx.request.url速挑,ctx.type相當(dāng)于ctx.response.type;
/*
* koa middleware
*/
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx, next) => {
console.log(`Step1: + ${ctx.request.method} ${ctx.request.url}`); // 打印URL
await next(); // 調(diào)用下一個(gè)middleware
});
app.use(async(ctx, next) => {
const start = new Date().getTime();
await next(); // 調(diào)用下一個(gè)middleware
const ms = new Date().getTime() - start; // 耗費(fèi)時(shí)間
console.log(`Step2: Time: ${ms}ms`); // 打印消耗時(shí)間
});
app.use(async(ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
console.log("Step3");
});
app.listen(3000);
console.log('app started at port 3000');
- 當(dāng)用瀏覽器訪問(wèn)http://localhost:3000時(shí),命令行的輸出為:
Step1: +GET /
Step3
Step2: Time: 2ms- 每收到一個(gè)http請(qǐng)求副硅,koa就會(huì)調(diào)用app.use()注冊(cè)的async函數(shù)姥宝,并傳入ctx和next參數(shù)。app.use()的順序決定了middleware的順序恐疲。
② 處理URL
/*
* koa-router GET
*/
const Koa = require('koa');
const router = require('koa-router')();
const app = new Koa();
// log request url
app.use(async(ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}`);
await next();
});
// add url-route
router.get('/hello/:name', async(ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!`;
});
router.get('/', async(ctx, next) => {
ctx.response.body = `<h1>Index</h1>`;
});
// add router middleware;
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000');
在瀏覽器輸入:http:/localhost:3000腊满,返回的頁(yè)面顯示 Index;
在瀏覽器輸入:http://localhost:3000/hello/andy培己,返回的頁(yè)面顯示 Hello, andy! ;
router.get('/path', async fn)來(lái)注冊(cè)一個(gè)GET請(qǐng)求碳蛋。可以在請(qǐng)求路徑中使用帶變量的/hello/:name省咨,變量可以通過(guò)ctx.params.name訪問(wèn)
/*
* koa-router POST
*/
const Koa = require('koa');
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');
var app = new Koa();
app.use(async(ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}`);
await next();
});
app.use(bodyParser()); // 用來(lái)解析原始request請(qǐng)求肃弟,把解析后的參數(shù)綁定到ctx.request.body中
router.get('/', async(ctx, next) => {
ctx.response.body = `<h1>Index</h1>
<form action="/signin" method="post">
<p>Name: <input name="name" value="koa"></p>
<p>Password: <input name="password" type="password"></p>
<p><input type="submit" value="Submit"></p>
</form>`;
});
router.post('/signin', async(ctx, next) => {
var
name = ctx.request.body.name || '',
password = ctx.request.body.password || '';
console.log(`signin with name: ${name}, password:${password}`);
if (name === 'koa' && password === '12345') {
ctx.response.body = `<h1>Welcome, ${name}!`;
} else {
ctx.response.body = `<h1>Login failed!</h1>
<p><a href="/">Try again</a></p>`;
}
});
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000');
koa-bodyparser必須在router之前被注冊(cè)到app對(duì)象上;
類(lèi)似的put、delete、head請(qǐng)求也可用router處理
優(yōu)化目錄
controllers
-- hello.js
-- index.js
controller.js
app.js
// hello.js
var fn_hello = async(ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};
module.exports = {
'GET /hello/:name': fn_hello
};
// index.js
// 主頁(yè)
var fn_index = async(ctx, next) => {
ctx.response.body = `<h1>Index</h1>
<form action="/signin" method="post">
<p>Name: <input name="name" value="koa"></p>
<p>Password: <input name="password" type="password"></p>
<p><input type="submit" value="Submit"></p>
</form>`;
};
// 登錄
var fn_signin = async(ctx, next) => {
var
name = ctx.request.body.name || '',
password = ctx.request.body.password || '';
console.log(`signin with name: ${name}, password:${password}`);
if (name === 'koa' && password === '12345') {
ctx.response.body = `<h1>Welcome, ${name}!`;
} else {
ctx.response.body = `<h1>Login failed!</h1>
<p><a href="/">Try again</a></p>`;
}
};
module.exports = {
'GET /': fn_index,
'POST /signin': fn_signin
};
// controller.js
const fs = require('fs');
function addMapping(router, mapping) {
for (var url in mapping) {
if (url.startsWith('GET')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}
function addControllers(router) {
var files = fs.readdirSync(__dirname + '/controllers');
var js_files = files.filter((f)=>{
return f.endsWith(".js");
});
for (var f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/controllers/' +f);
addMapping(router, mapping);
}
}
module.exports = function(dir) {
let
controllers_dir = dir || 'controllers';
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
}
// app.js
const controller = require('./controller');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
var app = new Koa();
app.use(bodyParser());
app.use(controller());
app.listen(3000);
console.log('app started at port 3000');
③ Nunjuncks
目錄
views
-- hello.html
demo.js
// demo.js
const nunjucks =require('nunjucks');
function createEnv(path, opts) {
// 參數(shù) opts
var autoescape = opts.autoescape === undefined ? true: opts.autoescape,
noCache = opts.noCache || false,
watch = opts.watch || false,
throwOnUndefined = opts.throwOnUndefined || false,
// nunjucks
env = new nunjucks.Environment (
new nunjucks.FileSystemLoader('views', {
noCache: noCache,
watch: watch
}), {
autoescape: autoescape,
throwOnUndefined: throwOnUndefined
}
);
if (opts.filters) {
for (var f in opts.filters) {
env.addFilter(f, opts.filters[f]);
}
}
return env;
}
var env = createEnv('views', {
watch: true,
filters: {
hex: function(n) {
return '0x' + n.toString(16);
}
}
});
var s1 = env.render('hello.html', {name: '小明'});
console.log(s1); // <h1>Hello 小明</h1>
var s2 = env.render('hello.html', {name: '<script>alert("小明")</script>'});
console.log(s2); // <h1>Hello <script>alert("小明")</script</script></h1>
參考資料
廖雪峰JavaScritp教程