【学习笔记】后端(Ⅰ)—— NodeJS(Ⅰ)

本文是关于NodeJS的学习笔记,介绍了NodeJS的基本概念、主要作用、优点,以及与浏览器JavaScript的区别。深入讲解了Buffer、fs、path模块和HTTP协议的基础知识,包括字符编码、文件操作和HTTP请求响应。同时,探讨了NodeJS的模块化编程,包括CommonJS规范和包管理工具。通过这篇笔记,读者将对NodeJS有一个全面的认识。
摘要由CSDN通过智能技术生成

1、概述

      1.1、NodeJS是什么

         Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,最初由 Ryan Dahl 于2009年发布。它允许开发者在服务器端运行 JavaScript 代码,而不只是局限于在浏览器中运行。Node.js 采用事件驱动、非阻塞式 I/O 模型,使其在处理大量并发连接时表现出色

      1.2、NodeJS的主要作用

         ① 服务器端开发:Node.js 常用于构建服务器端应用,尤其是实时应用,如聊天应用和在线游戏
         ② API 服务:Node.js 可以用于创建 RESTful API 服务,处理 HTTP 请求和响应
         ③ 开发工具、桌面端应用:Node.js 的生态系统中有许多开发工具和框架,如 Webpack、Vite、Babel 等,用于快速开发应用。除此之外,Node.js 也可以用于开发桌面端应用,例如VSCode、Figma、Postman(这三个 APP 基于 Electron 开发出来的,而 Electron 又是基于 Node.js 开发出来的)
         ④ 微服务架构:由于其轻量和高效的特点,Node.js 非常适合用于微服务架构
         ⑤ 任务自动化:通过工具如 Gulp 和 Grunt,Node.js 可用于自动化任务,如代码打包、测试等

      1.3、NodeJS的优点

         ① 高性能:基于 V8 引擎,Node.js 执行 JavaScript 代码非常快
         ② 非阻塞 I/O:事件驱动、非阻塞 I/O 模型使其在处理 I/O 密集型任务时效率极高
         ③ 统一的编程语言:前后端都使用 JavaScript,开发者只需掌握一种语言即可
         ④ 庞大的生态系统:npm(Node Package Manager)拥有丰富的模块和包,可以极大地提高开发效率
         ⑤ 大社区支持:Node.js 拥有活跃的开源社区,持续更新和丰富其功能

      1.4、NodeJS 与 浏览器 的 JavaScript 对比

浏览器的 JS组成
核心语法ECMAScript
Web APIDOM、BOM、AJAX、Storage、console、定时器、alert/confirm…
NodeJS 的 JS组成
核心语法ECMAScript
Node APIfs、url、http、util、console、定时器、path…

         例如让浏览器来执行 console.log(window) 是可以的,而让 NodeJS 来执行却不行因为它没有 BOM 模块,同样的浏览器来执行 console.log(document) 是可以的,而让 NodeJS 来执行却不行因为它没有 DOM 模块;在 NodeJS 中类似于浏览器顶级对象 window 的叫做 global / globalThis(ES11引入)

            1.4.1 ECMAScript 介绍

         ECMAScript 是一种由 ECMA 国际(European Computer Manufacturers Association)标准化的脚本语言规范。它为 JavaScript、JScript、ActionScript 等脚本语言提供了基础,例如变量声明、循环控制、对象声明、函数声明等基础语法。ECMAScript 的标准由 ECMA-262 规范定义,最早版本发布于 1997 年

