JS这门语言最开始是作为浏览器脚本存在的,本身并没有模块系统,模块的划分靠html中的script标签控制,然而node.js将js作为后台开发语言,必须要有模块划分,如果一个文件要将本文件定义的变量和函数暴露出去,可以使用export:
//文件a.js
let a = 10;
function f(){};
module.exports = {
a:a,
f:f
}
或者
module.exports.a = a;
module.exports.f = f;
如果不涉及函数和数组,直接使用export也可,因此下面这种写法也是可以的
exports.a = a; //a不是数组和函数,可以省略module关键字
module.exports.f = f;
node中模块的实现,本质是将文件包含在了一个函数里,上面文件中的代码编译后会用函数进行包装,转化为下面这种形式,并预先生成module对象,作为参数传入。
module={
id:'a.hs',
exports:{}
}
function load(module){
let a = 10;
function f(){};
return module.exports;
}
如果要使用上面的模块,则需要用到require关键字:
const a = require('./a.js');
console.log(a.a);
a.f();
这是用require导入自定义模块的情况,如果需要使用系统模块,比如文件系统,则不需要添加 ./ 表明在当前目录内:
const fs = require('fs');
在es6之后有了和其他语言一样的import关键字,import是编译时调用,require是运行时调用,因而可以放在文件的任何一个位置(这里其实有个疑惑,import难道不能放在文件的任何一个位置么?)。
一共分为读、写、文件状态获取、读流、写流、pipe几个部分。
读文件:
const fs = require('fs');
//异步读取
//如果是二进制数据,或者没有指定解析格式为utf-8,则会返回Buffer对象,本质是一个数组,数组中的每个元素是16进制值。
fs.read('文件路径','utf-8',function (err,data) {
if(err){
console.log(err);
}else{
console.log(data);
}
});
//同步读取
try {
let data =fs.readFileSync('文件路径','utf-8');
}catch (e) {
console.log(e)
}
//string和buffer之间相互转化
let data2 = Buffer.from(data,'utf-8'); //将字符串转化为二进制
data = data2.toString('utf-8');
//同步写入
fs.write('文件路径','utf-8',function (err) {
console.log(err);
});
//异步写入
try {
fs.writeFileSync('文件路径','utf-8');
}catch(e){
console.log(e);
};
如果文件很大,不可能一次性从磁盘读取到内存中,这个时候就需要使用流的方式每次读一部分。流是一种抽象,就好像从自来水管接一桶水一样。
const rs = fs.createReadStream('文件路径','utf-8');
re.on('data',function (chunk) {}); //读取一段文件
re.on('end',function () {}); //文件读取完毕
re.on('error',function (err) {}); //文件读取出错
const ws = fs.createWriteStream('');
ws.write('','utf-8');
ws.write('','utf-8');
ws.end();
pipe表示管道,如果需要把一个文件的内容读出来,然后写入另一个文件中,就好像在两个文件中建立了一根水管一样,所以叫管道。
过程:使用读流从文件A中读取数据,使用写流将读取的数据写入文件B中
const fs = require('fs');
const rs = fs.createReadStream('文件A','utf-8');
const ws = ts.createWriteStream('文件B');
rs.pipe(ws);
服务器自然离不开网络,这部分用于接收和响应http请求。
最简单的http服务器:
const http = require('http');
//创建服务器
//这里res对象本质是一个writeStream
const server = http.createServer(function (req,res) {
console.log(req.method);
console.log(req.url);
res.writeHead(200,{
'Content-Type':'text-html'
});
res.end('hello world
');
});
server.listen(8080);
文件服务器:
const
fs = require('fs');
url =require('url');
path = require('path');
http = require('http');
//||符号:如果左边表达式为真返回左边表达式,否则返回右边表达式,这里如果行命令启动的时候指定了文件路径,那么使用行命令指定的文件路径,否则使用当前代码文件所在的根目录为文件目录 path.resolve('.') 即是获取当前跟目录
const root = path.resolve(process.argv[2] || '.');
const server = http.createServer(function (req,res) {
//获取浏览器中输入的文件路径,这里的路径不包含host和端口因而不需要特殊处理
const pathname = req.url;
const filePath = path.join(root,pathname);
//判断文件是否存在
fs.stat(filePath,function (err,stat) {
if (err){
res.writeHead('404');
res.end('404 not find');
}else{
res.writeHead('200');
const rs = fs.createReadStream(filePath,'utf-8').pipe(res);
}
});
});
server.listen(8080);
console.log('http://127.0.0.1:8080');
hash: 用户验证文件的正确性,同样的文件生成和哈希值是相同的,如果哈希值不同,那么文件肯定也不一样,因而可以用于校验文件的完整性和正确性,hash算法有许多,常用的如MD5、SHA1、SHA256、SHA512等。
MD5:
const crypto = require('crypto');
const md5 = crypto.createHash('md5');
//输入用户MD5加密的内容,这里可以更新多次
md5.update('hello');
md5.update('world');
//使用16进制(hex)输出
console.log(md5.digest('hex'));
Hmac:
Hmac基于MD5、SHA等哈希算法,区别在于Hmac多了一个秘钥,相同的内容如果秘钥不同,生成的哈希也不一样。
const crypto = require('crypto');
//第二个参数即是秘钥
const hmac= crypto.createHmac('md5','goldfish');
hmac.update('hello');
hmac.update('word');
console.log(hmac.digest('hex'));
对称加密aes
let crypto = require('crypto');
function aesEncrypt(data, key, iv) {
//生成加密器,算法为aes192,秘钥为key,初始向量为iv
//只有key和iv都相同,生成的密文才是相同得1
let cipher = crypto.createCipheriv('aes192', key, iv);
//
let crypted = cipher.update(data,'utf-8','hex');
crypted += cipher.final('hex');
return crypted;
}
function aesDecrypt(encrypt, key, iv) {
let decipher = crypto.createDecipheriv('aes192', key, iv);
let decryped = decipher.update(encrypt,'hex','utf-8');
decryped += decipher.final('utf-8');
return decryped;
}
还有一些加密api用到的时候再查
之前nodejs的web框架为express,但由于express存在callback hell的问题,因此这从KOA2开始学起。
node.js可以使用package.json文件管理包依赖,在js目录下新建该文件,写入如下内容:
{
"name": "hello",
"version": "1.0.0",
"description": "Hello Koa 2 example with async",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"keywords": [
"koa",
"async"
],
"author": "Tang He",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": ""
},
"dependencies": {
"koa": "2.0.0"
}
}
然后同样在js目录下输入npm install即可,这种方式在管理多个包的时候更为方便,其实主要就是最后的dependencies。
使用koa2新建一个简单的web服务器:
const koa = require('koa');
const app = new koa();
//async 表示这里使用的是一个异步函数
app.use(async function (ctx, next) {
//调用下一个async函数
await next();
ctx.response.type = 'text/html';
ctx.response.body = 'hello koa
';
});
app.listen(3000);
koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能,看下面的例子。
const koa = require('koa');
const app = new koa();
app.use(async function(context,next) {
console.log(context.request.url+context.request.method);
await next();
});
app.use(async function(context, next) {
const timeBefore = new Date().getTime();
await next();
const timeAfter = new Date().getTime();
console.log(timeAfter-timeBefore);
});
app.use(async function(context, next) {
await next();
context.response.type = 'text/html';
context.response.body = 'hello koa';
});
app.listen(8000);
这里有三个use,先调用第一个use,在该use中调用next()拿到第二个use,同样在第二个use中调用next拿到第三个。如果第二个或者第一个use中的next没有被调用,后面的代码就不会被执行。
koa-router:
提供了路由相关功能,这里路由的实现其实和iOS中的路由框架很像,如果根据用户的请求路径一个个比较效率很低,底层使用哈希表或者二叉查找树就会简单许多,这里koa-router就提供了路由查找的功能。
const koa = require('koa');
const router = require('koa-router')();
const app = new koa();
//接收到请求后打印请求内容,然后转发给下一个use
app.use(async function(context,next) {
console.log(context.request.url+context.request.method);
await next();
});
//将请求转发给路由,路由会选择接收请求的处理函数
app.use(router.routes());
router.get('/',function (context,next) {
context.response.body = 'hello world
'
});
router.get('/goldfish',function (context,next) {
context.response.body = 'hello goldfish
';
});
app.listen(8000);
koa-bodyparser
上面的代码只能用户处理get请求,如果是post请求呢?请求内容在请求体重,无论是node原生还是koa-router都没有提供body解析的功能,这时候就需要一个解析工具 —— koa-bodyparse:
const koa = require('koa');
const bodyParse = require('koa-bodyparser');
const router = require('koa-router')();
const app = new koa();
app.use(async function(context,next) {
console.log(context.request.url+context.request.method);
await next();
});
//请求先被转发到body解析
app.use(bodyParse());
//解析后的请求被转发给路由处理
app.use(router.routes());
router.get('/', async (ctx, next) => {
ctx.response.body = `Index
`;
});
router.get('/goldfish',function (context,next) {
context.response.body = 'hello goldfish
';
});
router.post('/login',function (context,next) {
const username = context.request.body.name || '';
const password = context.request.body.password || '';
if (username === 'koa' && password === '123'){
context.response.body = 'success!';
} else {
context.response.body = 'login fail';
}
});
app.listen(8000);
上面的代码,如果响应的请求很多,通常不放在一个文件内,分多个文件写,其中请求处理部分称为controller,单独拉一个文件管理。
MVC
模板引擎部分可以使用 Nunjucks: 官方文档
其中 async 的响应函数就是controller,用于处理业务方面的逻辑,比如判断用户是否存在,从数据库或文件系统中读取数据等。模板就是view,用户视图展示。数据从数据库中取出来,拼接成model,再传递给view。