基础概念

Node环境和浏览器环境区别

1.内置对象不同

  • 浏览器环境中全局对象为window;nodejs环境中叫global

2.this默认指向不同

  • 浏览器环境中全局this默认指向window;nodejs环境中全局this默认指向空对象{}

3.API不同

  • 浏览器环境中提供了操作节点的DOM相关API和操作浏览器的BOM相关API
  • nodejs环境中没有html节点也没有浏览器,所有nodejs环境中没有DOM和BOM

什么是模块?

1.浏览器开发中的模块

在浏览器开发中为了避免命名冲突,方便维护等等,我们采用类或者立即执行函数的方式来封装js代码,来避免命名冲突和提升代码的维护性。其实这里的一个类或者一个立即执行的函数就是浏览器开发中的一个模块。

  • 存在的问题:没有标准,没有规范
1
2
3
4
5
6
7
let obj = {
//模块中的业务逻辑代码
};

;(function(){
//模块中的业务逻辑代码
})();

2.NodeJS开发中的模块

nodejs采用CommonJS规范实现了模块系统。

  • CommonJS规范规定了如何定义一个模块,如何暴露(导出)模块中的变量函数,以及如何使用定义好的模块。
  • 在CommonJS规范中一个文件就是一个模块
  • 在CommonJS规范中每个文件中的变量函数都是私有的,对其他文件不可见
  • 如果想公有使用,变量函数必须通过 exports 暴露(导出)之后其它文件才可以使用
  • 使用其它文件暴露的变量函数必须通过 require() 导入模块才可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// A文件 A.js
let name = "zs";
function sum(a,b){
return a+b;
}

exports.xx1 = name;
exports.xx2 = sum;

//B文件 B.js
let aModule = require("./A")
console.log(aModule); //{xx1: 'zs', xx2: ƒ}
console.log(aModule.xx1); // zs
console.log(aModule.xx2(10,5)); //15

导出数据的几种方式

导出有三种方式,注意每一种都需要 require() 接收!

1
2
3
4
5
6
7
8
9
10
11
//
exports.xx1 = name;
exports.xx2 = sum;

//
module.exports.xx1 = name;
module.exports.xx2 = sum;

//不符合CommonJS规范,不推荐
global.xx1 = name;
global.xx2 = sum;

exports 与 module.exports区别

  • exports 只能通过 exports.xx 方式导出,不能直接赋值
  • module.exports 既可以通过module.exports.xxx 方式导出,又可以直接赋值
  • 企业开发中无论哪种方式都不要直接赋值,这个问题只会在面试中出现
1
2
3
4
5
exports.xx= name;
exports= name;//直接赋值,不能

module.exports.xx2 = sum;
module.exports = sum; //直接赋值,可以

Require注意点

1.require导入模块时可以不添加导入模块的类型

  • 如果没有指定导入模块的类型,依次查找.js .json .node文件
  • 无论是三种类型的哪一种,导入之后都会转成js对象返回给我们

2.导入自定义模块时必须指定路径

  • require可以导入“自定义模块” 、“系统模块(核心模块)” 、 “第三方模块”
  • 导入自定义模块时前面必须加上路径
  • 导入系统模块和第三方模块是不用添加路径

什么是包?

前面说过在便携代码的时候尽量遵守单一原则,也就是一个函数尽量只做一件事情。例如:读取数据函数/写入数据函数/生成随机数函数等等,不要一个函数即读取数据又写入数据又生成随机数,这样代码非常容易出错,也难以维护。

在模块化开发中也是一样,在一个模块(一个文件)中尽量只完成一个特定的功能。但是有些比较复杂的功能可能需要多个模块组成。例如:jQuery选择器相关的代码在A模块,CSS相关的代码在B模块,我们需要把这些模块组合在一起才是完整的jQuery。那么这个时候我们就需要一个东西来维护多个模块之间的关系,这个维护多个模块之间关系的东西就是“包”。

在NodeJS中,为了方便开发人员发布,安装和管理包,NodeJS推出了一个包管理工具:NPM(Node Packge Manager)

NPM不需要我们单独安装,只要搭建好NodeJS环境就已经自动安装好了。NPM就相当于电脑上的应用商店,通过NPM我们就可以快速找到我们需要的包、快速安装我们需要的包、快速删除我们不想要的包等等。

NPM使用