版本描述
ECMAScript 1(ES1)1997年发布,是 ECMAScript 标准的第一个版本
ECMAScript 2(ES21998年发布,主要是一些编辑上的修改
ECMAScript 3(ES3)1999年发布,加入了正则表达式、错误处理、更多的字符串处理方法等
ECMAScript 4(ES4)原计划在 2008 年发布,但由于争议过大被放弃
ECMAScript 5(ES5)2009年发布,加入了严格模式、JSON 支持、更多的数组方法等
ECMAScript 5.1(ES5.1)2011年发布,主要是与国际标准 ISO/IEC 16262 的一致性修改
ECMAScript 6(ES6 / ECMAScript 2015)2015年发布,带来了许多重大改进,如箭头函数、类、模块、let 和 const、Promise 等
ECMAScript 2016(ES7)2016年发布,新增了 Array.prototype.includes 方法和指数操作符(**)
ECMAScript 2017(ES8)2017年发布,增加了 async/await、Object.values/Object.entries 等
ECMAScript 2018(ES9)2018年发布,增加了异步迭代器、Rest/Spread 属性等
ECMAScript 2019(ES10)2019年发布,增加了 Array.prototype.flat、Object.fromEntries 等
ECMAScript 2020(ES11)2020年发布,增加了可选链操作符(?.)、空值合并操作符(??)等
ECMAScript 2021(ES12)2021年发布,增加了逻辑赋值操作符、WeakRefs 等
ECMAScript 2022(ES13)2022年发布,增加了顶层 await、类字段、错误原因等
ECMAScript的重要特性描述
变量声明使用 var、let、const 关键字
函数包括普通函数、箭头函数、匿名函数等
作用域块级作用域(let、const)、函数作用域(var)
对象和类使用对象字面量和类语法创建对象和类
模块使用 import 和 export 进行模块化开发
异步编程使用回调、Promise、async/await 处理异步操作
数组和集合提供强大的数组方法(如 map、filter、reduce)以及集合(如 Set、Map)
字符串处理模板字符串、正则表达式等
ECMAScript的重要概念描述
严格模式(Strict Mode)通过在脚本或函数开头添加 “use strict”;,可以启用严格模式,捕获常见的编码错误
原型链ECMAScript 使用原型链实现继承,每个对象都有一个原型对象
闭包(Closure)闭包是指函数可以捕捉并访问其词法作用域中的变量,即使函数在其词法作用域之外执行

         ① 影响:ECMAScript 的规范对现代 JavaScript 开发产生了深远影响。每年的 ECMAScript 新版本带来了新特性和改进,使得 JavaScript 语言更加强大和易用。这些规范不仅在浏览器中实现,也在服务器端的 Node.js 中广泛应用
         ② 生态系统:ECMAScript 的标准化促进了 JavaScript 生态系统的繁荣,工具如 Babel 可以将现代 ECMAScript 代码转译为兼容性更好的代码,库如 React、Vue.js 等都依赖 ECMAScript 的特性来实现高效的开发和运行
         ③ 总结:ECMAScript 是定义 JavaScript 等脚本语言基础的规范。随着每年的更新,ECMAScript 不断引入新特性和改进,使 JavaScript 语言能够应对越来越复杂的开发需求。了解 ECMAScript 的演进和特性对于现代 JavaScript 开发者来说至关重要

            1.4.2 JavaScript 介绍

         ① 定义:JavaScript 是一种基于 ECMAScript 规范的脚本语言。它由 Netscape 的 Brendan Eich 在 1995 年开发,最初称为 Mocha,后改名为 LiveScript,最终命名为 JavaScript
         ② 实现:JavaScript 是 ECMAScript 的一种实现,但它不仅仅限于 ECMAScript 规范,还包括一些额外的特性和功能,例如 DOM(文档对象模型)和 BOM(浏览器对象模型),这些是浏览器提供的 API,用于操控网页
         ③ 用途:JavaScript 主要用于网页开发,能够在浏览器中运行,为网页添加动态功能。它也可以在服务器端运行,例如通过 Node.js

// 体会一下 JavaScript
let greeting = 'Hello, World!';
function greet(name) {
  return `${greeting}, ${name}!`;
}
console.log(greet('Alice'));

            1.4.3 TypeScript 介绍

         ① 定义:TypeScript 是由 Microsoft 开发的一种编程语言,它是 JavaScript 的超集,增加了静态类型和其他一些特性
         ② 特点:静态类型 —— TypeScript 增加了类型系统,使得开发者可以在编译阶段发现并修复潜在的错误;现代特性 —— TypeScript 支持 ECMAScript 的最新特性,并且可以将这些特性编译为兼容性更好的 JavaScript 代码,以便在旧版浏览器中运行;开发体验 —— TypeScript 提供了更好的开发体验,如智能代码补全、导航、重构等
         ③ 关于编译:TypeScript 代码需要编译为 JavaScript 才能运行。编译器 tsc 会将 .ts 文件转换为 .js 文件

let greeting: string = 'Hello, World!';
function greet(name: string): string {
  return `${greeting}, ${name}!`;
}
console.log(greet('Alice'));

2、基础篇

      2.1、Buffer

         由于在处理像TCP流或文件流时,必须使用到二进制数据,因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
         在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存

特点
大小固定且无法调整
性能较好,可以直接对计算机内存进行操作
每个元素的大小为 1B

在这里插入图片描述

            2.1.1 Buffer与字符编码

         Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换

Node.js 支持的字符编码描述
ascii使用 7 位(8 位中的最高位被忽略)来表示 128 个字符,包括字母、数字、标点符号等
utf8使用 1 到 4 个字节来表示一个字符,能够表示 Unicode 字符集中的所有字符
ucs2 / utf16le是一种 Unicode 编码方式,它使用 2 个字节来表示一个字符,其中最常见的字符使用一个字节,辅助平面字符使用两个字节
base64将二进制数据编码为 ASCII 字符的方法,它将 3 字节的数据编码为 4 个 ASCII 字符,由于编码后的数据仅包含可打印字符,所以它常被用于在文本协议中传输二进制数据
binary / latin1是一种单字节字符编码,它涵盖了西欧各种语言的字符
hex将每个字节编码为两个十六进制字符
const buf = Buffer.from("niki", "ascii");

// <Buffer 6e 69 6b 69> n的ascii码是110 换算成十六进制就是6e(6*16+14)
console.log(buf);

// 以十六进制的形式输出
console.log(buf.toString("hex"));

// 以base64编码的形式输出
console.log(buf.toString("base64"));

            2.1.2 Buffer 类的创建

API描述
Buffer.alloc(size, fill, encoding)创建一个 size 大小的 Buffer 实例,fiill(可选)即为填充的值,默认是 0,encoding(可选)是字符串的编码。默认是 ‘utf8’
Buffer.allocUnsafe(size)返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据,速度相较于 alloc 更快
Buffer.allocUnsafeSlow(size)相对allocUnsafe较慢的分配方式,因为它用于在无法立即分配所需大小的内存时,逐渐分配内存,以避免阻塞主事件循环
Buffer.from(array)返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
Buffer.from(arrayBuffer, byteOffset, length)返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer,byteOffset是索引默认为0,length是长度默认为全长
Buffer.from(buffer)复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
Buffer.from(string, encoding)返回一个被 string 的值初始化的新的 Buffer 实例,encoding默认为utf8
// 创建一个长度为 10、且用 0 填充的 Buffer。
const buf1 = Buffer.alloc(10);
console.log(buf1);

// 创建一个长度为 10、且用 0x1 填充的 Buffer。
const buf2 = Buffer.alloc(10, 1);
console.log(buf2);

// 创建一个长度为 10、且未初始化的 Buffer。
const buf3 = Buffer.allocUnsafe(10);
console.log(buf3);

// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。
const buf4 = Buffer.from([1, "2", 3]);
console.log(buf4);

// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。
const buf5 = Buffer.from("tést",'utf8');
console.log(buf5);

// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。
const buf6 = Buffer.from("tést", "latin1");
console.log(buf6)

            2.1.3 Buffer 类的操作

操作API
写入buf.write(string,offset,length,encoding)
读取buf.toString(encoding,start,end)
转为 JSON 对象buf.toJSON()
合并Buffer.concat(list, totalLength)
比较buf.compare(otherBuffer);
覆盖buf.copy(targetBuffer, targetStart, sourceStart, sourceEnd)
裁剪buf.slice(start, end)
获取长度buf.length
// 写入
buf = Buffer.alloc(20);
console.log(buf); //写入前
len = buf.write("www.runoob.com"); //返回长度
console.log(buf); //写入后:编码,以utf8进行存储
console.log("写入字节数 : " + len);

// 读取
buf = Buffer.alloc(26); //以utf8的进行编码,ascii是utf8的子集
for (var i = 0; i < 26; i++) {
  buf[i] = i + 97; //存储编码,对应存储的实际值是是a-z
}
console.log(buf); //以十六进制输出,97对应61
console.log(buf.toString("ascii")); //使用 'ascii' 解码,出来的就是a-z
console.log(buf.toString("ascii", 0, 5)); //使用 'ascii' 解码, 并输出: abcde
console.log(buf.toString("utf8", 0, 5)); // 使用 'utf8' 解码, 并输出: abcde
console.log(buf.toString(undefined, 0, 5)); // 使用默认的 'utf8' 解码, 并输出: abcde

//转为JSON对象
buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
console.log(buf); //编码过的
json = JSON.stringify(buf);
console.log(json); //采用什么编码,实际值是什么
copy = JSON.parse(json, (key, value) => {
  return value && value.type === "Buffer" ? Buffer.from(value.data) : value;
});
console.log(copy); //输出一个新的Buffer实例

//合并
buffer1 = Buffer.from("Hello ");
buffer2 = Buffer.from("World!");
buffer3 = Buffer.concat([buffer1, buffer2]);
console.log(buffer3.toString());

//比较
buffer1 = Buffer.from("ABC");
buffer2 = Buffer.from("ABCD");
result = buffer1.compare(buffer2);

if (result < 0) {
  console.log(buffer1 + " 在 " + buffer2 + "之前"); //因为buffer跟字符串拼接,因此自动调用toString()
} else if (result == 0) {
  console.log(buffer1 + " 与 " + buffer2 + "相同");
} else {
  console.log(buffer1 + " 在 " + buffer2 + "之后");
}

//覆盖
buf1 = Buffer.from("Hello");
buf2 = Buffer.from("World!");
buf2.copy(buf1, 2); //World将会从将Hello的llo覆盖掉,变成HeWor
console.log(buf1.toString());

//裁剪
buffer1 = Buffer.from("Hello World!");
buffer2 = buffer1.slice(3, 7);
console.log(buffer2.toString());

//获取长度
buffer = Buffer.from("Hello World!");
console.log(buffer.length);

            2.1.4 注意点

        ① 溢出:buffer存储

buf = Buffer.alloc(3);
buf[0] = 47;
buf[1] = 97;
buf[2] = 257;
console.log(buf);
console.log("ascii:", buf.toString("ascii")); //发生溢出,ascii只有0-127号,因此最后那个显示不出来
console.log("utf8:", buf.toString("utf8")); //正常输出
console.log("binary:", buf[0].toString(2)); //以二进制输出
console.log("binary:", buf[2].toString(2)); //257用1B输出的时候会发生溢出,保留地位

        ② 中文:在utf8中,一个中文要采用三个字符来进行表示

buf =Buffer.from('你好')
console.log(buf)

      2.2、fs 模块

        (文件系统)模块提供了对文件系统的访问功能,允许你与文件进行交互,包括读取文件内容、写入文件内容、更改文件权限、监视文件的变化等操作

操作API
异步读取Ⅰfs.read(fd, buffer, offset, length, position, callback)
同步读取Ⅰfs.readSync(fd, buffer, offset, length, position)
异步读取Ⅱfs.readFile(path[, options], callback)
同步读取Ⅱfs.readFileSync(path[, options])
流式读取
(占用内存小)
let rs = fs.createReadStream(file)
rs.on(‘data’,chunk=>{…}) //有新数据可读时触发,每次读取64KB
rs.on(‘end’,()=>{…}) //所有数据读取完成时触发
rs.on(‘error’,()=>{…}) //发生错误时触发
var fs = require("fs");

// 异步读取
fs.readFile("input.txt", function (err, data) {
  if (err) return console.error(err);
  console.log("异步读取: " + data.toString());
});

// 同步读取
var data = fs.readFileSync("input.txt");
console.log("同步读取: " + data.toString());

console.log("异步读取此时还在读取中...");
操作API
异步写入Ⅰfs.write(fd, buffer, offset, length[, position], callback)
同步写入Ⅰfs.write(fd, data[, position[, encoding]], callback)
异步写入Ⅱfs.writeFile(file, data[, options], callback)
同步写入Ⅱfs.writeFileSync(file, data[, options])
流式写入
(适合频繁写入)
let ws = fs.createWriteStream(file)
ws.write(…)
ws.end()
var fs = require("fs"); 

// 异步写入
fs.writeFile("input.txt", "Hello I'm Niki", function (err) {
  if (err) return console.error(err);
  console.log("数据写入成功!");
  data = fs.readFileSync("input.txt");
  console.log("写入的数据是:" + data);
});
操作API
异步打开文件fs.open(path, flags[, mode], callback)
同步打开文件fs.openSync(path, flags[, mode])
异步关闭文件fs.close(fd, callback)
同步关闭文件fs.closeSync(fd, callback)
flags参数描述
r只读模式打开,文件必须存在
rs同步+只读模式打开,文件必须存在
r+读写模式打开,文件必须存在
rs+同步+读写模式打开,文件必须存在
w只写模式打开,文件不存在则会创建文件,文件已存在则覆盖原来的内容
w+读写模式打开,文件不存在则会创建文件,文件已存在则覆盖原来的内容
wx+类似于w+,但是要求要读写的文件不存在
a只写模式打开,文件不存在则会创建文件,文件已存在则在原来的内容后面追加
a+读写模式打开,文件不存在则会创建文件,文件已存在则在原来的内容后面追加
ax类似于a,但是要求要写入的文件不存在
ax+类似于ax,但是要求要读写的文件不存在
var fs = require("fs");

//读写打开  fd是句柄
fs.open("test1.txt", "r", (err, fd) => {
  if (err) return console.log("打开文件时出错辣!");
  // 准备一个用于读取文件的缓冲区
  const buffer = Buffer.alloc(1024);
  // 读取文件到缓冲区中
  fs.read(fd, buffer, 0, buffer.length, 0, (err, bytesCounts, buffer) => {
    if (err) return console.log("读取文件时出错辣!");
    console.log(buffer.toString());
    //关闭文件
    fs.close(fd, (err) => {
      if (err) return console.log("关闭文件时出错辣!");
    });
  });
});
操作API
异步获取文件信息fs.stat(path, callback)
同步获取文件信息fs.statSync(path, callback)
stats的方法描述
isFile()是否为文件
isDirectory()是否为文件夹
isBlockDevice()是否为块设备
isCharacterDevice()是否为字符设备
isSymbolicLink()是否为软连接
isFIFO()是否为特殊类型的命令管道
isSocket()是否是Socket
var fs = require("fs");

// 获取文件信息
fs.stat("test1.txt", function (err, stats) {
  console.log(stats.isFile()); //true
});
操作API
异步截断文件fs.ftruncate(fd, len, callback)
同步截断文件fs.ftruncateSync(fd, len)
var fs = require("fs");
var buf = new Buffer.alloc(1024);

// text1.txt的内容是26个字母
fs.open("test1.txt", "r+", function (err, fd) {
  if (err) return console.log("打开文件出错辣!");
  // 截断文件
  fs.ftruncate(fd, 10, function (err) {
    if (err) console.log("截断文件出错辣!");
    //读取文件
    fs.read(fd, buf, 0, buf.length, 0, function (err, bytesCounts, buffer) {
      if (err) console.log("读取文件出错辣!");
      console.log(buffer.toString());
      //关闭文件
      fs.close(fd, (err) => {
        if (err) return console.log("关闭文件时出错辣!");
      });
    });
  });
});
操作API
异步删除文件Ⅰfs.unlink(path, callback)
同步删除文件Ⅰfs.unlinkSync(path)
异步删除文件Ⅰfs.rm(path, callback)
同步删除文件Ⅰfs.rmSync(path)
var fs = require("fs");

fs.unlink("test2.txt", function (err) {
  if (err) return console.log("删除文件出错辣!");
  console.log("文件删除成功!");
});
操作API
异步创建目录fs.mkdir(path[, options], callback)
同步创建目录fs.mkdirSync(path[, options])
var fs = require("fs");

// 默认不递归创建目录,因此 temp 目录必须存在
// 如果写 /temp 的话,对应的是 D:\temp  如果写 temp 的话,对应的是本js文件所在的目录\temp
fs.mkdir("temp/test/", function (err) {
  if (err) return console.log("创建目录失败");
  console.log("创建目录成功");
});

// 可以递归创建目录,可以不用存在 temp1、temp2
fs.mkdir("temp1/temp2/test/", {recursive: true}, function (err) {
  if (err) return console.log("创建目录失败");
  console.log("创建目录成功");
});
操作API
异步读取目录fs.readdir(path, callback)
同步读取目录fs.readdirSync(path)
var fs = require("fs");

// 先在当前文件夹下创建 temp1/temp2、temp1/temp3/temp4/
fs.readdir("temp1/", function (err, files) {
  if (err) return console.log("读取目录失败");
  files.forEach((file) => {
    console.log(file);
  });
});
操作API
异步删除目录Ⅰfs.rmdir(path[, options], callback)
同步删除目录Ⅰfs.rmdirSync(path[, options])
异步删除目录Ⅱfs.rm(path[, options], callback)
同步删除目录Ⅱfs.rmSync(path[, options])
var fs = require("fs");

// 要求目录为空,不能有文件 让recursive为true就可以不为空
fs.rmdir("temp1/", function (err, files) {
  if (err) return console.log("删除目录失败");
  console.log('删除目录成功')
});
操作API
文件复制const rs=fs.createReadStream(file1)
const ws=fs.createWriteStream(file2)
rs.pipe(ws)
var fs = require("fs");

let rs = fs.createReadStream("test1.txt");
let ws = fs.createWriteStream("test2.txt");
rs.pipe(ws);
操作API
异步文件重命名 / 移动fs.rename(old,new,callback)
同步文件重命名 / 移动fs.renameSync(old,new)
var fs = require("fs");

//重命名
fs.rename("test1.txt","test3.txt",err=>{
    if(err) return console.log("重命名文件出错辣!")
    console.log("重命名文件成功");
})

//移动
fs.rename("test2.txt", "temp1/test2.txt", (err) => {
  if (err) return console.log("移动文件出错辣!");
  console.log("移动文件成功");
});

      2.3、path 模块

        path模块是用于处理文件路径和目录路径的工具。它提供了一系列用于处理文件路径的方法,可以跨平台地生成正确的路径

API描述
path.join([…paths])将所有给定的路径片段连接到一起并返回规范化的路径
path.resolve([…paths])将路径或路径片段的序列解析为绝对路径。它类似于path.join(),但是总是返回绝对路径
path.sep返回当前操作系统所使用的路径分隔符。在Unix-like系统中,它是’/‘,在Windows系统中,它是’\’
path.parse(path)将路径解析为一个对象,该对象包含路径的各个部分,如目录名、文件名、扩展名等
path.basename(path[, ext])返回路径的最后一部分(文件名),可选择性地去掉扩展名
path.dirname(path)返回路径中的目录名
path.extname(path)返回路径的扩展名部分
const path = require("path");

const fullPath = path.join("a", "b");
console.log(fullPath);

const absolutePath = path.resolve("folder", "file.txt");
console.log(absolutePath);

console.log(path.sep);

const parsedPath = path.parse("/path/to/file.txt");
console.log(parsedPath);

const fileName = path.basename("/path/to/file.txt");
console.log(fileName);

const directoryName = path.dirname('/path/to/file.txt');
console.log(directoryName);

const ext = path.extname('/path/to/file.txt');
console.log(ext);

      2.4、 HTTP 协议

            2.4.1 HTTP 请求报文和响应报文

HTTP请求报文的结构描述
请求行请求行由请求方法、请求的资源路径和HTTP协议版本组成,它们之间以空格分隔。常用的请求方法包括GET、POST、PUT、DELETE等
请求头请求头部包含了一系列键值对,每个键值对表示一个请求头字段和对应的值。请求头部以键值对的形式包含了关于客户端、请求的资源、所需的服务器行为等信息
空行请求头部和请求体之间由一个空行分隔。这个空行是必需的,它标志着请求头部的结束
请求体(可选)对于像POST这样的请求,通常会在请求报文中包含一个消息体,用来向服务器发送数据
请求头的常用字段描述
Accept指定客户端能够接受的内容类型,如text/html、application/json等
Accept-Encoding指定客户端支持的内容编码方式,如gzip、deflate等
Accept-Language指定客户端接受的语言类型,如en-US、zh-CN等
Authorization用于身份验证的凭证,常用于发送基本认证或Bearer令牌
Cache-Control控制缓存的行为,例如no-cache、max-age等
Content-Length请求或响应中的实体主体的大小(以字节为单位)
Content-Type请求或响应中的实体主体的类型,例如application/json、text/plain等
Cookie发送已存储在客户端的Cookie信息
Host指定被请求资源的主机名和端口号
Referer指示请求的原始页面的URL
User-Agent包含了发起请求的用户代理的信息,通常是浏览器类型和版本
Connection指定与客户端的连接类型,如keep-alive、close等
Origin指示请求的发起源,用于跨域请求
If-None-Match用于条件性请求,指定如果ETag值与服务器上的匹配,则返回304未修改状态码
If-Modified-Since用于条件性请求,指定如果资源自指定日期以来未修改,则返回304未修改状态码
Range指定请求的资源范围,用于分块下载或断点续传
HTTP请求报文的结构描述
响应行由HTTP协议版本、状态码和状态消息组成,它们之间以空格分隔
响应头响应头部包含了一系列键值对,每个键值对表示一个响应头字段和对应的值。响应头部包含了关于服务器、返回资源、传输的数据类型等信息
空行响应头部和响应体之间由一个空行分隔。这个空行是必需的,它标志着响应头部的结束
响应体(可选)响应报文中的消息体包含了服务器返回的实际数据,通常是请求的资源或错误信息
常见状态码对应的状态消息描述
1xxInformational信息性状态码,表示请求已被接收,继续处理
100Continue请求的起始部分已经收到,客户端应该继续请求
101Switching Protocols服务器已经理解了客户端的请求,并将通过Upgrade消息头通知客户端切换协议
2xxSuccess成功状态码,表示请求被成功接收、理解、接受和处理
200OK请求成功
201Created请求已经被实现,并且一个新的资源已经被创建
204No Content服务器成功处理了请求,但不需要返回任何实体内容
3xxRedirection重定向状态码,表示需要客户端采取进一步的动作来完成请求
301Moved Permanently资源已被永久移动到新位置
302Found求的资源现在临时从不同的URI响应
304Not Modified资源未修改,客户端可以使用缓存的版本
4xxClient Error客户端错误状态码,表示客户端提交的请求有错误
400Bad Request服务器无法理解请求
401Unauthorized请求需要用户身份认证
404Not Found请求的资源未找到
5xxServer Error服务器错误状态码,表示服务器在处理请求时发生错误
500Internal Server Error服务器遇到了一个未知的错误
503Service Unavailable服务器当前无法处理请求,通常是由于维护或过载
响应头的常用字段描述
Content-Type指示响应中的实体主体的类型,如text/html、application/json等
Content-Length指示响应中的实体主体的大小(以字节为单位)
Content-Encoding指示响应中的实体主体的内容编码方式,如gzip、deflate等
Content-Disposition指示客户端如何处理响应的实体主体,常用于下载文件
Cache-Control控制缓存的行为,例如no-cache、max-age等
Location用于重定向,指示客户端应该访问的新位置
Set-Cookie用于在客户端设置Cookie
Expires指示响应过期的日期和时间
Last-Modified指示响应资源的最后修改日期和时间
ETag指示响应资源的实体标签,用于实现缓存验证
Server指示响应中所使用的服务器软件名称和版本
Date指示响应的日期和时间
Allow指示服务器支持的HTTP方法
Access-Control-Allow-Origin指示响应是否允许跨域请求
WWW-Authenticate指示客户端需要进行身份验证,并提供认证方式
X-Frame-Options指示响应是否允许在框架中加载

            2.4.2 http 模块

http 模块的 API描述
http.createServer([options][, requestListener])创建一个HTTP服务器实例
http 实例的 API描述
server.listen([port][, hostname][, backlog][, callback])启动服务器监听指定的端口和主机
server.close([callback])关闭服务器,停止接受新的连接
回调函数中req、res的API描述
req.url获取客户端请求的URL路径
req.method获取客户端请求的HTTP方法
req.httpVersion获取 Http 协议的版本号
req.headers获取客户端请求的所有HTTP头部信息
req.params获取请求的路由参数(在使用某些框架或路由器时可用)
req.query获取请求的查询字符串参数
req.body获取请求的消息体(通常用于POST请求,需要使用body-parser等中间件解析)
req.ip获取客户端的IP地址
req.on(‘data’, (chunk) => {…})当请求数据(请求体)可用时触发
req.on(‘err’, (err) => {…})当请求发生错误时触发
req.on(‘end’, () => {…})当请求数据传输完成时触发
res.write(chunk[, encoding][, callback])写入响应体
res.writeHead(statusCode[, statusMessage][, headers])写入响应头部
res.setHeader(name, value)设置响应头部的值
res.status(statusCode)设置响应的状态码
res.send([body])发送响应给客户端
res.end([data][, encoding][, callback])结束响应,可选地写入最后一块数据

Tip
1、要让响应中文,需要设置: res.setHeader(“content-type”,“text/html;charset=utf-8”); 这行代码是在设置 HTTP 响应头部的内容类型。具体来说,它告诉客户端(通常是浏览器)关于服务器发送的数据的类型和字符编码

const http = require("http");

const serve = http.createServer((req, res) => {
  let body=''
  req.on('data',chunk=>{
    body+=chunk
  })
  req.on('end',()=>{
    res.end('请求体的内容是'+body)
  })
});

serve.listen(3000);
console.log(`服务器正在运行:http://localhost:3000`)

            2.4.3 URL 构造函数

        URL 构造函数用于处理和解析 URL 。它允许你将 URL 字符串解析为各个组件(如协议、主机、路径等)

URL实例的参数描述
href完整的解析后的URL字符串
originURL的起源,包含协议、主机和端口(如果有的话)
protocolURL的协议部分,例如 http
usernameURL中的用户名部分
passwordURL中的密码部分
hostURL的完整主机部分,包含主机名和端口(如果有的话)
hostnameURL的主机名部分
portURL的端口部分
pathnameURL的路径部分
searchURL的查询字符串部分,包括?
searchParams一个URLSearchParams对象,包含了查询参数的键值对
hashURL的哈希部分,包括#
const http = require("http");

const server = http.createServer((req, res) => {
  const urlString = "https://www.example.com/path#abc?query=value";
  const parsedUrl = new URL(urlString);
  console.log(parsedUrl);
  console.log(parsedUrl.searchParams.get("query"));
  res.end("hello world!");
});

server.listen(3000, () => {
  console.log("服务器已启动:http://localhost:3000");
});

            2.4.4 搭建静态资源服务

        首先搭建文件结构如下

根目录/                 # vsCode打开的文件夹
├── page/
│   ├── css/
│   │   └── index.css
│   ├── images/
│   │   └── 1.png
│   ├── js/
│   │   └── index.js
│   └── index.html
└── server.js

        入口文件编写代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/index.css">
</head>
<body>
    <h1>Hello World!</h1>
    <img src="images/1.png" alt="...">
</body>
</html>

        在 vsCode 中 Open With Live Serve 所开启的服务器的根目录是 vsCode 所打开的文件夹,即是上面文件结构中的“根目录”,当我们使用 Open With Live Serve 运行这个 html 文件的时候,请求的 url 即为 http://127.0.0.1:5500/page/index.html

结构描述
http://127.0.0.1:5500Open With Live Serve开启的服务器位置
/page/index.html运行的 html 文件所在服务器中的路径

        因此以后使用 Open With Live Serve 运行一个文件的时候可以很方便地知道这个文件的请求 url 是什么,即 http://127.0.0.1:5500 + 该文件所在的路径(以 vsCode 打开的文件夹为根目录)
        也因此上述的文件可以正常地运行,因为当该 html 文件以相对路径的方式来请求 css 文件时,请求的 url 是 http://127.0.0.1:5500/page/css/index.css,而正好服务器根目录下正好有 /page/css/index.css 这个文件,因此可以请求成功,代码中所示的图片也是同理可以请求成功

相对路径和绝对路径对比
1、css/index.css:即 html 所在文件夹 http://127.0.0.1:5500/page 下的 css/index.css,即 http://127.0.0.1:5500/page/css/index.css
2、/css/index.css:即服务器根目录 http://127.0.0.1:5500 下的 css/index.css,即 http://127.0.0.1:5500/css/index.css

因此如果在代码中将css路径改成 /css/index.css 的话,按照上述给出的文件结构, Open With Live Serve 所开启的服务器将会找不到相应的css文件

        而当我们使用 http 模块来开启服务器时,服务器的根目录是 server.js 文件所在的文件夹,按照上面的文件结构,http 模块开启的服务器根目录也刚好是 vsCode 打开的文件夹,现我们设计服务器代码如下

const http = require("http");
const fs = require("fs");
const path = require("path");

const server = http.createServer((req, res) => {
  // 设置响应头
  const ext = path.extname(req.url);
  if (ext === ".css")
    res.setHeader("Content-Type", "text/css; charset=utf-8"); //这样css才能正确显示
  else res.setHeader("Content-Type", "text/html; charset=utf-8"); //这样html文档才能显示中文

  // 获取请求url的请求路径
  const {pathname} = new URL(req.url, "http://localhost:3000");

  // 拼接文件路径并防止目录遍历
  // let filePath = path.join(__dirname, "page", pathname);
  let filePath = path.join("page", pathname);

  // 如果请求根路径,返回index.html
  if (pathname === "/") {
    // filePath = path.join(__dirname, "page", "index.html");
    filePath = path.join("page", "index.html");
  }

  // 读取文件并处理错误
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.statusCode = 404;
      res.end("404 Not Found");
      return;
    }
    res.end(data);
  });
});

