前言

这是尚硅谷 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 应用是由哪几部分组成的:

  1. 引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
  2. 创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
  3. 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

3.1 引入 required 模块

我们使用 require 指令来载入 http 模块,并将实例化的 HTTP 赋值给变量 http,实例如下:

  1. var http = require("http");

3.2 创建服务器

接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口。 函数通过 request, response 参数来接收和响应数据。

实例如下,在你项目的根目录下创建一个叫 demo1.js 的文件,并写入以下代码:

  1. var http = require('http');
  2. http.createServer(function (request, response) {
  3. // 发送 HTTP 头部
  4. // HTTP 状态值: 200 : OK
  5. // 内容类型: text/plain
  6. response.writeHead(200, {'Content-Type': 'text/plain'});
  7. // 发送响应数据 "Hello World"
  8. response.end('Hello World\n');
  9. }).listen(8888);
  10. // 终端打印如下信息
  11. console.log('Server running at http://127.0.0.1:8888/');

以上代码我们完成了一个可以工作的 HTTP 服务器。

使用 node 命令执行以上的代码:

  1. node demo1.js
  2. Server running at http://127.0.0.1:8888/

NodeJS - 图2

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

NodeJS - 图3

分析Node.js 的 HTTP 服务器:

  • 第一行请求(require)Node.js 自带的 http 模块,并且把它赋值给 http 变量。
  • 接下来我们调用 http 模块提供的函数: createServer 。这个函数会返回 一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数, 指定这个 HTTP 服务器监听的端口号。

三、模块化详解

ECMAScript标准的缺陷:

  • 没有模块系统
  • 标准库较少
  • 没有标准接口
  • 缺乏管理系统

模块化:

  • 如果程序设计的规模达到了一定程度,则必须对其进行模块化
  • 模块化可以有多重形式,但至少应该提供能够将代码分隔为多个源文件的机制
  • CommonJS 的模块功能可以帮我们解决该问题

CommonJS规范

  • CommonJS规范的提出,主要是为了弥补当前JavaScript没有标准的缺陷
  • CommonJS规范为JS指定了一个美好的愿景,希望JS能够在任何地方运行
  • CommonJS对模块的定义十分简单:
    • 模块引用
    • 模块定义
    • 模块标识

模块引用

在Node中,一个js文件就是一个模块,
可以用require()方法,引入路径作为参数,来导入外部的模块
这里路径,如果使用相对路径,必须以.或者..开头

  1. const require1 = require("./03module");

使用require()引入模块后,会返回一个对象,这个对象代表的是引入的模块。

在node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域
所以一个模块中的变量和函数无法在其他模块中访问。

如果想向外部暴露属性或者方法,可以通过 exports

NodeJS - 图4

  1. //不暴露的方法
  2. function add2(a, b) {
  3. return a + b;
  4. }
  5. //暴露的方法
  6. exports.add = function (a, b) {
  7. return a + b;
  8. }

模块标识

我们使用require()引入外部模块时,使用的就是模块标识,我们可以用过标识来找到指定的模块,模块分为两大类:

  1. 核心模块:由node引擎提供的模块,标识就是模块的名字

    1. const require2 = require("fs");
    2. console.log(require2);
  2. 文件模块:由用户自己创建的模块,表示就是文件的路径

    1. const require1 = require("./03module");

全局变量

不使用var(const)修饰,就是全局变量

在node中有一个全局对象global,它的作用和网页中window类似
在全局中创建的变量都会作为global的属性保存
在全局中创建的函数都会作为global的方法保存

  1. x='10'; //全局变量
  2. const y = '2'
  3. exports.z = '213';
  4. console.log(global.x);
  5. console.log(global.y);
  6. console.log(global.z);
  7. --------------输出--------------
  8. 10
  9. undefined
  10. undefined

模块的5个参数

实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递了5个实参:

exports

  1. - 该对象用来将变量或函数暴露到外部

require

  • 函数,用来引入外部的模块

module

  • 代表的是当前模块本身
  • exports就是module属性
  • 既可以使用 exports 导出,也可以使用module.exports导出

__filename

  • 当前模块的完整路径

__dirname

  • 当前模块所在文件夹的完整路径

module和module.exports的区别

相同点

  1. exports.x = '张三';
  2. exports.y = 18;
  3. exports.z = function () {
  4. console.log("hello z")
  5. };
  6. module.exports.x1 = '李四';
  7. module.exports.y1 = 32;
  8. module.exports.z1 = function () {
  9. console.log("hello z1")
  10. };

不同点:

  1. //可以
  2. module.exports = {
  3. x2:"王五",
  4. y2:90,
  5. z2:function (){
  6. console.log("hello z2")
  7. }
  8. }
  9. //报错
  10. exports = {
  11. x2:"王五",
  12. y2:90,
  13. z2:function (){
  14. console.log("hello z2")
  15. }
  16. }

他们之间的关系就相当于

  1. var obj = {};
  2. obj.a = {};
  3. var a = obj.a;
  4. //a 和 obj.a 指向的是同一个对象

堆和栈