1
2
3
4
5
6
7
8
9
10
11
12
13
//全局安装、删除、更新(一般用于安装全局使用的工具,存储在全局node_modules中)
//npm config list 查看全局存储位置
npm install -g 包名
npm uninstall -g 包名
npm update -g 包名

包名 --version //查看包版本


//本地安装、删除、更新 (一般用于安装当前项目使用的包,存储在当前项目node_modules中)
npm install 包名
npm uninstall 包名
npm update 包名

本地安装

1
2
3
4
5
6
7
//初始化本地包
npm init //初始化package.json文件 自定义设置里面的每一项
npm init -y //初始化package.json文件,采用默认设置

//安装包
npm install 包名 //安装生产环境包 dependencies
npm install --save-dev //安装开发环境包 devDependencies

包描述文件package.json 定义了当前项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。

npm install 命令根据这个配置文件,自动下载所需要的模块,也就是配置项目所需要的运行和开发环境。

  • npm i 所有的包都会被安装
  • npm i --production 只会安装 dependencies 中的包

加速资源下载

npm 默认去国外下载资源,比较慢,可以全局安装 NRM 这个包,允许你切换资源下载地址

1
2
3
4
5
6
7
8
//安装nrm
npm install -g nrm
//查看是否安装成功
nrm --version
//查看允许切换的资源地址
nrm ls
//将下载资源切换到淘宝(根据上一步查看可用地址的名称)
npm use taobao

不想安装nrm的话,直接在npm中设置

1
2
3
4
//设置资源下载地址
npm config set registry https://registry.npm.taobao.org
//查看下载资源地址是否成功
npm config get registry

常用核心API

NodeJS中文文档 www.nodeapp.cn

Buffer

Buffer是NodeJS全局对象上的一个类,是一个专门用于存储字节数据的类。NodeJS提供了操作计算机底层API,而计算机底层API只能识别0和1,所以就提供了一个专门用于存储字节数据的类。

  • 00就是2bit

  • 00000000 就是8bit,就是一个字节(1B,1Byte)

  • 1B = 8 bit

  • 1KB = 1024 B

  • 1MB = 1024 KB

  • 1GB = 1024 MB