server.listen(3000, () => {
  console.log("服务器已启动: http://localhost:3000");
});

TIP
1、path.join(“page”, “index.html”):这是相对路径的方式,因为page前没有 / ,因此对应的文件是server.js所在文件夹/page/index.html —— 成功
2、path.join(__dirname, “page”, “index.html”):这是绝对路径的方式,因为有__dirname,这个是server.js所在文件夹的绝对路径,因此对应的文件依然是server.js所在文件夹/page/index.html —— 成功
3、path.join(“/page”, “index.html”):这是绝对路径的方式,因为page前有 / ,而且 / 代表着server.js 所在的盘符,例如 server.js 处于D盘下,那么 / 其实就是 D:/,因此对应的文件是 D:/page/index.html —— 失败

            2.4.5 媒体类型 MINE

        MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是一种互联网标准,最初用于电子邮件,以便在电子邮件中发送文本、图像、音频、视频等多种类型的内容。后来,MIME的使用范围扩展到HTTP协议,在Web上用于描述和处理各种文件类型,即为了让接收端知道这些数据的类型和该如何处理它们,需要使用MIME类型来标识数据类型
        例如对于一份css代码,当服务器发送给客户端的时候,客户端既可以将其视为一份纯文本,也可以将其视为一份用于美化页面的指南,那客户端该如何来看待这份代码,可以在服务器响应这份代码的时候,使用content-type来告诉客户端要怎么看待它

