如何学习:
- 看官方资料
- 看github代码
- 笔记不要用笔 最好就是思维导图
nodeJs常常放在前面说的套话
nodejs是一个异步的事件驱动的进行时(runtime)。
Ryan Dahl是一??深的C/C 程序?,在创??Node之前,他的?要工作都是??高性能 Web服务???的。经?过一些?试???之后,他?到了??高性能,Web服务?的几个要??点: 事件?驱动、异步???I/O。 JavaScript?C的开发门槛?要低?,比?Lua的??的历史包袱?要少?。?尽管管服务?端JavaScript ?在已经很多年了,?是后端部分?没有?市场,可以历史包袱????为?0;另外,JavaScript有广?泛的事件?驱动??的应用,暗合??Ryan Dahl喜好;同时,Chrome???的JavaScript? ?V8??引擎横空出世,?在性能方面拿下了第一。自然受??到Ryan Dahl的关注。?? 高性能、??事件?驱动、没有??历史包袱?这3?个?要?因,使得JavaScript?为了Node的实现语言。
node的特点:
-
异步i/o??:在Node中,绝大多数的?操作都是异步的,比如读取文件,数据库等。简单说就是是
???Don’t call me,I will call you
?的??的,这也是????关心结果,不关心过程???。 - 事件驱动,这个和前端一样。
- 单线程:线程之间无共享状态。
- 跨平台(Linux/Windows)
服务端和客户端的JavaScript:
核心语法都是ECMAScrit,比如数据类型,语法结构,内置对象等等。
前端关心的是浏览器的bom和dom。node关注的是操作系统(fs,net,database,buffer,event,os)
客户端JavaScript的顶层(this)是window;在node中不存在window,( console.log(this)
的结果是 {}
),它的顶层是全局对象(global),但是,当在node.js中定义一个a时。通过global.a却访问不了a(undefined)。
快速上手
不用说,第一个是hello world。创建一个app.js
代码语言:javascript复制console.log('hello word')
node app.js
即可运行。
以往有个很不好的体验就是,每次修改都需要重启node服务。应用工具 nodemon
可以解决这个问题。
sudo npm i nodemon -g
模块化
先看老例子:
代码语言:javascript复制var a=100
这个a属于当前js模块,并不是全局变量。一个文件就是一个模块。每个模块都有自己的作用域。
我们使用var 声明的一个变量,他并不就是全局的,而是属于当前模块。
你想声明一个全局变量,必须 global.a=100
。
每个文件都有一个独特的__filename属性:
代码语言:javascript复制//__filename:当前文件被解析之后的绝对路径(双下划线)
console.log(__filename); //打出了当前文件的路径
模块化导出遵循commonjs规范:
打印内存占用
代码语言:javascript复制// os是一个内置对象
const os =require('os')
const mem=(os.freemem()/os.totalmem()*100).toFixed(2)
console.log(`内存占用${mem}%`)
打印cpu占用:用更精准的依赖来实现:
代码语言:javascript复制npm i cpu-stat -s
代码语言:javascript复制const cpuStat=require('cpu-stat')
cpuStat.usagePercent((err,percent)=>{
console.log(`cpu占用${percent.toFixed(2)}%`)
})
这种回调其实是不好看的。如何更加优雅呢?现在node提供了util类
node的旧有api大都是用回调实现。
util有提供 promisify
方法,提供类似promise的方法。
const cpuStat=require('cpu-stat')
const getCpu=util.promisify(cpuStat.usagePercent)
getCpu().then((percent)=>{
console.log(`cpu占用${percent.toFixed(2)}%`)
}).catch((err)=>{
console.log(err)
})
那么如何导出呢?
代码语言:javascript复制// app.js
const util=require('util')
// util.promisify
const os =require('os')
const cpuStat=require('cpu-stat')
const getCpu=util.promisify(cpuStat.usagePercent)
const showState=async ()=>{
const mem=(os.freemem()/os.totalmem()*100).toFixed(2)
console.log(`内存占用${mem}%`)
const percent=await getCpu()
console.log(`cpu占用${percent.toFixed(2)}%`)
}
module.exports ={
showState
}
在新的js文件中使用:
代码语言:javascript复制const showState=require('./app').showState;
showState();
引入导出方法很 low是吧。这是原生node一直没有解决的问题。
我们可以导入babel,但是实际上效率很低。因此不推荐。
以上操作在前端工程化的实践中已经多次用到,所以没什么难的。
文件系统(fs,File System)
nodejs为操作文件提供了大量的api,它使用的是fs模块。文件操作都有两个方法,分别是同步和异步版本。
node使用流(stream)的方式来处理文件,
代码语言:javascript复制const fs=require('fs')
const data =fs.readFileSync('app.js')
console.log(data.toString())
异步版本(readFile)
代码语言:javascript复制const fs=require('fs')
const path=require('path')
const data =fs.readFileSync('app.js')
fs.readFile(path.resolve('./app.js'),(err,data)=>{
console.log(data.toString())
})
buffer
用于处理二进制的对象,类似数组。
代码语言:javascript复制var bf=new Buffer();
又比如:
代码语言:javascript复制const buf1=Buffer.alloc(10)//分配10个字节的内存
// 转化为asc码
const buf2=Buffer.from('a')
console.log(buff2)
//把字符串转化为buffer
const buf3=Buffer.from('中国')
console.log(buf3)
// 把buffer转化为字符串
const buf4=Buffer.concat([buff2,buf3])
console.log(buf4.toString())
作简单了解即可。
stream流
大禹率领民众,与自然灾害中的洪水斗争,最终获得了胜利。面对滔滔洪水,大禹从鲧治水的失败中汲取教训,改变了"堵"的办法,对洪水进行疏导,体现出他具有带领人民战胜困难的聪明才智;大禹为了治理洪水,长年在外与民众一起奋战,置个人利益于不顾,"三过家门而不入"。大禹治水13年,耗尽心血与体力,终于完成了治水的大业。
读写一个文件,比如说把1.jpg的内容复制到2.jpg,如果图片大到几个M就很吃力了。但假设这样一个模型:我通过 createReadStream
创建一个从1.jpg读取的管道,再通过 createWriteStream
生成一个写入到2.jpg的管道。最后通过 pipe
把这两个渠道连接起来,node在这个过程中只起到连接的作用。就实现了一个流(stram)。
这跟大禹治水的思路是一样的。流的精髓在于疏导。
比如说我把app.js的内容全部复制到app2.js:
代码语言:javascript复制const rs=fs.createReadStream('./app.js')
const ws=fs.createWriteStream('./app2.js')
rs.pipe(ws)
很快就实现了。同样的你可以操作图片:
代码语言:javascript复制const rs2=fs.createReadStream('./1.jpg')
const ws2=fs.createWriteStream('./2.jpg')
rs2.pipe(ws2);
http
相信接触node的人90%都是从这里开始的:
代码语言:javascript复制const http=require('http')
const server=http.createServer((req,res)=>{
res.end('hello world')
})
server.listen(3000,(err)=>{
if(!err){
console.log('服务器已启动。。')
}
})
res是什么
res究竟是什么?我们从原型链的角度来看。
定义一个函数:获取一个对象的原型链:
代码语言:javascript复制const getPrototypeChain=(obj)=>{
const protoChain=[];
while(obj=Object.getPrototypeOf(obj)){
protoChain.push(obj)
}
protoChain.push(null);
return protoChain;
}
上图是倒序打印出res对象的原型链。而重点在于:原型链上有 stream
完善功能
这种服务器实在是太过于简单了。不妨加点功能。
- 访问根路由时得到页面
- 访问
/users
得到接口数据
首先,在同目录下写一个index.html
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>home</title>
</head>
<body>
<h1>hello world</h1>
</body>
</html>
然后在路由中做判断:
代码语言:javascript复制const http=require('http')
const fs=require('fs')
const server=http.createServer((req,res)=>{
// console.log('res',getPrototypeChain(res))
const {url,method}=req
if(url=='/'&&method=='GET'){
fs.readFile('index.html',((err,data)=>{
res.statusCode='200'
res.setHeader('Content-Type','text/html')
res.end(data)
}))
}else if(url=='/users'&&method=='GET'){
res.writeHead(200,{
'Content-Type':'application/json'
})// 和上面的写法等效
res.end(JSON.stringify([
{
username:'12325'
}
]))
}
})
server.listen(3000,(err)=>{
if(!err){
console.log('服务器已启动。。')
}
})
那么功能就有了。
读取多媒体(图片,视频)
很自然的,想到图片的话,就在index.html里写一个图片标签:
代码语言:javascript复制<h1>hello world</h1>
<img src="2.jpg" alt="">
结果一运行就悲剧啦,图片一直处于pending状态,也不报错。
想到res是一个流。完全可以把图片导向res里面去!
在req对象中解构出headers。从 headers.accept
找到图片请求。
const http=require('http')
const fs=require('fs')
const server=http.createServer((req,res)=>{
console.log('res',getPrototypeChain(res));
const {url,method,headers}=req;
if(url=='/'&&method==='GET'){
fs.readFile('index.html',((err,data)=>{
res.statusCode='200'
res.setHeader('Content-Type','text/html')
res.end(data)
}));
}else if(url=='/users'&&method==='GET'){
res.writeHead(200,{
'Content-Type':'application/json'
});
res.end(JSON.stringify([{ username:'12325'}]));
}else if(method==='GET'&&headers.accept.indexOf('image/*')!=-1){
fs.createReadStream(`.${url}`).pipe(res);
}
})
实"操":实现一个简单的express服务器
Express是一套基于 Node.js 平台,快速、开放、极简的 Web 开发框架。首先看下使用方法:
代码语言:javascript复制npm install express --save
代码语言:javascript复制const express = require('express')
const app = express()
app.get('/', (req, res) => res.end('Hello World!'))
app.get('/users', (req, res) => res.end(JSON.stringify([{username:'djtao',job:coder}])))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
嗯,不多不少,大致就是这点功能。
和往常一样,这个简化版的express就叫 dexpress
吧。
结构
代码语言:javascript复制const http=require('http')
const server=(callback)=>{
return http.createServer(callback);
}
// 简化版express
class Dexpress{
constructor(params) {
this.server=server((req,res)=>{
const {url,method,headers}=req;
// ...
})
}
get(url,callback){
}
//listen方法已经实现
listen(port,callback){
this.server.listen(port,callback)
}
}
module.exports=()=>{
return new Dexpress()
};
那么listen方法已经实现了。
express-router的实现
问题在于get。考虑以工厂模式建立诸多到switch判断。
每当执行一次get,就往工厂中添加一条规则,到执行时(listen)才拿出来用:
代码语言:javascript复制const http=require('http')
const url=require('url')
const server=(callback)=>{
return http.createServer(callback);
}
// 简化版express
class Dexpress{
constructor(params) {
this.router=[];
}
get(url,callback){
if(typeof url=='string'){
let rule={url,callback,method:'GET'};
this.router.push(rule);
}
}
listen(port,callback){
// 调用
const _server=server((req,res)=>{
const {pathname}=url.parse(req.url,true);
this.router.forEach(x=>{
if(pathname===x.url&&req.method===x.method){
x.callback(req,res);
}
})
});
_server.listen(port,callback)
}
}
module.exports=()=>{
return new Dexpress()
};
眼看大楼建起,眼看它崩了( process.on
)
假如你在get回调函数中使用了一个不存在的方法:
代码语言:javascript复制app.get('/abc', (req, res) => abc())
当你尝试访问abc,那么程序就会崩溃。这是开发者所不希望看到的。
在原生node中有一个 process.on
方法,可以守护你的进程即使报错也不崩溃:
process.on('uncaughtException',(err)=>{
console.error(err)
})
如果把这个功能整合到dexpress中,就好了。
奔溃这是一个事件(event)所以让你的dexpress继承它的内容即可:
代码语言:javascript复制const {EventEmitter}=require('events')
class Dexpress extends EventEmitter{
constructor(params) {
super(params)
this.router=[];
}
// get ...
listen(port,callback){
//...
process.on('uncaughtException',(err)=>{
console.error(err)
})
}