前言
这是尚硅谷 node.js 的学习笔记
视频地址:【尚硅谷】最经典Node.JS全套完整版教程(快速入门nodejs)_哔哩哔哩_bilibili
一、进程和线程
- 进程
- 进程负责为程序的运行提供必备的环境
- 进程就相当于工厂中的车间
- 线程
- 线程计算机中的最小的计算单位,线程负责执行程序的运行
- 线程就相当于工厂的工人
js是单线程的
node的服务器单线程的,node处理请求时是单线程,但是在后台拥有一个I/O线程池
二、node简介
node:节点
- Node.js 是一个能够在服务端运行JavaScript 的开放源代码、跨平台的JavaScript运行环境
- Node采用Google开发的V8引擎运行js代码,使用事件驱动、非阻塞和异步I/O模型等技术来提高性能,可优化应用程序的传输量和规模
- Node 大部分基本模块都用 JavaScript 编写。在Node出现之前,JS通常作为客户端程序设计语言使用,以JS写出的程序常在用户的浏览器上运行。
Node的用途:
- Web服务API,比如REST
- 实时多人游戏
- 后端的web服务,例如跨域、服务端的请求
- 基于web的应用
- 多客户端的通信,如即时通信
三、Node.js 创建第一个应用
如果我们使用 PHP 来编写后端的代码时,需要 Apache 或者 Nginx 的 HTTP 服务器,并配上 mod_php5 模块和 php-cgi。
从这个角度看,整个”接收 HTTP 请求并提供 Web 页面”的需求就不需要 PHP 来处理。
不过对 Node.js 来说,概念完全不一样了。使用 Node.js 时,我们不仅仅 在实现一个应用,同时还实现了整个 HTTP 服务器。事实上,我们的 Web 应用以及对应的 Web 服务器基本上是一样的。
在我们创建 Node.js 第一个 “Hello, World!” 应用前,让我们先了解下 Node.js 应用是由哪几部分组成的:
- 引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
- 创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
- 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。
3.1 引入 required 模块
我们使用 require 指令来载入 http 模块,并将实例化的 HTTP 赋值给变量 http,实例如下:
var http = require("http");
3.2 创建服务器
接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。
实例如下,在你项目的根目录下创建一个叫 demo1.js 的文件,并写入以下代码:
var http = require('http');http.createServer(function (request, response) {// 发送 HTTP 头部// HTTP 状态值: 200 : OK// 内容类型: text/plainresponse.writeHead(200, {'Content-Type': 'text/plain'});// 发送响应数据 "Hello World"response.end('Hello World\n');}).listen(8888);// 终端打印如下信息console.log('Server running at http://127.0.0.1:8888/');
以上代码我们完成了一个可以工作的 HTTP 服务器。
使用 node 命令执行以上的代码:
node demo1.jsServer running at http://127.0.0.1:8888/

接下来,打开浏览器访问 http://127.0.0.1:8888/,你会看到一个写着 “Hello World”的网页。

分析Node.js 的 HTTP 服务器:
- 第一行请求(require)Node.js 自带的 http 模块,并且把它赋值给 http 变量。
- 接下来我们调用 http 模块提供的函数: createServer 。这个函数会返回 一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数, 指定这个 HTTP 服务器监听的端口号。
三、模块化详解
ECMAScript标准的缺陷:
- 没有模块系统
- 标准库较少
- 没有标准接口
- 缺乏管理系统
模块化:
- 如果程序设计的规模达到了一定程度,则必须对其进行模块化
- 模块化可以有多重形式,但至少应该提供能够将代码分隔为多个源文件的机制
- CommonJS 的模块功能可以帮我们解决该问题
CommonJS规范
- CommonJS规范的提出,主要是为了弥补当前JavaScript没有标准的缺陷
- CommonJS规范为JS指定了一个美好的愿景,希望JS能够在任何地方运行
- CommonJS对模块的定义十分简单:
- 模块引用
- 模块定义
- 模块标识
模块引用
在Node中,一个js文件就是一个模块,
可以用require()方法,引入路径作为参数,来导入外部的模块
这里路径,如果使用相对路径,必须以.或者..开头
const require1 = require("./03module");
使用require()引入模块后,会返回一个对象,这个对象代表的是引入的模块。
在node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域
所以一个模块中的变量和函数无法在其他模块中访问。
如果想向外部暴露属性或者方法,可以通过 exports

//不暴露的方法function add2(a, b) {return a + b;}//暴露的方法exports.add = function (a, b) {return a + b;}
模块标识
我们使用require()引入外部模块时,使用的就是模块标识,我们可以用过标识来找到指定的模块,模块分为两大类:
核心模块:由node引擎提供的模块,标识就是模块的名字
const require2 = require("fs");console.log(require2);
文件模块:由用户自己创建的模块,表示就是文件的路径
const require1 = require("./03module");
全局变量
不使用var(const)修饰,就是全局变量
在node中有一个全局对象global,它的作用和网页中window类似
在全局中创建的变量都会作为global的属性保存
在全局中创建的函数都会作为global的方法保存
x='10'; //全局变量const y = '2'exports.z = '213';console.log(global.x);console.log(global.y);console.log(global.z);--------------输出--------------10undefinedundefined
模块的5个参数
实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递了5个实参:
exports
- 该对象用来将变量或函数暴露到外部
require
- 函数,用来引入外部的模块
module
- 代表的是当前模块本身
- exports就是module属性
- 既可以使用 exports 导出,也可以使用module.exports导出
__filename
- 当前模块的完整路径
__dirname
- 当前模块所在文件夹的完整路径
module和module.exports的区别
相同点
exports.x = '张三';exports.y = 18;exports.z = function () {console.log("hello z")};module.exports.x1 = '李四';module.exports.y1 = 32;module.exports.z1 = function () {console.log("hello z1")};
不同点:
//可以module.exports = {x2:"王五",y2:90,z2:function (){console.log("hello z2")}}//报错exports = {x2:"王五",y2:90,z2:function (){console.log("hello z2")}}
他们之间的关系就相当于
var obj = {};obj.a = {};var a = obj.a;//a 和 obj.a 指向的是同一个对象
堆和栈
① 基本数据类型,值存在栈内存中,=的时候会赋值,所以a和b互不影响

② 引用数据类型,值存在堆内存中,栈内存中存的是堆内存中的地址,所以=的传递的是地址,当值发生变化的时候,a和b都会发生变化

③ 在②的基础上,添加obj2=null ,对obj没有影响

obj.name = 'zbj' //这里是改对象(的属性)obj = null //这里是改变量
区分变量和对象的关系
四、包简介
CommonJS 的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具
CommonJS 的包规范由包结构和包描述文件两个部分组成。
包实际上就是一个压缩文件,解压以后还原为目录。符合规范的目录应该包含如下文件:
- package.json 描述文件
- bin 可执行二进制文件
- lib js代码
- doc 文档
- test 单元测试
package.json必须放在包的根目录下,且不能有注释。
4.1 NPM(Node Package Manager)
CommonJS 包规范是理论,NPM是其中一种实践。
对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。
npm init //初始化包,就是创建package.jsonnpm install xxx //下载包,安装到当前文件夹npm install xxx -g //下载包,全局模式安装npm remove xxx //移除包# 重要npm install xxx --save //安装包,并添加到依赖中npm install //根据当前package.json中的依赖,自动下载包
4.2 node 搜索包的过程
node在使用模块名宇来引入模块时,它会言先在当前目录的 node modules 中寻找是否含有该模块
如果有则直接使用,如果没有则去上一级目录的 node modules 中寻找
如果有则直接使用,如果没有则再去上一级目录寻找 node modules ,直到找到为止
直到找到磁盘的根目录 node modules ,如果依然没有,则报错
就是一直往上找 node modules 文件夹 必须是这个文件名
五、Buffer模块
Buffer的结构和数组很像,操作的方法也和数组类似
使用buffer不需要引用,直接使用即可
为什么需要buffer?
- js原生数组,性能比较差
- 图片 MP3 视频 统称为二进制文件。数组中不能存储二进制文件,buffer专门用来存储二进制文件
let str = "hello world";//将一个字符串保存到buffer中//使用buffer不需要引用,直接使用即可var bufferStr = Buffer.from(str);console.log(bufferStr);console.log(bufferStr.length); //占用内存大小console.log(str.length); //数组的长度//一个汉字占3个字节,如果str中含有中文,这两个输出就不一样了//在buffer中都存储的是二进制数据,//计算机中二进制都会以十六进制的形式来显示--------------控制台输出---------------------<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>1111
Buffer 每一个元素的范围是 00 ~ ff
计算机中一个 0 或者一个 1 ,我们成为1位 ,bit
8位, byte 字节
1byte = 8 bit
最小单位就是字节,
buffer中一个元素占用内存一个字节。
Buffer的大小一旦确定,就不能被修改。alloc 分配
let buffer2 = Buffer.alloc(10);console.log(buffer2);buffer2[0] = 11;buffer2[1] = 255;buffer2[2] = 0xaa; //十六进制都是0x开头buffer2[3] = 256; //输出00buffer2[4] = 257; //输出01buffer2[10] = 9; //没有输入进去console.log(buffer2);--------------控制台输出---------------------<Buffer 00 00 00 00 00 00 00 00 00 00><Buffer 0b ff aa 00 01 00 00 00 00 00>
console.log(buffer2[2].toString(10)) //输出170console.log(buffer2[2]) //输出170console.log(buffer2[2].toString(2))//输出10101010console.log(buffer2[2].toString(16))//输出aa//控制台永远打印的是10进制数//可以使用toString方法,中间传入参数,来转换进制
六、FS模块
需要引入fs核心模块,直接引入,不需要下载
fs槙块中所有的操作都有两种形式可供选择 同步和异步。
同步文件系统会阻塞程序的执行,也就是 除非操作完毕,否则不会向下执行代码。
异步文件系统不会阻塞程序的执行,而是 在操作完成时,通过回调函数将结果返回。
方法名含有Sync的都是同步的方法
以下标志在 flag 选项接受字符串的任何地方可用。

6.1 同步文件写入
文件的写入分为三个步骤:
- 打开文件
- 写入文件
- 保存并关闭文件
let fs = require("fs");/*** 写入文件* openSync(path, flags, mode)* path:文件的路径* flags:打开文件的操作类型* r:可读的* w:可写的* a:追加写入* mode:设置文件的权限,一般不传* 返回值:文件描述值,作为结果.通过描述符来对文件进行各种操作*/let number = fs.openSync("hello.txt", "w");/*** 写入文件*/fs.writeSync(number,"hello world ");/*** 关闭文件*/fs.closeSync(number);
同步文件的方法都有Sync后缀,而且是顺序执行
6.2 异步文件写入
因为回调函数的存在,代码虽然是顺序执行,但是先后顺序不一定谁先谁后
let fs = require("fs");//异步操作肯定没有返回值,因为如果有返回值则表示必须等返回之后才能继续操作//所以异步这里有回调函数,回调函数调用的方法,结果是通过回调函数的参数返回的/** 回调函数的两个参数:* err:错误对象,如果没有错误则为null* fd:文件的描述符* */fs.open("hello2.txt","w",function (err,fd) {if(!err){console.log(fd);//写入文件fs.write(fd,"hello yibu",function (error){if(!error){console.log("文件写入成功");}//关闭文件fs.close(fd,function (error1){if(!error){console.log("文件关闭成功");}})});}else {console.log(err);}});
6.3 简单文件写入 
不需要打开和关闭文件了
writeFile 和 writeFileSync 中对 open 和 close 进行了封装
/*** fs.writeFile(file, data[, options], callback)* fs.writeFileSync(file, data[, options])** --file:要操作的文件路径* --data:要写入的数据* --options:对写入进行一些设置,可选参数:w r a* --callback:当写入之后进行的一些操作*/let fs = require("fs");fs.writeFile("hello3.txt","这是写入的内容",{flag:"a"},function (err){if (!err){console.log("写入成功")}});fs.writeFileSync("hello4.txt","hello 4");
6.4 流式文件写入
同步、异步、简单文件写入都不适合大文件的写入,性能较差,容易导致内存溢出
let fs = require("fs");//1.创建一个可写流//fs.createWriteStream(path[, options])let ws = fs.createWriteStream("hello5.txt");//通过监听流的open和close事件来监听流从打开和关闭ws.once("open",function () {console.log("流打开了");});//on() 和 once() 都可以用来绑定事件,on长期绑定,once表示一次性事件ws.write("通过可写流写入文件");ws.write("锄禾日当午");ws.write("汗滴禾下土");ws.write("谁知盘中餐");ws.write("粒粒皆辛苦");ws.close();//关闭流//关闭监听ws.once("close",function () {console.log("流关闭了");});
6.5 简单文件读取
读取的是 buffer 类型,为什么?因为可能读取的是一个图片、音频呀
let fs = require("fs");//异步fs.readFile("hello5.txt",function (err,data){if (!err){console.log(data.toString())fs.writeFile("new.txt",data,function (err){if(!err){console.log("文件复制成功")}});}});//同步let buffer = fs.readFileSync("hello4.txt");console.log(buffer.toString())
6.6 流式文件读取
let fs = require("fs");//创建可读流let rs = fs.createReadStream("hello5.txt");rs.once("open",function () {console.log("流打开了");});//读取可读流,绑定一个data事件,data事件绑定完成,会自动读取数据rs.on("data",function (data){console.log(data);});//rs.close(); 不需要关闭了,会自动关闭rs.once("close",function () {console.log("流关闭了");});
pipe() 可以将可读流中的数据,直接输出到可写流中(复制功能)
let fs = require("fs");let path1 = "1.md";//读取的路径let path2 = "2.md";//写入到的路径let readStream = fs.createReadStream(path1);let writeStream = fs.createWriteStream(path2);readStream.pipe(writeStream)
6.7 其他方法:
① 验证文件是否存在
fs.existsSync(path);//没必要为了异步而异步,这个判断文件是否存在需要立马知道
② 获取文件信息
fs.stat("hello.txt",function (err, stats){console.log(err);console.log(stats);});
③ 删除文件
fs.unlink("hello.txt",function (err) {if(!err){console.log("删除成功");}})
④ 列出文件
. 表示当前路径
fs.readdir(".",function (err, files){console.log(files)});
⑤ 截断文件
fs.truncateSync("hello2.txt",3);fs.truncate("hello2.txt",2,function (){});
⑥ 建立目录
fs.mkdir("new",function (){});
⑦ 删除目录
fs.rmdir("new",function (){});
⑧ 命名(剪切)
fs.rename("new","new1",function () {});
⑨ 监视文件的修改
会监视文件的内容变化 和 重命名变化
fs.watchFile("hell2.txt", function (curr, prev) {console.log("修改前文件大小:" + curr.size);console.log("修改后文件大小:" + prev.size);})