常见的mime类型content-type值
htmltext/html
csstext/css
jstext/javascript
pngimage/png
jpgimage/jpg
gifimage/gif
mp4video/mp4
mp3audio/mpeg
jsonapplication/json
未知application/octet-stream(八进制流)

Tip:当浏览器在遇到application/octet-stream(未知)类型的资源时,会对响应体的内容进行独立存储,也就是常见的下载效果

            2.4.6 中文乱码

        当服务器在发送响应的时候,响应体会经过 utf8 编码之后再发送给客户端,因此我们可以通过告知客户端要使用 utf-8 来解码响应体的内容,这个可以通过设置content-type响应头来实现

content-type:charset=utf-8
当然也可以结合mine一起设置在content-type中,例如content-type:text/css;charset=utf-8

        另一种方式是,在html代码中加入<meta charset=“UTF-8”>,这是一个 HTML 元素,用于指定网页的字符编码,它告诉浏览器应该使用 UTF-8 编码来解释和显示网页内容,不过仅有这样设置的话,只会让html文档正确显示出中文,而当我们查看css这些其他类型文件的响应体时,会发现其中的中文依然是乱码,因此还是建议使用前一种方法

      2.5、模块化编程

            2.5.1 模块化介绍

        ① 模块化与模块:将一个复杂的程序文件依据一定的规则、规范拆分为多个文件的过程称为模块化;其中拆分出的每个文件就是一个模块,模块的内部数据是私有的,不过模块可以以暴露内部数据以供其他模块使用
        ② 模块化项目:编码时是按照模块一个一个编码的,整个项目就是一个模块化的项目
        ③ 模块化的好处:防止命名冲突、高复用性、高维护性

            2.5.2 模块暴露数据的方式

