nodejs笔记
本笔记根据河北师范大学软件学院王顶老师授课内容及网络资料编写,如果侵权,联系删除~
如有错误欢迎联系更正~
文章目录
nodejs简介,及常见名词解释
(此处的简介可以在后面文章中遇到疑问再返回此处查看)
nodejs是什么
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型。
Node 是一个让 JavaScript 运行在服务端的开发平台。
实质是对Chrome V8引擎进行了封装,使用C++实现。
node主要特征
1、单线程
在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存。也就是说,理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右。要让Web应用程序支持更多的用户,就需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了。
Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4万用户的连接。另外,单线程的带来的好处,还有操作系统完全不再有线程创建、销毁的时间开销。坏处,就是一个用户造成了线程的崩溃,整个服务都崩溃了,其他人也崩溃了。
2、异步I/O
例如,当在访问数据库取得数据的时候,需要一段时间。在传统的单线程处理机制中,在执行了访问数据库代码之后,整个线程都将暂停下来,等待数据库返回结果,才能执行后面的代码。也就是说,I/O阻塞了代码的执行,极大地降低了程序的执行效率。
由于Node.js中采用了非阻塞型I/O机制,因此在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。
当某个I/O执行完毕时,将以事件的形式通知执行I/O操作的线程,线程执行这个事件的回调函数。为了处理异步I/O,线程必须有事件循环,不断的检查有没有未处理的事件,依次予以处理。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。
3、事件驱动
在Node中,客户端请求建立连接,提交数据等行为,会触发相应的事件。在Node中,在一个时刻,只能执行一个事件回调函数,但是在执行一个事件回调函数的中途,可以转而处理其他事件(比如,又有新用户连接了),然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制。
Node.js底层是C++(V8也是C++写的)。底层代码中,近半数都用于事件队列、回调函数队列的构建。
运行 node.js 脚本文件时要省略 node 命令,如何操作
在脚本代码前面加入#!/usr/bin/node,并对脚本文件增加可执行权限
增加 linux 文件的可执行权限 chmod u+x file-name
node的单线程
在开启电脑后,会运行浏览器,微信,视频等软件,然而cpu数量很少,所以使用的时并发的方式,即cpu给不同的进程分配时间片。打开视频,不仅可以有画面,还有音频播放等等,其实是这些进程内的线程在起作用。 一个进程至少要有一个线程。
node和浏览器中的JavaScript都是单线程的。 但是,我们要理解node的单线程到底是什么意思?实际上, 这里所说的单线程是指我们所编写的代码运行在单线程上,实际上node不是真正的单线程。比如我们执行 node app.js 时启动了一个进程,但是这个进程并不是只有一个线程,而是同时创建了很多歌线程(比如:异步IO需要的一些IO线程)。 但是,仍然只有一个线程会运行我们编写的代码。 这就是node中单线程的含义。
但是node单线程会导致下面的问题:
无法利用多核CPU(只能获得一个CPU的时间分片)。
错误就会引起整个应用退出(整个应用就一个进程,挂了就挂了)。
大量计算长时间占用CPU,导致阻塞线程内的其他操作(异步IO发不出调用,已完成的异步IO回调不能及时执行)。
http请求协议
http介绍
根据HTTP标准,HTTP请求可以使用多种请求方法。例如:HTTP1.1支持7种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。在Internet应用中,最常用的方法是GET和POST。
http请求头
- 起始行:HTTP请求方法 URL HTTP版本
- 请求头:请求头的形式通过一个键值对进行渲染
- 请求体:get方法的请求体是没有内容的(放在了url里) post方法的请求体包含请求的内容
/*起始行*/
GET /sample.jsp HTTP/1.1
/*请求头*/
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
/*请求体*/
username=jinqiao&password=123412345678
http响应头
- 起始行:HTTP协议版本 响应状态码 响应状态信息
- 响应头(Response Header) :通过键值对的形式进行表示
- 响应体: 网页代码 HTML、CSS、JS代码文件
HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112
<html>
<head>
<title>HTTP响应示例<title>
</head>
<body>
Hello HTTP!
</body>
</html>
http应答码
HTTP应答码也称为状态码,它反映了Web服务器处理HTTP请求状态。HTTP应答码由3位数字构成,其中首位数字定义了应答码的类型:
- 1XX-信息类(Information),表示收到Web浏览器请求,正在进一步的处理中
- 2XX-成功类(Successful),表示用户请求被正确接收,理解和处理例如:200 OK
- 3XX - 重定向类(Redirection),表示请求没有成功,客户必须采取进一步的动作。
- 4XX - 客户端错误(Client Error),表示客户端提交的请求有错误 例如:404 NOT Found,意味着请求中所引用的文档不存在。
- 5XX - 服务器错误(Server Error)表示服务器不能完成对请求的处理:如 500
curl测试http协议
curl http://sample.wangding.in/web/one-div.html 显示出响应体
curl http://sample.wangding.in/web/one-div.html -v 显示所有响应内容、请求内容
跨域问题及代理
浏览器由于同源策略的原因,不同域名之间发送ajax请求,响应的数据不会被浏览器加载。而服务器向服务器发送请求则没有同源策略的限制
解决跨域问题的方法
1、jsonp
2、cors
3、配置代理服务器。
常用模块
见此博客
nodejs基础&全局api
全局api注意点
1.不需要require引入,可以直接使用
路径变量
__filename:展示当前的文件名称,从主目录开始的绝对路径
__dirname:展示当前文件所在的文件目录的名称,从主目录开始的绝对路径
//写在app.js文件中
console.log(__dirname);//C:\学习\大三\nodejs\nodejsTest
console.log(__filename);//C:\学习\大三\nodejs\nodejsTest\app.js
控制台变量
方法 | 描述 |
---|---|
console.log() |
向标准输出流打印字符并以换行符结束 |
console.info() |
向标准输出流打印信息性消息。输出文字外,会显示一个蓝色的惊叹号。 |
console.warn() |
向标准输出流打印警告性消息。输出文字外,会显示一个黄色的惊叹号。 |
console.error() |
向标准输出流打印错误性消息。输出文字外,会显示一个红色的叉子。 |
console.time() |
可通过此方法对方法进行时间采样 |
输出变量的三种方式
const user = {
name:"xiaweixuan",
age:21,
qq:"1977541709"
};
//method1
log("name: %s",user.name); // string类型
log("age: %d",user.age); // number类型
log("user: %j",user);// json字符串
//method2
log("qq: " + user.qq);// 字符串拼接 通过+进行连接
//method3
log(`qq: ${
user.qq}`);
console.time时间采样
function longTask(){
var n = 0;
for(var i = 0;i<1000;i++){
for(var j = 0;j<1000;j++){
n = n + i*j;
}
}
}
console.time("test");// 确定任务开始
longTask();
console.timeEnd("test");// 确定任务结束 两个console的参数必须是一样的
## 输出结果
test:91.867ms // 每次测试的结果是不同的
进程对象
属性或方法 | 描述 |
---|---|
process.arch |
CPU 架构信息 |
process.platform |
操作系统版本信息 |
process.pid |
打印进程 id 信息。 |
process.execPath |
Node.js 可执行文件的绝对路径信息 |
process.stdin.pause() |
在脚本中让程序暂停执行 |
process.version |
nodejs版本信息 |
process.getuid() |
当前用户id |
process.getgid() |
当前用户组id |
process.cwd() |
当前脚本路径信息 |
process.memoryUsage().rss |
系统的常驻内存大小 |
process.memoryUsage().heapTotal |
v8动态分配的总内存大小 |
process.memoryUsage().heapUsed |
v8动态分配的已用内存大小 |
process.memoryUsage().external |
查看 v8 管理的绑定到 JS 对象上的 C++ 对象的内存 |
process.env |
查看环境变量 |
process.argv |
是一个字符串数组 头两个字符串 第一个字符串是node 第二个参数是运行的脚本的绝对路径,之后为命令行参数 |
process.exit(n) |
设置退出码 |
process.stdin.on() |
用于标准输入输出流 |
process.on() |
用于自定义接受信号 |
process.kill(pid,sig); |
结束指定进程 |
退出码
退出码实质上是给程序看的,在Linux程序中,有时候子进程会输出退出码,父进程通过查看子进程输出的退出码来判断是否正确
在退出码中,0表示的是成功,1表示的是错误
var code = process.argv[2];
if(typeof code === 'number'){
console.log("命令行参数类型不符合!");
process.exit(1);
}
if(process.argv.length <3){
console.error("请给出命令行参数!");
process.exit(1);
}
process.exit(code);
// process.exit(code:number) ?表示可以有也可以表示没有
// 获取退出码 echo $?
标准输入输出流
读取用户键盘输入信息,保存到对象中
用户键盘输入结束后,打印完整的对象信息
#!/usr/bin/node
const msg = ['name','email','qq','mobile'];
var i = 0,
me = {
};
console.log(msg[i] + ':');
process.stdin.on('data',(data)=>{
me[msg[i]] = data.slice(0,data.length-1).toString('utf-8');// slice 是将回车进行去掉
i++;
if(i == msg.length){
console.log(me);
process.exit();
}
console.log(msg[i] + ':');
});
自定义接受信号
第一个参数为监听的时间名字,后面为回调函数
接收信号量,并对信号(SIGINT 和 SIGTSTP)进行处理
process.stdin.resume();
process.on('SIGINT',()=>{
console.log('you have pressed Ctrl+C');
process.exit();
})
process.on('SIGTSTP',()=>{
console.log('you have pressed Ctrl+Z');
})
实现my-kill程序
```javascript
const pid = process.argv[2];
const sig = process.argv[3];
process.kill(pid,sig);
定时器
setTimeout(func(),0)setImmediate(func())代表主线程完成后立即执行,其执行结果是不确定的,可能是setTimeout回调函数执行结果在前,也可能是setImmediate回调函数执行结果在前,但setTimeout回调函数执行结果在前的概率更大些,这是因为他们采用的观察者不同,setTimeout采用的是类似IO观察者,setImmediate采用的是check观察者,而process.nextTick()采用的是idle观察者。
buffer
什么是buffer
数据在计算机中是以二进制存储的,不论是图片、视频哪怕只是简单的字符串都是要先转化为二进制数字去存储的,而具体的转化方式就叫做编码。如将字符转化为二进制,用多少位数字来表示一个字符,这就叫做字符编码。
在编程中,我们常常会需要将一系列二进制数据从一处传到另一处,我们传输数据往往是为了处理它,或者读它,或者基于这些数据做处理等。而且每次处理量是固定的,例如我可能一次就要处理1000个二进制数据。
但是,在每次传输过程中,有一个数据量的问题。因此当数据到达的时间比数据理出的时间快的时候,这个时候我们处理数据就需要等待了。
如果处理数据的时间比到达的时间快,这一时刻仅仅到达了一小部分数据,那这小部分数据需要等待剩下的数据填满,凑够了1000个二进制数据,然后再送过去统一处理。这个”等待区域”就是buffer。它是你电脑上的一个很小的物理地址,一般在RAM中,在这里数据暂时的存储、等待,最后在流(stream)中,发送过去并处理。
buffer虽然作为缓冲区,在stream中,Node.js会自动帮你创建buffer之外,你可以创建自己的buffer并操作它。
//buffer基本操作
var buf1 = new Buffer(256);//实例化一个 buffer 对象 buf1,缓冲区的大小是 256 字节
console.log(buf1)//<Buffer 00 00 00 00 00 ...>
console.log(buf1.length)//256
var buf2 = buf1.slice(246,257);//对 buf1 做切片操作,取出后 10 个字节,存放到 buf2 中
buf1.fill(1)//<Buffer 01 01 01 01 01 ...>
var arr = ['a',0xBA,0xDF,0x00,0x00,255,10];
var buf3 = new Buffer(arr,'utf-8');
buf2.copy(buf3);//复制buf3的内容到buf2中
字符串的编码有字符编码如ASCII,而文件编码在计算机中,有两种方式,一种方式是通过文本文件进行编码(例如:utf-8 ascii utf16le),另一种方式是通过二进制文件进行编码的(例如:base64 lantin1(二进制) hex)
对编码的理解:不论是字符串还是图片、音频等,在计算机中都是以二进制的形式去存储,不同的编码不过是把二进制翻译成字符串的规则不同而已。比如有的编码是8个二进制数表示一个字符,有的是16个二进制数表示一个字符。也即是说同样是存储‘abc’这个字符串,各种编码把他们转化成的二进制不相同,所以在解析二进制代码的时候,也一定要用相对应的编码方式去解码。此外,本文文件编码多指对字符串、文件的编码,二进制文本编码多指对图片、音频的编码。
在buffer的原型中存在着对应的编码的方法
//buffer编码
var str = 'xiaweixuan';
var buf = new Buffer(str);
console.log(buf.toString('base64'));//eGlhd2VpeHVhbg==
//buffer解码
var str = 'eGlhd2VpeHVhbg==';
var buf = new Buffer(str,"base64");
console.log(buf.toString('utf-8'));//xiaweixuan
模块管理
JS 模块化的两种方案分别是:AMD 和 CommonJS
AMD 规范的主要内容:AMD是异步模块加载机制。从它的规范描述页面看,AMD很短也很简单,但它却完整描述了模块的定义,依赖关系,引用关系以及加载机制。define和require这两个定义模块、调用模块的方法,合称为AMD模式
CommonJS规范的主要内容:模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。
将模块发布到npm
1、npm init //初始化环境
2、npm login
3、npm publish
npm init后
产生 package.json文件,记录当前环境配置。其中需要注意"devDependencies" 和"dependencies" ,其中前者是开发时以来的npm模块,后者是自己所写的打包成的应用程序所需要的npm模块
上传git时注意
创建.gitignore文件书写,里面指定的文件不会上传到git上
# dependencies
/node_modules
此外在模块中,使用module.exports = circle
导出,通过var circle = require('./02-export-function')
导入
使用全局对象 global
// 03-global.js
global.pi = Math.PI;
global.circle = (radius)=>{
return {
circumference:function(){
return 2*Math.PI*radius;},
area:function(){
return Math.PI*radius*radius;}
}
}
global.circleobj = {
circumference:function(radius){
return 2*Math.PI.radius},
area:function(radius){
return Math.PI*radius*radius}
}
// 03-main.js
require('./03-global.js');
console.log(global.pi);
console.log(global.circle(20).circumference());
console.log(global.circleobj.area(20));
事件
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。
events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。可以通过require(“events”);来访问该模块。
// 引入 events 模块
var events = require('events').EventEmitter;
// 创建 eventEmitter 对象
var eventEmitter = new events();
方法 | 描述 |
---|---|
evt.on() |
监听事件 |
evt.emit() |
发出事件 |
var EventEmitter = require('events').EventEmitter;
var evt = new EventEmitter();
evt.on('hello',()=>{
console.log('hello');
});
/*一个事件可以有多个执行函数(订阅者)*/
evt.on('hello',()=>{
console.log('HELLO');
})
evt.on('bye',()=>{
console.log('bye');
process.exit();
});
global.setInterval(()=>{
evt.emit('hello');
},500);// 在触发hello事件的时候,有两个执行的函数,两个执行的函数会依次执行
global.setTimeout(()=>{
evt.emit('bye');
},3000);
继承EventEmitter对象
1、在构造函数中EventEmitter.bind(this)
2、ClassName.prototype = EventEmitter.prototype
之后ClassName实例出来的对象便继承了on方法
利用util设置继承
util = new require('util');
util.inherits(ClassName,EventEmitter);
流
什么是流
比如说我们将水从一个杯子倒入另一个杯子中,相当于水的位置发生了改变,即从一个杯子到了另一个杯子中,这实质上是每个水分子一个一个发生了位移中,而我们通常把流动着的水成为水流。
同理,数据在计算中,如果想从内存的一处移动到另一处,我们可以想成是每个字符在移动,这便成为字符流
此外,流是一种机制,我们不能说流有哪些方法,我们要理解为很多方法的实现基于流
标准输入流
敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,这就是标准输入流(stdin)。
标准输出流
如果应用程序把字符一个一个输出到显示器上,这就是标准输出流(stdout)。
为什么需要流?
在node中读取文件的方式有来两种,一个是利用fs模块,一个是利用流来读取。如果读取小文件,我们可以使用fs读取,fs读取文件的时候,是将文件一次性读取到本地内存。而如果读取一个大文件,一次性读取会占用大量内存,效率很低,这个时候需要用流来读取。流是将数据分割段,一段一段的读取,效率很高。
process中基于流的方法 | 描述 |
---|---|
process.stdin.on() |
监听数据流 |
process.stdout.write() |
写入输出流 |
//将输入的字符串转换为大写输出
process.stdin.resume();
process.stdin.setEncoding('utf-8');
process.stdin.on('data',(data)=>{
process.stdout.write(data.toUpperCase());
});
process.stdin.on('end',()=>{
process.exit();
})
fs中基于流的方法 | 描述 |
---|---|
fs.redFileSync(file).pipe(res) |
将文件数据写入输出流 |
//基于res.end的静态服务文件
const http = require('http')