学习笔记-NodeJS 发表于 2022-11-19 | 更新于 2022-11-24
| 阅读量:
基础概念 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 let name = "zs" ;function sum (a,b ) { return a+b; } exports .xx1 = name;exports .xx2 = sum;let aModule = require ("./A" )console .log(aModule); console .log(aModule.xx1); console .log(aModule.xx2(10 ,5 ));
导出数据的几种方式 导出有三种方式,注意每一种都需要 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;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 npm install -g 包名 npm uninstall -g 包名 npm update -g 包名 包名 --version npm install 包名 npm uninstall 包名 npm update 包名
本地安装
1 2 3 4 5 6 7 npm init npm init -y npm install 包名 npm install --save-dev
包描述文件package.json 定义了当前项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。
npm install 命令根据这个配置文件,自动下载所需要的模块,也就是配置项目所需要的运行和开发环境。
npm i 所有的包都会被安装
npm i --production 只会安装 dependencies 中的包
加速资源下载 npm 默认去国外下载资源,比较慢,可以全局安装 NRM 这个包,允许你切换资源下载地址
1 2 3 4 5 6 7 8 npm install -g nrm nrm --version nrm ls npm use taobao
不想安装nrm的话,直接在npm中设置
1 2 3 4 npm config set registry https: npm config get registry
常用核心API NodeJS中文文档 www.nodeapp.cn
Buffer Buffer是NodeJS全局对象上的一个类,是一个专门用于存储字节数据的类。NodeJS提供了操作计算机底层API,而计算机底层API只能识别0和1,所以就提供了一个专门用于存储字节数据的类。
创建Buffer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Buffer.alloc(size[,[fill[,encoding]]) let buf = Buffer.alloc(5 ); console .log(buf);var buf = Buffer.from("abc" ); console .log(buf);var buf = Buffer.from([1 ,3 ,5 ]);console .log(buf); buf[0 ] = 9 ; console .log(buf);
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 var buf1 = Buffer.from([97 ,98 ,99 ]);console .log(buf1); console .log(buf1.toString()); var buf2 = Buffer.alloc(5 );console .log(buf2); buf2.write("abcdefg" ); console .log(buf2.toString()); var buf3 = Buffer.alloc(5 );console .log(buf3); buf3.write("abcdefg" , 2 ); console .log(buf3.toString()); var buf4 = Buffer.alloc(5 );console .log(buf4); buf4.write("abcdefg" , 2 , 2 ); console .log(buf4.toString()); var buf5 = Buffer.from("abcdefg" );var buf6 = buf5.slice(2 ,4 );console .log(buf6); console .log(buf6.toString());
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 console .log(Buffer.isEncoding("gbk" )); console .log(Buffer.isEncoding("UTF-8" ));var obj = {}var buf = Buffer.alloc(5 );console .log(Buffer.isBuffer(obj), Buffer.isBuffer(buf)); var buf1 = Buffer.from("123" );var buf2 = Buffer.from("abc" );var buf3 = Buffer.from("百度" );console .log(buf1.length, buf2.length, buf3.length);var buf1 = Buffer.from("123" );var buf2 = Buffer.from("abc" );var buf3 = Buffer.from("百度" );let res = Buffer.concat([buf1, buf2, buf3])console .log(res);console .log(res.toString());
Path Path是一个系统模块,Buffer模块已经自动添加到了global,所以使用的时候不用手动导入。而Path模块必需要手动导入才能使用。
path 模块提供了一些工具函数,用于处理文件与目录的路径。
1 2 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 console .log(path.basename("a/b/c/d/index.html" ));console .log(path.basename("a/b/c/d/" ));var res = path.basename("a/b/c/d/index.html" , ".html" );console .log(res);console .log(path.dirname("a/b/c/d/index.html" ));console .log(path.dirname("a/b/c/d/" ));console .log(path.extname("a/b/c/d/index.html" ));console .log(path.extname("a/b/c/d" ));console .log(path.isAbsolute("/a/b/c/d/index.html" )); console .log(path.isAbsolute("a/b/c/d/index.html" )); console .log(path.isAbsolute("./a/b/c/d/index.html" )); console .log(path.isAbsolute("c:\\a\\b\\c\\d\\index.html" )); 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 console .log(path.parse("/a/b/c/d/index.html" ));console .log(path.join("/a/b" , "c" )); console .log(path.join("/a/b" , "/c" )); console .log(path.join("/a/b" , "/c" , "../" )); console .log(path.join("/a/b" , "/c" , "../../" )); console .log( path.normalize("/a//b////c/d////index.html" ) );let res = path.relative('/data/orandea/test/aaa' , '/data/orandea/impl/bbb' );console .log(res); console .log(path.resolve("/a/b/c" , "./d" )); console .log(path.resolve("/a/b/c" , "../d" )); console .log(path.resolve("/a/b/c" , "/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 console .log(__filename);fs.stat(__filename,function (err,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 ) { console .log(err); }); 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(str,function (err,data ) { if (err){ throw new Error ("读取文件失败" ); } console .log(data); console .log(data.toString()); }) fs.readFile(str,"utf-8" ,function (err,data ) { console .log(data); }) 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" ); let readStrem = fs.createReadStream(str,{encoding :"utf-8" ,highWaterMark :1 }); 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" ); 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 let readPath = path.join(__dirname,"test.jpg" );let writePath = path.join(__dirname, "abc.jpg" );let readStrem = fs.createReadStream(readPath);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 var str = path.join(__dirname,"abc" );fs.mkdir(str,function (err ) { if (err){ throw new Error ("创建目录失败" ); }else { console .log("创建目录成功" ); } }); var str = path.join(__dirname,"abc" );fs.rmdir(str,function (err ) { if (err){ throw new Error ("删除目录失败" ); }else { console .log("删除目录成功" ); } }); fs.readdir(__dirname,function (err,files ) { if (err){ throw new Error ("读取目录失败" ); }else { console .log(files); 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 let sever = http.createServer();sever.on("request" ,function (req,res ) { res.writeHead(200 ,{ "Content-Type" :"text/plain; charset=utf-8" }); res.end("百度www.baidu.com" ); }); 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 let sever = http.createServer(); 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(); } }); 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 let sever = http.createServer();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 ) { if (err){ res.end("Sever Error" ); } res.end(content); }); }else if (req.url.startsWith("/login" )){ let filePath = path.join(__dirname, 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,没有数据" ) } }); 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); }); } let sever = http.createServer();sever.on("request" , function (req, res ) { readFile(req, res); }); 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 let sever = http.createServer();sever.on("request" , function (req, res ) { readFile(req, res); }); 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 );console .log(obj);console .log(obj.query.name);
获取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 let sever = http.createServer();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); }) }); sever.listen(3000 );
区分GET请求还是POST请求 1 2 3 4 5 6 7 8 9 10 11 12 let sever = http.createServer();sever.on("request" , function (req, res ) { console .log(req.method); if (req.method.toLowerCase() === "get" ){ }else if (req.method.toLowerCase() === "post" ){ } 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); vm.runInNewContext(str); global .age = "18" ;var str = "console.log(age)" ;vm.runInThisContext(str); vm.runInNewContext(str);
事件循环顺序
微任务(MicroTask):Promise / MutationObserver / process.nextTick (node独有)
宏任务(MacroTask):SetTimeout / setInterval / setImmediate (IE独有)
NodeJS事件环和浏览器事件环区别
队列个数:浏览器有两个(宏任务队列和微任务队列);NodeJS有6个。
微任务队列:浏览器有专门存储微任务的队列,NodeJS中没有专门存储微任务的队列
微任务执行时机:浏览器每执行完一个宏任务都会清空微任务队列;NodeJS中只有同步代码执行完毕和其它队列之间切换的时候会去清空微任务队列
NodeJS中的任务队列
先执行完所有的同步代码,然后执行异步代码
异步代码按照以下6个队列依次执行
每执行完同步代码、每执行完一个队列就检查有没有微任务,如果有,就执行微任务-
然后接着往下执行
经典面试题
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); }); });