方式
module.exports = 暴露的东西(暴露多个的时候使用对象形式)
exports.自定义名 = 暴露的东西

TIP
1、module.exports 是默认导出的对象,可以给 module.exports 赋任何值,包括对象、函数、字符串等
2、exports 是 module.exports 的一个引用,最初指向 module.exports,如果你直接给 exports 赋值,它将不再指向 module.exports,而是指向你给它赋的值,因此 exports 只能是通过“exports.自定义名=暴露的东西” 这种方式来为 module.exports 这个对象身上追加东西,而不能通过 “export={…}” 这种方式

function a() {
  console.log("aaa");
}

function b() {
  console.log("bbb");
}

// 在其他文件通过require来导入模块,例如const me = require(这个文件),然后me就是这里暴露的东西

// module.exports = a; //[Function: a]
// module.exports = {a, b}; //{ a: [Function: a], b: [Function: b] }
// exports.a = a; //{ a: [Function: a] }
exports.a = a;exports.b = b; //{ a: [Function: a], b: [Function: b] }

            2.5.3 模块导入注意事项

        ① 对于js、json文件,导入时可以不加后缀,c++编写的node扩展文件也可以不加后缀(但是一般用不到)
        ② 如果导入其他类型的文件或者没有后缀名的文件,会以js文件进行处理
        ③ 如果导入的是一个文件夹,会先检查该文件夹下 package.json 配置文件的 main 属性所指示的文件,如果 main 属性不存在或者 package.json 配置文件不存在,则会检查文件夹下的 index.js 或 index.json 如果也不存在的话,那就会报错
        ④ 导入 node 内置模块时,直接 require(模块名) 即可,例如require(‘http’)