① 基本数据类型,值存在栈内存中,=的时候会赋值,所以a和b互不影响

NodeJS - 图5

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

NodeJS - 图6

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

NodeJS - 图7

  1. obj.name = 'zbj' //这里是改对象(的属性)
  2. 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与第三方模块之间形成了很好的一个生态系统。

  1. npm init //初始化包,就是创建package.json
  2. npm install xxx //下载包,安装到当前文件夹
  3. npm install xxx -g //下载包,全局模式安装
  4. npm remove xxx //移除包
  5. # 重要
  6. npm install xxx --save //安装包,并添加到依赖中
  7. npm install //根据当前package.json中的依赖,自动下载包

4.2 node 搜索包的过程

node在使用模块名宇来引入模块时,它会言先在当前目录的 node modules 中寻找是否含有该模块

如果有则直接使用,如果没有则去上一级目录的 node modules 中寻找

如果有则直接使用,如果没有则再去上一级目录寻找 node modules ,直到找到为止

直到找到磁盘的根目录 node modules ,如果依然没有,则报错

就是一直往上找 node modules 文件夹 必须是这个文件名

五、Buffer模块

Buffer的结构和数组很像,操作的方法也和数组类似

使用buffer不需要引用,直接使用即可

为什么需要buffer?

  1. js原生数组,性能比较差
  2. 图片 MP3 视频 统称为二进制文件。数组中不能存储二进制文件,buffer专门用来存储二进制文件
  1. let str = "hello world";
  2. //将一个字符串保存到buffer中
  3. //使用buffer不需要引用,直接使用即可
  4. var bufferStr = Buffer.from(str);
  5. console.log(bufferStr);
  6. console.log(bufferStr.length); //占用内存大小
  7. console.log(str.length); //数组的长度
  8. //一个汉字占3个字节,如果str中含有中文,这两个输出就不一样了
  9. //在buffer中都存储的是二进制数据,
  10. //计算机中二进制都会以十六进制的形式来显示
  11. --------------控制台输出---------------------
  12. <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
  13. 11
  14. 11

Buffer 每一个元素的范围是 00 ~ ff
计算机中一个 0 或者一个 1 ,我们成为1位 ,bit
8位, byte 字节
1byte = 8 bit
最小单位就是字节,
buffer中一个元素占用内存一个字节。

Buffer的大小一旦确定,就不能被修改。alloc 分配

  1. let buffer2 = Buffer.alloc(10);
  2. console.log(buffer2);
  3. buffer2[0] = 11;
  4. buffer2[1] = 255;
  5. buffer2[2] = 0xaa; //十六进制都是0x开头
  6. buffer2[3] = 256; //输出00
  7. buffer2[4] = 257; //输出01
  8. buffer2[10] = 9; //没有输入进去
  9. console.log(buffer2);
  10. --------------控制台输出---------------------
  11. <Buffer 00 00 00 00 00 00 00 00 00 00>
  12. <Buffer 0b ff aa 00 01 00 00 00 00 00>
  1. console.log(buffer2[2].toString(10)) //输出170
  2. console.log(buffer2[2]) //输出170
  3. console.log(buffer2[2].toString(2))//输出10101010
  4. console.log(buffer2[2].toString(16))//输出aa
  5. //控制台永远打印的是10进制数
  6. //可以使用toString方法,中间传入参数,来转换进制

六、FS模块

需要引入fs核心模块,直接引入,不需要下载

fs槙块中所有的操作都有两种形式可供选择 同步异步
同步文件系统会阻塞程序的执行,也就是 除非操作完毕,否则不会向下执行代码。
异步文件系统不会阻塞程序的执行,而是 在操作完成时,通过回调函数将结果返回。
方法名含有Sync的都是同步的方法

以下标志在 flag 选项接受字符串的任何地方可用。

NodeJS - 图8

6.1 同步文件写入

文件的写入分为三个步骤:

  1. 打开文件
  2. 写入文件
  3. 保存并关闭文件
  1. let fs = require("fs");
  2. /**
  3. * 写入文件
  4. * openSync(path, flags, mode)
  5. * path:文件的路径
  6. * flags:打开文件的操作类型
  7. * r:可读的
  8. * w:可写的
  9. * a:追加写入
  10. * mode:设置文件的权限,一般不传
  11. * 返回值:文件描述值,作为结果.通过描述符来对文件进行各种操作
  12. */
  13. let number = fs.openSync("hello.txt", "w");
  14. /**
  15. * 写入文件
  16. */
  17. fs.writeSync(number,"hello world ");
  18. /**
  19. * 关闭文件
  20. */
  21. fs.closeSync(number);

同步文件的方法都有Sync后缀,而且是顺序执行

6.2 异步文件写入