创建Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建一个指定大小的Buffer
//size 存储多少字节的数据(B);fill 初始化一些数据 ;encoding 编码方式,默认 UTF-8
Buffer.alloc(size[,[fill[,encoding]])

let buf = Buffer.alloc(5);//创建一个5字节大小的数据
//通过console.log();输出Buffer会自动将存储的内容转换成16进制再输出
console.log(buf);//<Buffer 00 00 00 00 00>


//根据数组/字符串创建Buffer
var buf = Buffer.from("abc");
//字母a对应的ASCII值为97,十进制97转为16进制是61
console.log(buf);//<Buffer 61 62 63>


//Buffer的本质就是一个数组
var buf = Buffer.from([1,3,5]);
console.log(buf); //<Buffer 01 03 05>
buf[0] = 9;
console.log(buf); //<Buffer 09 03 05>

Buffer实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//toString 将二进制数据转换为字符串转换给我们
var buf1 = Buffer.from([97,98,99]);
console.log(buf1); //<Buffer 61 62 63>
console.log(buf1.toString());//abc


//write(string[, offset[,length]][,encoding]) 写入数据
//string 要写入的字符串;
var buf2 = Buffer.alloc(5);
console.log(buf2); //<Buffer 00 00 00 00 00>
buf2.write("abcdefg");
console.log(buf2.toString()); //abcde
//写入之前要跳过的字节数(从索引x开始);
var buf3 = Buffer.alloc(5);
console.log(buf3); //<Buffer 00 00 00 00 00>
buf3.write("abcdefg", 2);
console.log(buf3.toString()); //abc
//length要写入x个字节数
var buf4 = Buffer.alloc(5);
console.log(buf4); //<Buffer 00 00 00 00 00>
buf4.write("abcdefg", 2, 2);
console.log(buf4.toString()); //ab


//slice 截取数据
var buf5 = Buffer.from("abcdefg");
var buf6 = buf5.slice(2,4);//从索引2开始,截到索引4(不包括)
console.log(buf6); //<Buffer 63 64>
console.log(buf6.toString()); //cd

Buffer静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Buffer.isEncoding() 检查是否支持某种编码格式
console.log(Buffer.isEncoding("gbk")); //false
console.log(Buffer.isEncoding("UTF-8"));//true


//Buffer.isBuffer() 检查是否是Buffer类型对象
var obj = {}
var buf = Buffer.alloc(5);
console.log(Buffer.isBuffer(obj), Buffer.isBuffer(buf)); //false true


//xxx.length 获取字节长度
var buf1 = Buffer.from("123");
var buf2 = Buffer.from("abc");
var buf3 = Buffer.from("百度");//中文占3个字节
console.log(buf1.length, buf2.length, buf3.length);//3 3 6


//Buffer.concat() 拼接Buffer
var buf1 = Buffer.from("123");
var buf2 = Buffer.from("abc");
var buf3 = Buffer.from("百度");
let res = Buffer.concat([buf1, buf2, buf3])
console.log(res);//<Buffer 31 32 33 61 62 63 e7 99 be e5 ba a6>
console.log(res.toString());//123abc百度

Path

Path是一个系统模块,Buffer模块已经自动添加到了global,所以使用的时候不用手动导入。而Path模块必需要手动导入才能使用。

  • path 模块提供了一些工具函数,用于处理文件与目录的路径。
1
2
//导入Path模块
let path = require('path'); //系统模块不能添加路径

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//path.basename() 用于获取路径的最后一个组成部分
console.log(path.basename("a/b/c/d/index.html"));// index.html
console.log(path.basename("a/b/c/d/"));// d

var res = path.basename("a/b/c/d/index.html", ".html");//过滤(剔除)规则
console.log(res);// index


// //path.dirname() 获取路径中的目录,也就是除了最后一个部分以外的内容
console.log(path.dirname("a/b/c/d/index.html"));// a/b/c/d
console.log(path.dirname("a/b/c/d/"));// a/b/c


// //path.extname() 获取路径中最后一个组成部分的扩展名
console.log(path.extname("a/b/c/d/index.html"));// .html
console.log(path.extname("a/b/c/d"));// (空,无)


//path.isAbsolute() 判断是否是绝对路径
console.log(path.isAbsolute("/a/b/c/d/index.html")); //true
console.log(path.isAbsolute("a/b/c/d/index.html")); //false
console.log(path.isAbsolute("./a/b/c/d/index.html")); //false
//注意点:在Linux系统中 /开头就是绝对路径;在windows系统中盘符开头就是绝对路径
console.log(path.isAbsolute("c:\\a\\b\\c\\d\\index.html")); //true


//path.sep 获取当前操作系统中路径的分隔符
//linux为左斜杠 / windows为右斜杠 \
console.log(path.sep);

其它方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//path.parse() 将路径转换成对象
//path.format() 将对象转换成路径
console.log(path.parse("/a/b/c/d/index.html"));
/* {
root: '/',
dir: 'a/b/c/d',
base: 'index.html',
ext: '.html',
name: 'index'
} */


//path.join([...paths]) 拼接路径
console.log(path.join("/a/b", "c")); //=> /a/b/c
console.log(path.join("/a/b", "/c")); //=> /a/b/c
console.log(path.join("/a/b", "/c", "../")); //=> /a/b
console.log(path.join("/a/b", "/c", "../../")); //=> /a


//path.normalize() 规范化路径
console.log( path.normalize("/a//b////c/d////index.html") );//=> /a/b/c/d/index.html


//path.relative() 根据第一个参数(的目录)找到第二个参数的相对路径)
let res = path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
console.log(res); //=> ../../impl/bbb


//path.resolve() 解析为绝对路径
//从后向前
//若字符以 / 开头,不会拼接到前面的路径
//若以 ../ 开头,拼接前面的路径,且不含最后一节路径;
//若以 ./ 开头 或者没有符号 则拼接前面路径
console.log(path.resolve("/a/b/c", "./d")); //=> /a/b/c/d
console.log(path.resolve("/a/b/c", "../d")); //=> /a/b/d
console.log(path.resolve("/a/b/c", "/d")); //=> /d

fs

查看文件状态

fs文件模块,也需要导入才能使用。

  • fs.stat 查看文件状态-异步

  • fs.statSync 查看文件状态-同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//fs.stat(path, callback) 异步查看文件状态
console.log(__filename);//当前文件路径
fs.stat(__filename,function(err,stats){
//成功拿到文件 返回到stats
console.log(stats);//一个对象,包含文件的所有信息
console.log(stats.birthtime); //文件的创建时间
console.log(stats.mtime); //文件内容发生变化(修改的)时间
if(stats.isFile()){
console.log("当前路径对应的是一个文件")
}else if(stats.isDirectory){
console.log("是文件夹")
}
});
fs.stat("c:\a\b",function(err,stats){
//失败拿到文件 返回到err
console.log(err);
});


//fs.statSync(path) 同步查看文件状态
let stats = fs.statSync(__filename);
console.log(stats);//返回的是一个对象{}

读取文件

  • fs.readFile 读取文件-异步
  • fs.readFileSync 读取文件-同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let str = path.join(__dirname,"A.js");  //拿到需要读取的文件路径

//fs.readFile(path[, options], callback) 异步读取文件内容
fs.readFile(str,function(err,data){
if(err){
throw new Error("读取文件失败");
}
console.log(data); //<Buffer ... >
console.log(data.toString()); //转成字符串
})
fs.readFile(str,"utf-8",function(err,data){
console.log(data); //加上参数utf-8编码,直接转成字符串
})

//fs.readFileSync(path[, options]) 同步读取文件内容
let data = fs.readFileSync(str,"utf-8");
console.log(data);

写入文件

  • fs.writeFile 写入文件(覆盖)-异步
  • fs.writeFileSync 写入文件(覆盖)-同步
  • fs.appendFile 写入文件(追加)-异步
  • fs.appendFileSync 写入文件(追加) -同步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let str = path.join(__dirname,"my.txt");  //拼接写入的路径

//fs.writeFile(file, data[, options], callback) 异步写入文件
fs.writeFile(str,"百度www.baidu.com","utf-8",function(err){
if(err){
throw new Error("写入数据失败");
}else{
console.log("写入成功");
}
})

//fs.writeFileSync(file, data[, options]) 同步写入文件
let res = fs.writeFileSync(str,"淘宝taobao.com","utf-8");
console.log(res); //undefined 写入成功


//fs.appendFile(file, data[, options], callback) 异步追加写入文件
fs.appendFile(str,"百度www.baidu.com","utf-8",function(err){
if(err){
throw new Error("追加数据失败");
}else{
console.log("追加写入成功");
}
})

前面fs.readFile和fs.writeFile 读写文件都是一次性将数据读入内存或者一次性写入到文件中,如果数据比较大,直接将所有数据都读到内存中会导致计算机内存爆炸,卡顿,死机等。所以对于比较大的文件我们需要分批读取和写入

分批读取

fs.createReadStream()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let str = path.join(__dirname,"my.txt");  //拼接路径

//fs.createReadStream(path[, options])
//创建一个读取流
let readStrem = fs.createReadStream(str,{encoding:"utf-8",highWaterMark:1});//每次读取1B,默认值是64 * 1024(16KB)
//添加事件监听
readStrem.on("open",function(){
console.log("表示数据流和文件建立关系成功");
});
readStrem.on("error",function(){
console.log("表示数据流和文件建立关系失败");
});
readStrem.on("data",function(data){
console.log("表示通过读取流从文件中读取到了数据",data);
console.log("读取的数据以参数的形式传递给回调函数");
});
readStrem.on("close",function(){
console.log("表示数据读取完毕,数据流断开了和文件的关系");
});

分批写入

fs.createWriteStream()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let str = path.join(__dirname,"my.txt");  //拼接路径

//fs.createWriteStream(path[, options])
//创建一个写入流
let writeStrem = fs.createWriteStream(str,{encoding:"utf-8"});
//监听写入流的时间
writeStrem.on("open",function(){
console.log("表示数据流和文件建立关系成功");
});
writeStrem.on("error",function(){
console.log("表示数据流和文件建立关系失败");
});
writeStrem.on("close",function(){
console.log("表示数据写入完毕,数据流断开了和文件的关系");
});
//设置要写入的内容
let data = "百度网址:www.baidu.com";
let index = 0;
let timerId = setInterval(function(){
let ch = data[index];
index++;
writeStrem.write(ch);
console.log("本次写入了",ch);
if(index === data.length){
clearInterval(timerId);//清除定时器
writeStrem.end(); //!!一定要手动断开数据流链接,否则关系一直在,消耗性能
}
},500)

拷贝文件

读取流.pipe(写入流)

1
2
3
4
5
6
7
8
9
//1.生成读取和写入的路径
let readPath = path.join(__dirname,"test.jpg");
let writePath = path.join(__dirname, "abc.jpg");
//2.创建一个读取流
let readStrem = fs.createReadStream(readPath);
//3.创建一个写入流
let writeStrem = fs.createWriteStream(writePath);
//利用读取流的管道方法来快速的实现文件拷贝
readStrem.pipe(writeStrem);

目录操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//【创建目录】 
//fs.mkdir(path[, mode], callback)
//fs.mkdirSync(path[, mode])
var str = path.join(__dirname,"abc");
fs.mkdir(str,function(err){
if(err){
throw new Error("创建目录失败");
}else{
console.log("创建目录成功");
}
});


//【删除目录】
//fs.rmdir(path, callback)
//fs.rmdirSync(path)
var str = path.join(__dirname,"abc");
fs.rmdir(str,function(err){
if(err){
throw new Error("删除目录失败");
}else{
console.log("删除目录成功");
}
});


//【读取目录】
//fs.readdir(path[, options], callback)
//fs.readdirSync(path[, options])
fs.readdir(__dirname,function(err,files){
if(err){
throw new Error("读取目录失败");
}else{
console.log(files); //数组[ 'A.js','abc','abc.jpg'....]

//判断是文件还是目录
files.forEach(function(obj){
let filePath = path.join(__dirname,obj);//拼接
let stats = fs.statSync(filePath);
if(stats.isFile()){
console.log("是一个文件",obj);
}else if(stats.isDirectory()){
console.log("是一个目录",obj);
}
});

}
});

http

http模块需要手动导入。

快速搭建web服务器方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request",function(req,res){
res.writeHead(200,{ //告诉浏览器200状态码,请求成功
//告诉浏览器返回的数据是什么类型,返回的数据需要什么字符集来解析
"Content-Type":"text/plain; charset=utf-8"
});
res.end("百度www.baidu.com");//结束本次请求,返回数据
});
//3.指定监听的端口
sever.listen(3000);