Tip:模块暴露的module.exports、exports以及模块导入的require这些都是CommonJS模块化规范中的内容,而Node.js实现了CommonJS模块化规范,因此也是用的这些接口

            2.5.4 模块导入的流程

         当在 Node.js 中运行 JavaScript 文件时,Node.js 会用一个函数包装你的模块代码。这个函数包装器提供了五个特定的参数:exports、require、module、__filename 和 __dirname

查看该函数包装器:console.log(arguments.callee.toString());

// 代码内容
const info = {name: "niki", age: 18};
module.exports = info;
// 查看该函数包装器
console.log(arguments.callee.toString());
// 运行结果
function (exports, require, module, __filename, __dirname) {
	const info = {name: "niki", age: 18};
	module.exports = info;
	console.log(arguments.callee.toString());
}
参数描述
exports一个对象,用于导出模块的公开接口。可以添加属性或方法到这个对象上,从而使这些属性或方法可以被其他模块访问
require一个函数,用于引入其他模块。可以用来加载 Node.js 内置模块或其他文件中的模块
module代表当前模块自身。它有一个 exports 属性,这个属性最终会被返回给调用 require() 的代码
__filename当前模块文件的绝对路径
__dirname当前模块目录的绝对路径

         当我们在导入模块的时候,nodejs会将模块的代码用上述的函数包装器进行封装、运行,作为导入流程的一个步骤,现用伪代码来模拟一下导入模块的流程

