本文主要基于极客时间《Nodejs开发实战》课程。
本篇(一)为课程的第二篇——技术预研篇。
来源官网:
在Chrome里写NodeJS和在NodeJS里写JS有区别吗?
几乎没有区别,因为都是用V8引擎编译和解释,唯一的区别在于:NodeJS中没有浏览器API,没有浏览器环境变量window、document等等,却多了NodeJS API,比如文件系统、进程管理等。
可以直接创建一个js文件试一下,执行Node命令只需要输入node + js名称 即可。
特有常见环境变量:
node版本号version/versions,当前操作系统plantform等
kill / exit 一些管理 / 杀进程操作会使用
hrtime(function:bigint) 时间精度统计
cpuUsage cpu占用率,性能分析时有用
memoryUsage 内存占用率,同上。
env 运行时的环境变量对象集合,Node运行时可以使用env识别改变运行环境,比如正式、测试、本地、debug
argv 用户运行进程时敲击命令记录数组,最新的命令将push到数组的最后一行,如果程序中用到命令行可以使用这个属性。
比如:node index.js test ,也就是以test命令运行node.js,打印argv,最后一个数就是test。
// index.js
const playerAction = process.argv[process.argv.length-1];
const computerAction = Math.random()*3 <1 ? 'rock': Math.random()*3 <2 ? 'scissor':'paper';
console.log(playerAction,computerAction)
const win = {
rock:'paper',
scissor:'rock',
paper:'scissor'
}
if(computerAction === playerAction){
console.log('平局')
}else{
if(win[playerAction] === computerAction){
console.log('电脑胜')
}else{
console.log('玩家胜')
}
}
node index.js scissor 执行,argv取出的最后一个命令(玩家出的)的就是scissor
NodeJS模块中文官方文档
NodeJS模块英文官方文档
NodeJS的内置模块都放置于Node源码中的lib文件夹下。其实Node获取到操作系统相关信息能力并不是JS提供的,而是V8引擎中C++底层提供的方法,Node提供的很多内置方法其实是对Node中C++模块的封装调用,由此实现Node和操作系统之间的交互。
处理二进制数据,用于处理网络协议、TCP文件流、数据库、图片、上传接受文件等。
在Node应用中,需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,要处理大量二进制数据,而Buffer就是在内存中开辟一片区域(初次初始化为8KB),用来存放二进制数据
在上述操作中都会存在数据流动,每个数据流动的过程中,都会有一个最小或最大数据量
如果数据到达的速度比进程消耗的速度快,那么少数早到达的数据会处于等待区等候被处理。反之,如果数据到达的速度比进程消耗的数据慢,那么早先到达的数据需要等待一定量的数据到达之后才能被处理
这里的等待区就指的缓冲区(Buffer),它是计算机中的一个小物理单位,通常位于计算机的 RAM 中
简单来讲,Nodejs不能控制数据传输的速度和到达时间,只能决定何时发送数据,如果还没到发送时间,则将数据放在Buffer中,即在RAM中,直至将它们发送完毕
Buffer.from() 将数据转为Buffer
Buffer.from('test') | ([1,2,3])
Buffer.alloc() 创建一个固定长度的Buffer区间
const buffer = Buffer.from("你好");
console.log(buffer);
//
const str = buffer.toString();
console.log(str);
// 你好
支持的字符集如下:
const http = require('http');
http.createServer( (req,res)=> {
if(req.url === /favicon.ico'){
res.whiteHead(200);
res.end();
return;
}
res.writeHead(200);
res.end('hello');
}).listen(3100)
const http = require('http');
// 返回html,使用fs文件模块输出html文件流
const fs = request('fs)
http.createServer( (req,res)=> {
res.writeHead(200);
fs.createReadStream(__dirname+'/index.html')
.pipe(res)
}).listen(3100)
console.log(req)
,可以发现req对象其实是一个叫做IncommingMessage的对象,而这个对象的详细内容都可以在Node文档官网中找到:网址:http://caibaojian.com/nodejs/api/en/http.html#http_class_http_incomingmessageurl
aborted 是否是中断请求
headers
method在请求url中有一个特殊的url,不是客户端发起的,而是浏览器发起的url,用于请求标签页上的icon ,此url为:/favicon.ico,如果不想这个额外url引起额外的跳转问题,需要注意。比如用户发起一个/page/1页面的请求,实际上服务器会接受到两个请求,一个是/favicon.ico,另一个才是/page/1
console.log(url.parse(req.url))
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/',
path: '/',
href: '/'
}
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ href │
├──────────┬──┬─────────────────────┬─────────────────────┬───────────────────────────┬───────┤
│ protocol │ │ auth │ host │ path │ hash │
│ │ │ ├──────────────┬──────┼──────────┬────────────────┤ │
│ │ │ │ hostname │ port │ pathname │ search │ │
│ │ │ │ │ │ ├─┬──────────────┤ │
│ │ │ │ │ │ │ │ query │ │
" https: // user : pass @ sub.host.com : 8080 /p/a/t/h ? query=string #hash "
│ │ │ │ │ hostname │ port │ │ │ │
│ │ │ │ ├──────────────┴──────┤ │ │ │
│ protocol │ │ username │ password │ host │ │ │ │
├──────────┴──┼──────────┴──────────┼─────────────────────┤ │ │ │
│ origin │ │ origin │ pathname │ search │ hash │
├─────────────┴─────────────────────┴─────────────────────┴──────────┴────────────────┴───────┤
│ href │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
(all spaces in the "" line should be ignored — they are purely for formatting)
有了url对象后,我们就可以比较方便地处理响应路径了
const http = require('http');
const url = require('url);
http.createServer( (req,res)=> {
const parseUrl = url.parse(req.url);
if(parseUrl.pathname === /favicon.ico'){
res.whiteHead(200);
res.end();
return;
}
if(parseUrl.pathname === /test/interface'){
const query = qs(parseUrl.query);
res.end(`${query}_1`);
return;
}
if(parseUrl.pathname === /'){
res.writeHead(200);
res.end('hello');
}
}).listen(3100)
因为Node大部分核心API都采用异步驱动的方式,所以为异步任务“标记命名”和“监听触发”就成了Node中一个非常常见的行为。
比如net.Server 对象会在每次有新连接时触发事件;fs.ReadStream 会在文件被打开时触发事件;流对象 会在数据可读时触发事件。
所有能触发事件的对象都是 EventEmitter 类的实例(从这个角度上来说,任何可以被监听的对象都是EventEmitter实例)。这些对象会继承EventEmitter实例的方法eventEmitter.on(),允许将一个或多个函数绑定到会被对象触发的命名事件上。
例:
// 一个绑定了一个监听器的 EventEmitter 实例。 eventEmitter.on() 方法用于注册监听器,eventEmitter.emit() 方法用于触发事件。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor(){
super();
setInterval(()=>{
this.emit('event',{"参数":"参数值"})
// 测试:process.exit();
},3000)
}
}
const myEmitter = new MyEmitter();
myEmitter.on('event', (res) => {
console.log('触发了一个事件!',res);
});
以上监听和触发的行为,就实现了观察者模式。
emitter.addListener(eventName, listener)
emitter.removeListener(eventName, listener)
emitter.emit(eventName[, …args])emitter.on(eventName, listener) (效果等于addListener
emitter.once(eventName, listener)
事件循环是非阻塞IO的基础,而且非阻塞IO和事件循环是LIBUV的重要组成成分。
Node中的异步事件执行在其他线程中,等到执行完成将消息通知到JS执行主线程进行任务回调处理。
function eventloop() {
const queue = []; // 消息队列,异步任务已经执行完后将结果Push此处
const loop = ()=>{
while(queue.length){
const callback = queue.shift();
callback?.();
}
// 假设50ms一个循环时间片,处理消息队列中的数据
setTimeout(()=>{
loop();
},50)
}
const add = (callback)=>{
queue.push(callback)
}
return {
loop,
add
}
}
const LOOP = eventloop();
LOOP.loop();
setTimeout(()=>{
LOOP.add(()=>{
console.log("500 ms")
})
},500)
setTimeout(()=>{
LOOP.add(()=>{
console.log("800 ms")
})
},800)
Remote Procedure Call(远程过程调用)
以AJAX使用类比举例
Node主要做BFF层(Backend for Frontend)
具体内容请看初识Node第二篇。