//----------简写------------
http.createServer(function(req,res){
res.writeHead(200,{
"Content-Type":"text/plain; charset=utf-8"
});
res.end("百度www.baidu.com");
}).listen(3000);

路径分发(路由)

路径分发也称之为路由,就是根据不同的请求路径返回不同的数据

  • 通过请求监听方法中的request对象,我们可以获取到当前请求的路径
  • 通过判断请求路径中的地址就可以实现不同的请求路径返回不同的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
//request对象其实就是http.IncomingMessage 类的实例
//response对象其实就是http.ServerResponse 类的实例
sever.on("request",function(req,res){
res.writeHead(200,{
"Content-Type":"text/plain; charset=utf-8"
});

console.log(req.url);//返回请求的路径(拿到用户要浏览的地址)
if(req.url.startsWith("/index")){
res.end("首页")
}else if(req.url.startsWith("/login")){
res.write("正在");
res.write("登录");//一直在加载,数据出不来
}else{
res.write("拿到");
res.write("数据");
res.end();
}
//write()方法返回数据,可以返回多次;
//write()方法不具备结束本次请求的功能,所以还需要手动的调用.end()方法来结束本次请求
});
sever.listen(3000);

返回静态网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request",function(req,res){
if(req.url.startsWith("/index")){
let filePath = path.join(__dirname, req.url);//拼接文件路径
console.log(filePath);
console.log(req.url);
fs.readFile(filePath,"utf8",function(err,content){ //读取文件,指定UTF-8编码
if(err){
res.end("Sever Error");
}
res.end(content);
});

}else if(req.url.startsWith("/login")){
//let filePath = path.join(__dirname, "login.html");
let filePath = path.join(__dirname, req.url); //req.url就是用户输入的相对路径
fs.readFile(filePath,"utf8",function(err,content){
if(err){
res.end("Sever Error");
}
res.end(content);
});

}else{
res.writeHead(200,{
"Content-Type":"text/plain;charset=utf-8"
});
res.end("404,没有数据")
}
});
//3.指定监听的端口
sever.listen(3000);

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//封装
function readFile(req, res) {
let filePath = path.join(__dirname, req.url);//拼接文件路径
console.log(filePath);
console.log(req.url);
fs.readFile(filePath, "utf-8", function (err, content) { //读取文件
if (err) {
res.end("Sever Error");
}
res.end(content);
});
}