因为回调函数的存在,代码虽然是顺序执行,但是先后顺序不一定谁先谁后

  1. let fs = require("fs");
  2. //异步操作肯定没有返回值,因为如果有返回值则表示必须等返回之后才能继续操作
  3. //所以异步这里有回调函数,回调函数调用的方法,结果是通过回调函数的参数返回的
  4. /*
  5. * 回调函数的两个参数:
  6. * err:错误对象,如果没有错误则为null
  7. * fd:文件的描述符
  8. * */
  9. fs.open("hello2.txt","w",function (err,fd) {
  10. if(!err){
  11. console.log(fd);
  12. //写入文件
  13. fs.write(fd,"hello yibu",function (error){
  14. if(!error){
  15. console.log("文件写入成功");
  16. }
  17. //关闭文件
  18. fs.close(fd,function (error1){
  19. if(!error){
  20. console.log("文件关闭成功");
  21. }
  22. })
  23. });
  24. }else {
  25. console.log(err);
  26. }
  27. });

6.3 简单文件写入 NodeJS - 图9

不需要打开和关闭文件了

writeFilewriteFileSync 中对 open 和 close 进行了封装

  1. /**
  2. * fs.writeFile(file, data[, options], callback)
  3. * fs.writeFileSync(file, data[, options])
  4. *
  5. * --file:要操作的文件路径
  6. * --data:要写入的数据
  7. * --options:对写入进行一些设置,可选参数:w r a
  8. * --callback:当写入之后进行的一些操作
  9. */
  10. let fs = require("fs");
  11. fs.writeFile("hello3.txt","这是写入的内容",{flag:"a"},function (err){
  12. if (!err){
  13. console.log("写入成功")
  14. }
  15. });
  16. fs.writeFileSync("hello4.txt","hello 4");

6.4 流式文件写入

同步、异步、简单文件写入都不适合大文件的写入,性能较差,容易导致内存溢出

  1. let fs = require("fs");
  2. //1.创建一个可写流
  3. //fs.createWriteStream(path[, options])
  4. let ws = fs.createWriteStream("hello5.txt");
  5. //通过监听流的open和close事件来监听流从打开和关闭
  6. ws.once("open",function () {
  7. console.log("流打开了");
  8. });
  9. //on() 和 once() 都可以用来绑定事件,on长期绑定,once表示一次性事件
  10. ws.write("通过可写流写入文件");
  11. ws.write("锄禾日当午");
  12. ws.write("汗滴禾下土");
  13. ws.write("谁知盘中餐");
  14. ws.write("粒粒皆辛苦");
  15. ws.close();//关闭流
  16. //关闭监听
  17. ws.once("close",function () {
  18. console.log("流关闭了");
  19. });

6.5 简单文件读取

读取的是 buffer 类型,为什么?因为可能读取的是一个图片、音频呀

  1. let fs = require("fs");
  2. //异步
  3. fs.readFile("hello5.txt",function (err,data){
  4. if (!err){
  5. console.log(data.toString())
  6. fs.writeFile("new.txt",data,function (err){
  7. if(!err){
  8. console.log("文件复制成功")
  9. }
  10. });
  11. }
  12. });
  13. //同步
  14. let buffer = fs.readFileSync("hello4.txt");
  15. console.log(buffer.toString())

6.6 流式文件读取

  1. let fs = require("fs");
  2. //创建可读流
  3. let rs = fs.createReadStream("hello5.txt");
  4. rs.once("open",function () {
  5. console.log("流打开了");
  6. });
  7. //读取可读流,绑定一个data事件,data事件绑定完成,会自动读取数据
  8. rs.on("data",function (data){
  9. console.log(data);
  10. });
  11. //rs.close(); 不需要关闭了,会自动关闭
  12. rs.once("close",function () {
  13. console.log("流关闭了");
  14. });

pipe() 可以将可读流中的数据,直接输出到可写流中(复制功能)

  1. let fs = require("fs");
  2. let path1 = "1.md";//读取的路径
  3. let path2 = "2.md";//写入到的路径
  4. let readStream = fs.createReadStream(path1);
  5. let writeStream = fs.createWriteStream(path2);
  6. readStream.pipe(writeStream)

6.7 其他方法:

① 验证文件是否存在
  1. fs.existsSync(path);
  2. //没必要为了异步而异步,这个判断文件是否存在需要立马知道

② 获取文件信息
  1. fs.stat("hello.txt",function (err, stats){
  2. console.log(err);
  3. console.log(stats);
  4. });

③ 删除文件
  1. fs.unlink("hello.txt",function (err) {
  2. if(!err){
  3. console.log("删除成功");
  4. }
  5. })

④ 列出文件

. 表示当前路径

  1. fs.readdir(".",function (err, files){
  2. console.log(files)
  3. });

⑤ 截断文件
  1. fs.truncateSync("hello2.txt",3);
  2. fs.truncate("hello2.txt",2,function (){
  3. });

⑥ 建立目录
  1. fs.mkdir("new",function (){});

⑦ 删除目录
  1. fs.rmdir("new",function (){});

⑧ 命名(剪切)
  1. fs.rename("new","new1",function () {});

⑨ 监视文件的修改

会监视文件的内容变化 和 重命名变化

  1. fs.watchFile("hell2.txt", function (curr, prev) {
  2. console.log("修改前文件大小:" + curr.size);
  3. console.log("修改后文件大小:" + prev.size);
  4. })