const fs = require("fs");
const path = require("path");

let caches = {};

function require(file) {
  // 1. 将相对路径转为绝对路径,定位目标文件
  let absolutePath = path.resolve(__dirname, file);
  // 2. 缓存监测
  if (caches[absolutePath])
    return caches[absolutePath];
  // 3. 读取文件的代码
  let code = fs.readFileSync(absolutePath).toString();
  // 4. 包裹为一个函数然后立即执行
  let module = {exports: {}};
  let exports = module.exports;
  let wrappedCode = `(function (exports, require, module, __filename, __dirname) {
    ${code}
  })(exports, require, module, absolutePath, path.dirname(absolutePath));`;
  eval(wrappedCode);
  // 5. 缓存结果
  caches[absolutePath] = module.exports;
  // 6. 返回模块的导出对象
  return module.exports;
}

// 示例调用
const someModule = require("./someModule.js");

            2.5.5 CommonJS模块化规范

        CommonJS 是一种模块规范,主要用于在服务器端 JavaScript 环境中组织和管理代码。Node.js 是 CommonJS 模块规范的最著名实现。CommonJS 规范定义了一个模块的结构

TIP:CommonJS 与 NodeJS 的关系有如 ECMAScript 与 JavaScript 的关系

        CommonJS 规范主要包括如下几个方面
        ① 模块可以通过 module.exports 或 exports 对象来导出变量、函数或对象,使其可以被其他模块导入和使用