//使用
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request", function (req, res) {
readFile(req, res);
});
//3.指定监听的端口
sever.listen(3000);

返回静态资源

加载其它的资源不能写utf8,因为并不是字符串;如果服务器在响应数据的时候没有指定响应头,那么在有的浏览器上,响应的数据可能无法加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request", function (req, res) {
readFile(req, res);
});
//3.指定监听的端口
sever.listen(3000);

function readFile(req, res) {
let filePath = path.join(__dirname, req.url);//拼接文件路径

let extName = path.extname(filePath);//【拿到静态文件后缀】
let type = mime[extName];
console.log(type);
if (type.startsWith("text")) {
type += "; charset = utf-8";
}
res.writeHead(200, {
"Content-Type": type
});

fs.readFile(filePath, "utf-8", function (err, content) { //读取文件
if (err) {
res.end("Sever Error");
}
res.end(content);
});
}

获取Get参数

url

url 模块提供了一些实用函数,用于 URL 处理与解析。需要手动导入:

1
const url = require('url');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var str = "http://root:123456@www.baidu.com:80/index.html?name=zs&age=18#2";
var obj = url.parse(str, true);//true,把query转成对象
console.log(obj);//{}
console.log(obj.query.name);//zs
/*
Url {
protocol: 'http:',
slashes: true,
auth: 'root:123456',
host: 'www.baidu.com:80',
port: '80',
hostname: 'www.baidu.com',
hash: '#2',
search: '?name=zs&age=18',
query: [Object: null prototype] { name: 'zs', age: '18' },
pathname: '/index.html',
path: '/index.html?name=zs&age=18',
href: 'http://root:123456@www.baidu.com:80/index.html?name=zs&age=18#2'
}
*/

获取POST参数

在NodeJS中,POST请求的参数我们不能一次性拿到,必须分批获取(为了性能)

querystring 模块提供了一些实用函数,用于解析与格式化 URL 查询字符串。 使用以下方法引入:

1
const querystring = require('querystring');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request", function (req, res) {
var params = "";
req.on("data", function (chunk) {
params += chunk;
});
req.on("end",function(){
console.log(params);//拿到完整的参数
let obj = queryString.parse(params);//将参数转化为对象
})
});
//3.指定监听的端口
sever.listen(3000);

区分GET请求还是POST请求

1
2
3
4
5
6
7
8
9
10
11
12
//1.创建一个服务器实例对象
let sever = http.createServer();
//2.注册请求监听
sever.on("request", function (req, res) {
console.log(req.method);//GET或者POST
if(req.method.toLowerCase() === "get"){//toLowerCase()转换成小写字母
//....
}else if(req.method.toLowerCase() === "post"){
//...
}
//3.指定监听的端口
sever.listen(3000);

vm

vm 模块提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码。需要导入使用

1
const vm = require('vm');
  • vm.runInThisContext 提供了一个安全的环境给我们执行字符串中的代码;提供的环境不能访问本地的变量,但是可以访问全局的变量
  • vm.runInNewContext 提供了一个安全的环境给我们执行字符串中的代码;提供的环境不能访问本地的变量,也不能访问全局的变量
1
2
3
4
5
6
7
8
9
var name = "zs";
var str = "console.log(name)";
vm.runInThisContext(str); //name is noe defined
vm.runInNewContext(str);//name is note defined

global.age = "18";
var str = "console.log(age)";
vm.runInThisContext(str); //18
vm.runInNewContext(str);//name is note defined

事件循环顺序

  • 微任务(MicroTask):Promise / MutationObserver / process.nextTick (node独有)
  • 宏任务(MacroTask):SetTimeout / setInterval / setImmediate (IE独有)

NodeJS事件环和浏览器事件环区别

  • 队列个数:浏览器有两个(宏任务队列和微任务队列);NodeJS有6个。
  • 微任务队列:浏览器有专门存储微任务的队列,NodeJS中没有专门存储微任务的队列
  • 微任务执行时机:浏览器每执行完一个宏任务都会清空微任务队列;NodeJS中只有同步代码执行完毕和其它队列之间切换的时候会去清空微任务队列

NodeJS中的任务队列

  • 先执行完所有的同步代码,然后执行异步代码
  • 异步代码按照以下6个队列依次执行
  • 每执行完同步代码、每执行完一个队列就检查有没有微任务,如果有,就执行微任务-
  • 然后接着往下执行

nojsshijianxunhuan

经典面试题

1
2
3
4
5
6
7
8
9
fs.readFile(path.join(__dirname,"index.html"),function(){
setTimeout(function(){
console.log("setTimeout");
});
setImmediate(function(){
console.log(setImmediate);
});
});
//先执行setImmediate,后setTimeout