// 导出一个函数
module.exports = function() {
  console.log('Hello, CommonJS!');
};

// 导出一个对象
module.exports = {
  greet: function() {
    console.log('Hello, CommonJS!');
  }
};

        ② 模块可以使用 require 函数来导入其他模块

const greet = require('./greet');
greet(); // 输出: Hello, CommonJS!

        ③ 每个文件都是一个独立的模块,具有独立的作用域。文件中的变量和函数不会污染全局作用域

// foo.js
let localVariable = 'This is local to foo.js';

// bar.js
let localVariable = 'This is local to bar.js';

        ④ 当一个模块第一次被加载时,Node.js 会缓存该模块的导出对象,以后再次加载时会直接从缓存中获取,而不是重新执行模块代码

const foo = require('./foo');
const fooAgain = require('./foo');
console.log(foo === fooAgain); // 输出: true

        ⑤ require 函数可以加载相对路径、绝对路径或核心模块(如 fs、http 等)

const fs = require('fs'); // 加载核心模块
const localModule = require('./localModule'); // 加载相对路径模块
const absoluteModule = require('/path/to/module'); // 加载绝对路径模块
CommonJS存在的缺点描述
同步加载require 是同步的,适合服务器端,不适合浏览器端(尽管有工具可以将 CommonJS 模块打包成浏览器可用的代码)
缺乏灵活性与 ES6 模块相比,CommonJS 的一些功能较为有限,如动态导入(import())和模块拆分等

            2.5.5 包管理工具

        包管理工具(Package Manager)是用于管理软件包(libraries、modules、frameworks 等)的工具。它们简化了软件包的下载、安装、更新、配置和删除过程,并管理软件项目中的依赖关系。包管理工具在开发中起着关键作用,因为它们可以帮助开发者轻松地引入和管理项目所需的外部库和模块

JS 常见的包管理工具描述
npm
(Node Package Manager)
① 用于 Node.js 的包管理工具,也是 JavaScript 社区最流行的包管理工具
② 提供了一个公共注册表(npm registry)来托管和分享开源包
③ 命令行工具用于安装、更新和管理包
yarn① Facebook 开发的替代 npm 的包管理工具,提供更快的依赖解析和更一致的安装结果
cnpm① 是一个基于 npm 源的淘宝镜像的命令行工具,是淘宝团队为了解决 npm 官方源速度慢的问题而搭建的一个 npm 镜像
pnpm① 提供高效磁盘空间利用率和更快的安装速度的包管理工具,尤其适用于大型 monorepo 项目

TIP:python 的包管理工具是 pip、Java 的包管理工具是 Maven

操作命令描述
初始化包npm init包名不能使用中文、大写
快速初始化包npm init -y文件夹名不能有中文、大写,因为会拿文件夹名作为包名
搜索包npm s一般会去网站搜,因为在控制台搜索体验很差
安装包npm i xxxrequire包的时候,当前文件夹没有node_modules或找不到相应包的时候,会向上级目录一直找,找到盘符那,再没找到就会报错说找不到相应模块
安装生产依赖npm i -S xxx① -S 等同于 --save
② -S是默认选项
③ 包信息保存在 package.json 中的 dependencies 属性中
安装开发依赖npm i -D xxx① -D 等同于 --save-dev
② 包信息保存在 package.json 中的 devDependencies 属性中
全局安装npm i -g xxx/
查看全局安装的包npm root -g① 只有一些全局类的工具才适合全局安装,例如yarn、nodemon…
安装所有依赖npm i/
安装指定版本的包npm i xxx@版本/
删除包npm r xxx删除全局的包就是npm r -g xxx
配置命令别名Ⅰnpm run server① 在 package.json 中配置 scripts/server 字段
② npm run有自动向上级目录查找的特性,跟require一样
③ 如果使用 yarn 的话,可以简写yarn server
配置命令别名Ⅱnpm run start① 在 package.json 中配置 scripts/start 字段
② 可以简写为 npm start
切换npm源npm i -g nrmnrm use taobao① nrm 是一个用于管理 npm 源的命令行工具,可以帮助开发者快速地在不同的 npm 源之间进行切换,从而加快依赖包的下载速度,提高开发效率
nrm ls可以查看支持的镜像地址,然后直接use就行了,就没必要一直通过配置网址来切换镜像
查看配置npm config list/
查看全局安装npm list -g/
发布包npm loginnpm publish① 需要先在 npm 官网上注册账号
② 在一个文件夹中npm init,然后创建index.js,到时发布上去包的名字同package.json的name
更新发布的包/① 修改index.js代码,然后将package.json中的版本号做一个修改,继续npm publish完成更新
删除发布的包npm unpublish --force① 要在该包的文件夹中执行

            2.5.6 扩展

        除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是软件包,而不像nodejs里面这种相对小型的工具包

操作系统包管理工具
Centosyum
Ubuntuapt
MacOShomebrew
Windowschocolatey

            2.5.7 NVM

        NVM 是 “Node Version Manager” 的缩写,是一个用于管理多个 Node.js 版本的工具。使用 NVM,你可以方便地在不同的 Node.js 版本之间进行切换,这对于需要在不同项目中使用不同版本的 Node.js 的开发者来说非常有用

命令说明
nvm list available显示所有可以下载的Node.js的版本
nvm list显示已安装的版本
nvm install 版本号安装对应版本号的nodejs
nvm install lastest安装最新版本的nodejs
nvm uninstall 版本号删除对应版本号的nodejs
nvm use 版本号切换对应版本号的nodejs
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值