Node.js中的一些模块

4/26/2023

# 什么是Node.js ?

Node.js 不是一门新的编程语言, 也不是一个用于实现具体功能的第三方工具库, 而是一个基于Chrome V8引擎的 JavaScript 运行环境 , 用来支持 JavaScript 代码的执行。用编程术语来讲,Node.js 是一个 JavaScript 运行时(Runtime)。它让 JavaScript 脱离了浏览器环境,可以直接在计算机上运行,极大地拓展了 JavaScript 用途。

记录 Node.js 中的一些内置模块...

# 一、Buffer模块

Buffer ( 缓冲区 )是一个类似于数组的对象 ,用于表示固定长度的字节序列 ;Buffer本质是一段内存空间, 专门 用来存放二进制数据
Buffer实例的创建和字符串转换:

//Node.js 中创建 Buffer 的方式主要如下三种:
// Buffer.alloc()
let buff = Buffer.alloc(5) //创建一个大小为 5 个字节的缓冲区,相当于申请了 5 字节的内存空间,每个字节的值为 0
console.log(buff); // <Buffer 00 00 00 00 00>

// Buffer.allocUnsafe()
let buff2 = Buffer.allocUnsafe(5)
console.log(buff2);  //<Buffer 00 00 00 00 00>

// Buffer.from() 可以将一个字符串或数组转为Buffer
let buffer3 = Buffer.from("hello world")
let buffer4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117])
console.log(buffer3); // <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
console.log(buffer4); // <Buffer 69 6c 6f 76 65 79 6f 75>

// Buffer与字符串转换
let str = buffer3.toString()
console.log(str); // hello world

//可以直接通过 [] 下标的方式对Buffer的读取和修改 
console.log(buffer3[0]); //104
buffer3[0] = 100
console.log(buffer3[0]); // 100
console.log(buffer3.toString()); //dello world


# 二、fs 文件系统模块

fs 全称为 file system ,称之为 文件系统 ,是 Node.js 中的 内置模块。fs模块可以实现与硬盘的交互,例如文件的创建、删除、重命名、移动,以及文件内容的写入和读取,以及文件夹相关的操作。

# 2.1 文件写入

使用场景 :下载文件、安装软件、保存程序日志(如 Git)、编辑器保存文件、视频录制等需要持久化保存数据的时候,应该想到文件写入

  1. writeFile 异步写入
  2. writeFileSync 同步写入
  3. appendFile / appendFileSync 追加写入
  4. createWriteStream 流式写入

# 2.1.1 异步写入

语法: fs.writeFile( file,data,[,options ],callback )

  • file 写入的路径 ( 文件不存在会自动创建 )
  • data 要写入的数据
  • options 选项设置(可选)
  • callback 回调函数

例如需求 : 在当前 js 文件的同级目录新建一个 " 滕王阁序.txt "文件, 并在txt文件中写入内容 " 时维九月 "

// 1.想要使用 fs 模块需要先使用全局方法require( )导入 fs模块
const fs = require("fs")
// 2 异步的去做磁盘的文件写入,即 fs.writeFile( )是异步的
fs.writeFile("./滕王阁序.txt","时维九月",err=>{
    //写入成功时 形参err为null; 写入失败时 err为错误信息对象
    if(err){
        console.log("写入失败");
        return
    }
    console.log("写入成功");
})

console.log(111)
//先打印111,再打印异步的回调函数里的  "写入成功"或" 写入失败"

# 2.1.2 同步写入

语法: fs.writeFileSync( file, data ,[,options ] ),无回调函数

 //同步的去做磁盘的文件写入,即 fs.writeFileSync( )是同步的
fs.writeFileSync("./滕王阁序.txt",",序属三秋")

# 2.1.3 追加写入(异步)

追加写入就是将写入内容追加至文件的末尾,语法与异步写入fs.writeFile( )相同

fs.appendFile("./滕王阁序.txt",",潦水尽而寒潭清",err=>{
    if(err){
        console.log("追加内容失败");
        return
    }
    console.log("追加内容成功");
})
console.log(11);

//注: 使用异步写入 fs.writeFile( ) 的配置项 { flag:"a" }也可以实现 追加写入的效果  ( 若无此配置项会清空文件内容后再写入内容 )
fs.writeFile("./滕王阁序.txt",",俨骖騑于上路",{ flag:"a" }, err=>{
    if(err){
        console.log("追加内容失败");
        return
    }
    console.log("追加内容成功");
})

# 2.1.4 同步追加写入

语法:与同步写入fs.writeFileSync( )相同

fs.appendFileSync("./滕王阁序.txt","\r\n烟光凝而暮山紫") // fs模块中 \r\n表示换行

# 2.1.5 流式写入

程序打开一个文件是需要消耗资源的,流式写入可以减少文件的打开和关闭次数,因此流式写入适合大文件写入和频繁写入的场景;而writeFile( )适合的是写入频率较低的场景

//创建写入流对象
const ws = fs.createWriteStream("./早春呈水部张十八员外·txt")

//调用写入流对象的write()方法写入内容
ws.write("天街小雨润如酥\r\n");
ws.write("草色遥看近却无\r\n");
ws.write("最是一年春好处\r\n");
ws.write("绝胜烟柳满皇都\r\n");

//关闭流通道(可选)
ws.close()

# 2.2 文件读取

就是通过程序从文件中取出其中的数据, 场景:电脑开机、程序运行 、编辑器打开文件、 查看图片 、播放视频 、播放音乐 、 Git 查看日志( git log ) 、上传文件 、查看聊天记录 。

  1. readFile 异步读取
  2. readFileSync 同步读取
  3. createReadStream 流式读取

# 2.2.1 异步读取

语法: fs.readFile( path [, options], callback )

  • path 文件路径
  • options 选项配置
  • callback 回调函数( null或错误信息,读取到的数据 )
//异步的读取文件
const fs = require("fs")
fs.readFile("./早春呈水部张十八员外·txt",(err,data)=>{
    if(!err){
        console.log(data); // 打印的是二进制Buffer
        console.log(data.toString()); // 把Buffer转字符串,可以得到正确的诗词
        return
    }
    console.log("读取失败");
})

# 2.2.2 同步读取

语法:fs.readFileSync( '文件路径' )

const fileBuffer = fs.readFileSync("./早春呈水部张十八员外·txt")
console.log(fileBuffer.toString());

# 2.2.3 流式读取

//创建流式读取对象
const rs = fs.createReadStream("./video/dancer.mp4")
//读取: chunk是大小为64kB的每一个块,读取视频的一个块执行一次回调函数
rs.on("data",chunk=>{
    // console.log(chunk); //每一个块的Buffer
    console.log(chunk.length) //65536byte ===>64kB
})

//读取结束
rs.on("end",()=>{
    console.log("读取视频完成");
})

# 2.3 文件复制

在 Node.js 中可以使用 fs.copyFile( ) 实现文件的复制功能,也可以既复制文件又更改文件名字

const fs = require("fs")
const path = require("path")
const clonePath = path.resolve(__dirname,"../test/server.js")
const newPath = path.resolve(__dirname,"./video/newServer.js")
fs.copyFile(clonePath,newPath,err=>{
    if(err)console.log(err)
    else console.log("server.js文件复制并更改名字成功")
})

文件先读再写就可以完成一个复制功能

//方式一:使用常规的文件读取后再写入另一个文件
const fs = require("fs")
//读取
const readFiles = fs.readFileSync("./video/dancer.mp4")
//写入
fs.writeFileSync("./video/dancer-2.mp4",readFiles)

//方式二 : 使用流式读取和写入 (性能更好)
//创建读取流对象
const rs = fs.createReadStream("./video/dancer.mp4")
//创建写入流对象
const ws = fs.createWriteStream("./video/dancer-3.mp4")
rs.on("data",chunk=>{
    //边读边写,将每一个分片写入文件
    ws.write(chunk)
})
//读取完成时触发
rs.on("end",()=>{
    console.log("文件复制成功,一份新的dancer视频文件复制到video文件夹下");
})

//方式三 : pipe ()
//在方式二中不必再去绑定读取流的data事件,直接调用 pipe方法也可以实现复制
rs.pipe(ws)

# 2.4 文件重命名与移动

在 Node.js 中,可以使用 rename renameSync 来移动或重命名文件或文件夹
语法:

  1. fs.rename( oldPath, newPath, callback )
  2. fs.renameSync( oldPath, newPath )

参数说明:

  • oldPath 文件当前的路径
  • newPath 文件新的路径
  • callback 操作后的回调
const fs = require("fs")
//重命名  路径不变,改文件名即可
fs.rename("./早春呈水部张十八员外·txt","./诗词.txt",err=>{
    if(!err){
        console.log("重命名成功");
        return
    }
    console.log("重命名失败");
})

//移动,文件的名称不变,修改前面的路径(移动时,移动到不存在的文件夹会移动失败,例:poem)
fs.rename("./诗词.txt","./poem/诗词.txt",err=>{
    if(!err){
        console.log("移动成功");
        return
    }
    console.log("移动失败");
})

# 2.5 文件删除

在 Node.js 中,我们可以使用 unlink unlinkSync rm 来删除文件
语法:

  1. fs.unlink( path, callback )
  2. fs.unlinkSync( path )
    rm( ) 同上
fs.unlink("./诗词.txt",err=>{
    if(!err){
        console.log("删除成功");
        return
    }
    console.log("删除失败");
})
//第二种方式 
fs.rm("./诗词1.txt",err=>{
    if(!err){
        console.log("删除成功");
        return
    }
    console.log("删除失败");
})

# 2.6 文件夹的创建/读取/删除

借助 Node.js 的能力,我们可以对文件夹进行 创建 、读取 、删除 等操作

  1. mkdir / mkdirSync 创建文件夹
  2. readdir / readdirSync 读取文件夹
  3. rmdir / rmdirSync 删除文件夹

语法:

  • fs.mkdir( path [, options], callback )
  • fs.mkdirSync( path [, options] )

# 2.6.1 创建文件夹

//创建单个文件夹
fs.mkdir("./utils",err=>{
    if(!err){
        console.log("创建成功");
        return
    }
    console.log("创建失败");
})

//创建多层级文件夹,需要添加配置项
fs.mkdir("./audio/myAudio",{ recursive:true },err=>{
    if(!err){
        console.log("创建成功");
        return
    }
    console.log("创建失败");
})

# 2.6.2 读取文件夹下的资源

//例: 读取当前js文件的父级文件夹下有哪些资源
fs.readdir("./",(err,data)=>{
    if(!err){
        console.log(data);// 数组  [ '1.js',  'audio', 'utils', 'video' ]
    }
    console.log("读取文件夹失败");
})

# 2.6.3 删除文件夹

在 Node.js 中,我们可以使用 rmdir rm 来删除文件, 推荐使用 rm 方法

//删除单层级文件夹
fs.rmdir("./utils",err=>{
    //提示
})

 //删除多层级文件夹,需要添加 recursive配置项
fs.rmdir("./audio",{ recursive:true },err=>{
    //提示
})

//删除文件夹建议使用rm()
fs.rm("./audio",{ recursive:true },err=>{
    //提示
})

# 2.7 查看文件/文件夹的状态

在 Node.js 中,我们可以使用 statstatSync 来查看资源的详细信息
语法:

  1. fs.stat( path [, options], callback )
  2. fs.statSync( path [, options] ) 参数说明:
  • path 文件夹路径
  • options 选项配置( 可选 )
  • callback 操作后的回调
fs.stat("./video/dancer.mp4",(err,data)=>{
    if(!err){
        console.log(data); //资源的信息(资源的创建时间、修改时间、文件大小等)
        //资源是不是文件
        console.log(data.isFile());  // true 
        //资源是不是文件夹
        console.log(data.isDirectory()); // false
        return
    }
    console.log("查看资源状态失败");
})


# 三、Path模块

Node.js 中的模块提供了用于处理文件和目录的路径的实用工具。 先了解相对路径和绝对路径, 再去介绍 Path 模块提供的一些 API

# 相对路径

相对路径是指以当前文件资源所在的目录为参照基础,链接到目标文件资源(或文件夹)的路径。

//相对路径
fs.writeFileSync("./index.txt","hello")
fs.writeFileSync("index.txt","hello") //两种相对路径写法一样,都会在当前js文件同级目录新建index.txt文件

# 绝对路径

绝对路径就是文件的真正存在的路径,是指从硬盘的根目录(盘符)开始,进行一级级目录指向文件 ;
__filename : 可以大致理解为一个全局变量,表示当前js程序文件的绝对路径 ;
__dirname :可以大致理解为一个全局变量,表示当前js文件所在的文件目录的绝对路径(即,当前js文件的父级);

//绝对路径(例:在D盘根目录新建一个index.txt)
fs.writeFileSync("D:index.txt","hello")
fs.writeFileSync("C:index.txt","hello") //报错,权限不够
fs.writeFileSync(`${__dirname}/index.txt`,"hello world") //在当前js程序文件的同级目录新建了index.txt

console.log(__filename); // E:\study\node\test\index.js
console.log(__dirname); // E:\study\node\test
/* node.js 中的相对路径是按照终端所在的文件位置决定的,而不是当前js程序所在的位置决定的,这就导致在不同的
位置执行同一个js文件时,里面的相对路径都不相同(可以使用绝对路径__dirname解决这个问题) */

# path模块的一些API

  • path.resolve 把全部给定的 path 片段按规则拼接到一起, 并规范化生成的绝对路径( 常用 )
  • path.join 把全部给定的 path 片段按规则拼接到一起,并规范化生成的相对路径
  • path.sep 获取操作系统的路径分隔符
  • path.parse 解析路径并返回对象
  • path.basename 获取路径的基础名称
  • path.dirname 获取路径的目录名
  • path.extname 获得路径的扩展名

# 1. path.resolve ( )

path.resolve( ) 用于拼接规范的路径,会把一个路径或路径片段的序列解析为一个绝对路径
拼接规则: 想要理解path.resolve( )对路径片段的拼接规则,我们从三种情况中去入手 ;
情况一: path.resolve( )接收的所有路径片段中不带 / 或者为 ./   ===> 此时所有参数都会被拼接到当前目录的后面

const path = require("path")
console.log(__filename);    // E:\study\node\test\index.js
console.log(  path.resolve(__dirname)  ); //E:\study\node\test 注:当前js程序文件所在的工作目录(js文件的父级)
//  注:  不带 /的情况
console.log(  path.resolve("a") ); // E:\study\node\test\a  不加__dirname,自动找到了__dirname再去拼接a
console.log( path.resolve(__dirname,"a")  ); // E:\study\node\test\a
console.log( path.resolve(__dirname,"a","b")  ); // E:\study\node\test\a\b
//  注:  ./的情况
console.log( path.resolve(__dirname,"./a")  ); // E:\study\node\test\a
console.log( path.resolve(__dirname,"./a","./b")  ); //  E:\study\node\test\a\b
console.log( path.resolve(__dirname,"a","./b","c")  ); //  E:\study\node\test\a\b\c

情况二: path.resolve( )接收的所有路径片段带 ../   ===> 拼接的过程中碰到 ../会跳出当前文件夹( 往前跳一级 ),然后再拼接上 ../后的片段

const path = require("path")
console.log(  path.resolve(__dirname)  ); //E:\study\node\test 注:当前js程序文件所在的工作目录(js文件的父级)
console.log(  path.resolve(__dirname,"../")  ); // E:\study\node      往前跳了一级,跳出了test文件夹
console.log(  path.resolve(__dirname,"../a")  ); //E:\study\node\a    往前跳了一级,再去拼接a
console.log(  path.resolve(__dirname,"../a","../b")  ); //E:\study\node\b    跳出a文件夹再去拼接b
console.log(  path.resolve(__dirname, 'static_files/png/', '../gif/image.gif')  );  //E:\study\node\test\static_files\gif\image.gif  跳出png文件夹再去拼接gif/image.gif

情况三: path.resolve( )接收的所有路径片段带 /   ===> 以最后一个出现的带 / 参数为起始位置,不接受当前路径前的任何参数,包括文件所在路径( 即,最后一个 带 / 的路径片段为起始位置,且从当前文件所在的磁盘开始拼接 )

const path = require("path")
console.log(  path.resolve(__dirname)  ); // E:\study\node\test 注:当前js程序文件所在的工作目录(js文件的父级)
console.log(  path.resolve(__dirname,"/a")  ); // E:\a   定向到E盘,再拼接a  (最后一个带/的片段会定向到js文件所在的磁盘根目录,再去拼接)
console.log(  path.resolve(__dirname,"/a","/b")  ); // E:\b   定向到E盘,再拼接b
console.log(  path.resolve(__dirname,"./a","/b","/c")  ); // E:\c   定向到E盘,再拼接c (最后一个带/的路径片段作为起始位置,且它前面的所有路径片段都不可用)
console.log(  path.resolve(__dirname,"/a/b","./c")  ); // E:\a\b\c   定向到E盘,再拼接起始位置/a/b ,再拼接/c
console.log(  path.resolve(__dirname,"./a","/b/c","../d")  ); // E:\b\d   定向到E盘,再拼接起始位置/b/c ,然后跳出c再拼接d  

读到这,我相信你是懂path.resolve( ) 的拼接规则的

# 2. path.join ( )

path.join( ) 会把全部给定的 path 片段连接到一起,并规范化生成的相对路径 ( 不会定向到磁盘根目录 )
拼接规则:

  •   带/./ 的非空片段都会依次拼接 ( 但是 / 在开头和末尾都会保留, ./ 仅在末尾会保留。 path.resolve()则不会保留开头和末尾的斜杠 )
  • ../ 也会跳出文件夹,返回上一级 ( 但是会保留末尾的/, 这是与 resolve 不同的地方 )
  • .. 跳出文件夹,返回上一级
  • "" 空片段为空会跳过此片段

看几个例子就明白了...

console.log( path.join("b") ); //  b 
console.log( path.join("/b") ); //  /b  保留开头\
console.log( path.join("./b") ); //  b  ./不会保留开头的\
console.log( path.join("a","b") ); //  a\b  依次拼接
console.log( path.join("/a","/b/") ); //  \a\b\  依次拼接并保留末尾的\
console.log( path.join("/a","b","./") ); //  \a\b\  依次拼接并保留末尾的\
console.log( path.join("/a","./","./b") ); // \a\b 依次拼接( 第二片段已经到了a目录下,./返回a目录,再去拼接./b  )
console.log( path.join("./a","/b","/") ); //  a\b\  依次拼接,不保留前面的./ , 保留后面的 /
console.log( path.join("./","./a","/b","/") ); //  a\b\  依次拼接,不保留前面的两个./ , 保留后面的 /
console.log( path.join("./a","/b","../c") ); //  \a\c  ../跳出了b目录,进入a目录再去拼接c
console.log( path.join("./a","/b","..") ); //  a     ..跳出了b目录,进入a目录
console.log( path.join("./a","/b","../") ); //  a\  ..跳出了b目录,进入a目录,并保留了末尾的/
console.log( path.join("./a","","c") ); //  a\c

console.log(__dirname); // E:\study\node\test
console.log( path.join( __dirname,"./a","/b","c","../d/" ) ); // E:\study\node\test\a\b\d\ 

读到这,我相信你是懂path.join( ) 的拼接规则的

# 3. path.extname ( )

console.log(__filename); // E:\study\node\test\index.js
const fileExt = path.extname( __filename )
console.log(fileExt);  //  .js


# 四、http模块

谈 Node.js 的 http 模块之前,先大致了解一下什么是 HTTP。HTTP(Hypertext transfer protocol)超文本传输协议,是互联网的一种通信协议,用于客户端与服务器之间的通信,它规定了客户端和服务器之间的通信格式,包括请求与响应的格式。它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。

Node.js 中的 http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块,通过 http 模块提供的 http.createServer( ) 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务

# 4.1 创建HTTP服务

//引入http模块
const http = require("http")
//创建服务
const server = http.createServer((req,res)=>{
    // 此回调函数会在浏览器客户端发起请求的时候调用,可以获取到请求相关的信息和设置要响应的内容
    // res.end("hello world") //设置响应体
    res.setHeader("content-type","text/html;charset=utf-8")
    res.end("你好")  //响应体中文乱码,可以通过上面设置响应头添加;charset=utf-8 utf-8编码解决
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})
//注:HTTP协议的默认端口号是80,HTTPS协议的默认端口号是443
//浏览器访问 127.0.0.1:3000 即可; 如果使用了默认的80端口,访问127.0.0.1即可

# 4.2 获取请求头和请求参数相关信息

// 假设完整 url ===> http://127.0.0.1:3000/list-search?pages=1&rows=20
const http = require("http")
//引入 url 模块
const url = require("url")
//创建服务
const server = http.createServer((req,res)=>{
    //一、获取请求行和请求头相关信息
    console.log( req.httpVersion ); //获取http协议版本 ===> 1.1
    console.log( req.url ); // 获取url中的接口路径和查询字符串部分 ===>  /list-search?pages=1&rows=20
    console.log( req.method ); // GET
    console.log( req.headers ); // 获取请求头的信息 ===> 请求头信息对象
    console.log( req.headers.host ); // 请求的服务器主机地址和端口 ===> 127.0.0.1:3000

    //二、获取请求路径和请求参数相关信息 ( 借助Node.js 的 url 模块来序列化路径 )
    let pathRes = url.parse(req.url,true)   
    //获取路径
    let requestPath = pathRes.pathname
    //获取参数对象
    let requestQueryObj = pathRes.query 
    console.log( requestPath );  //  /list-search
    console.log( requestQueryObj ); // { pages: '1', rows: '20' }

    //三、使用Node.js的URL类也可以获取请求的路径和请求参数等相关信息
    const myUlr = new URL(req.url,"http:127.0.0.1") // 参数二 : 协议和域名/IP地址 
    //获取路径
    console.log( myUlr.pathname );//     /list-search
    //获取请求参数
    console.log( myUlr.searchParams.get("pages") ); //  1
    res.end("hello world")
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})

# 4.3 设置响应体相关信息

const http = require("http")
//创建服务
const server = http.createServer((req,res)=>{
    //设置响应状态码
    res.statusCode = 200
    //设置响应状态描述 
    res.statusMessage = "okok"
    //设置响应头( key,value )
    res.setHeader("content-type","text/html;charset=utf-8")
    //设置同名响应头
    res.setHeader("myCustomServer",["NodeJs","JavaScript","abc"]) //设置了三个同名的 myCustomServer 请求头 ,值分别为 NodeJs 、JavaScript、abc
    //设置响应体 ( write设置了响应体一般就不在end里再写了 ),服务端设置的响应体内容可以在浏览器Network的Response和Preview中查看
    res.write("返回一个 hello world给浏览器客户端")
    res.end()
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})

响应练习 : 尝试给浏览器客户端响应一段 html 字符串(浏览器客户端会自动解析响应体中的 html 字符串,展示到页面上) 例:响应一段 html 字符串让浏览器解析为一个两行三列的 table 表格,第一行和第二行颜色不同,并且点击单元格可以 toggle 来回切换一个黑色背景色

const http = require("http")
//创建服务
const server = http.createServer((req,res)=>{
   res.end(`
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <!-- 样式 -->
            <style>
                td{
                    padding:15px 35px;
                }
                table,td{
                    border-collapse:collapse;
                }
                table tr:nth-child(odd){
                    background-color:#40E0D0;
                }
                table tr:nth-child(even){
                    background-color:pink;
                }
                .toggleBlock{
                    background-color:#000;
                }
            </style>
        </head>
        <!-- 结构 -->
        <body>
            <table border="1">
                <tr><td /><td /><td /></tr>
                <tr><td /><td /><td /></tr>
                <tr><td /><td /><td /></tr>
            </table>
        </body>
        <script>
            //脚本
            let allTd = document.querySelectorAll("td")
            allTd.forEach(item=>{
                item.onclick = ()=>{
                    item.classList.toggle("toggleBlock")
                }
            })
        </script>
        </html> 
   `)
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})
//浏览器地址栏访问127.0.0.1:3000就可以看到这个两行三列的table表格了

上面有一个明显的问题就是, html、css、js都是在无代码提示的情况下纯手写的, 显然很不方便, 这个时候我们可以借助 fs 文件系统模块对它做一些调整

const http = require("http")
const fs = require("fs")
const path = require("path")
//创建服务
const server = http.createServer((req,res)=>{
    //现在一个.html文件里写好上面res.send中响应的内容,假设叫 table.html
    //解析文件路径
    const filepath = path.resolve(__dirname,"./table.html")
    //读取html文件内容
    const responseHtmlBuffer = fs.readFileSync(filepath)
    //设置响应体( 读取的Buffer也可以不转字符串,res.send( )接受Buffer类型和字符串类型的参数
     )
   res.end(responseHtmlBuffer.toString())
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})
//浏览器地址栏访问127.0.0.1:3000就可以看到这个两行三列的table表格了

# 4.4 搭建一个静态资源服务

  • 假设 index.html 文件里的 css、js 或者图片都是通过src或者href属性在外部链入的,这个时候浏览器解析 index.html 时, 遇到这些外部链入的资源会去发送一个新的请求去 获取这些外链资源 , 这个时候服务的回调函数会再次调用, 就需要返回相应的 css 或 js 或图片等资源 , 而不是都返回 html ;我们可以通过请求路径去返回相应资源;
  • 假设文件资源目录如下:与当前 server.js 程序同级的 pageResource 文件夹( 静态资源目录/网站根目录 )下,有 css、js、images文件夹和 index.html 文件;文件夹内存放了相应的资源, index.html 链入了这些文件文件夹内的资源

目录结构

project 
│
└───pageResource
│   │____css  
│   │    index.css
│   │    ....css
│   |   
|   |____js
|   |    index.js
│   │    ....js 
|   |
|   |____images
|   |    1.png
│   |    ....jpg  
|   |
|   |____index.html
| 
|______server.js //服务   

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 采用绝对路径的形式链入,请求资源时服务的回调函数中可以获取到此路径,再去拼接资源根目录 -->
    <link rel="stylesheet" type="text/css" href="/css/index.css" />
</head>
<body>
    <h1>返回HTML</h1>
    <img src="/images/1.png" alt="">
</body>
<!-- 相对路径也可以,但没有绝对路径可靠 -->
<script src="./js/index.js"></script>
</html>

服务server.js

//server.js
const http = require("http")
const fs = require("fs")
const path = require("path")
//创建服务
const server = http.createServer((req,res)=>{
    //获取用户请求的路径
    const {pathname} = new URL(req.url,"http:127.0.0.1") //解构赋值
    // console.log( pathname); //如 /index.html  或 /css/index.css
    //获取绝对资源路径
    const filepath = __dirname + "/pageResource" + pathname // E:\study\node\test/pageResource/css/index.css
    //读取文件内容
    fs.readFile(filepath,(err,data)=>{
        if(err){
            res.statusCode = 500
            res.end("error")
            return
        }
        //响应资源
        res.end(data)
    })
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})
  • 浏览器访问资源 127.0.0.1:3000/index.html, 发起网络请求, server.js 中的回调函数执行, 根据请求路径 /index.html 拼接的资源路径找到了相应的资源,并响应给浏览器, Network 中查看浏览器发送的几个请求中都正确返回了相应的html、css、js、图片,而不是都返回 html ;
  • 单独访问127.0.0.1:3000/css/index.css 也可以正确返回相应的css资源

访问127.0.0.1:3000/index.html



# 4.5 网页URL中的绝对路径与相对路径

绝对路径

  • https://www.baidu.com 完整地址形式, 直接指向了目标地址, 常用于网站外链
  • //www.baidu.com/img/1.png 省略协议的形式, 会去自动拼接当前网页的协议形成完整路径,再去请求资源
  • /static/img/1.png 会去自动拼接当前网页的协议、域名、端口形成完整路径再去请求资源 例:<img src="/static/img/1.png" />

相对路径
相对路径在发送请求时,会与当前路径进行一定规则的重新拼接, 再去发送请求, 例:当前网页地址为 https://www.baidu.com/pages/index.html

  • ./static/img/1.png ===> https://www.baidu.com/pages/static/img/1.png , ./ 会寻找到当前 index.html 的工作目录( 即, index.html 的父级文件夹 ), 再去拼接 /static/img/1.png
  • ./static/img/1.png ====> 同上
  • ../static/img/1.png ====> https://www.baidu.com/static/img/1.png , ../ 跳出了工作目录进入了根目录, 再去拼接 /static/img/1.png

# 4.6 响应错误信息给浏览器客户端

const http = require("http")
const fs = require("fs")
//创建服务
const server = http.createServer((req,res)=>{
    //假设需要GET请求
    if(req.method !== "GET"){
        res.statusCode = 405
        res.end('<h1> 405 Request Method Not Allowed <h1>')
        return
    }
    const {pathname} = new URL(req.url,"http:127.0.0.1") //解构赋值
    //获取绝对资源路径
    const filepath = __dirname + "/pageResource" + pathname
    fs.readFile(filepath,(err,data)=>{
        if(err){
            //判断错误码
            switch(err.code){
                case "EPERM":
                    res.statusCode = 403
                    res.end("<h1> 403 Forbidden </h1>")
                      break;
                case "ENOENT":
                    res.statusCode = 404
                    res.end("<h1> 404 Not Found </h1>");
                      break;
                default:
                    res.statusCode = 500
                    res.end("<h1> 500 Interval Server Error </h1>")
            }
            return
        }
        //正常
        res.statusCode = 200
        res.end(data)
    })
})
//监听一个端口,启动服务
server.listen(3000,()=>{
    console.log("服务启动成功");
})

# 五、模块化

在 Node.js 中, 应用由模块组成。 模块可以使用require 函数来引入其他模块,使用 module.exports 来导出模块中的数据或函数。模块化开发可以提高程序的可维护性、可扩展性和代码的复用性( 模块化开发使得程序中的代码可以被多次使用,不同的模块之间可以互相引用和调用,从而减少代码的重复编写 )。 毫无疑问, 在多人协作开发项目时, 模块化开发的思想变得尤为重要。

其实每个 js 文件就算一个模块, 每个模块都有一个独立的作用域, 在这个文件中定义的变量、函数、对象都是私有的, 对其他文件不可见。

语法 : module.exports 来导出模块 , require 函数来导入模块

  • 使用 require 函数导入 Node.js 的内置模块时无需加./ 和 ../ , 导入 自定义的模块时,相对路径必须加上相应的./ 和 ../

  • 如果导入的路径是个文件夹, 则会首先检测该文件夹下 package.json 文件中 main 属性对应的路径文件, 如果 main 属性对应路径文件存在则导入, 如果文件不存在会报错。

  • 如果导入的路径是个文件夹, 且导入的文件夹下的 package.json 文件不存在或者 package.json 文件中 main 属性不存在, 则会尝试导入文件夹下的 index.js 和 index.json, 如果还是没找到, 就会报错

//myCustomModule.js
const list = [
    {label:"商用",value:"0"},
    {label:"非商用",value:"1"}
]

const sayHello = ()=>{
    console.log("hello world")
}
//导出
module.exports =  {
    list, //ES6
    sayHello
}
//index.js
//导入自定义模块
const customFun = require("./myCustomModule.js") 
customFun.sayHello() // hello world

# 六、npm包管理工具

npm 全称 Node Package Manager , 翻译为中文意思是『Node 的包管理工具』, 是 Node.js 官方内置的包管理工具。Node.js 在安装时会自动安装 npm , 即下载安装了 Node.js 就已经可以使用 npm 了。
这里介绍一个能解决 Node.js 在使用 http 模块开发时, 每次更改代码都要重启服务的 npm 包 : nodemon , 安装这个包之后, 使用 npmmon 执行程序文件( 如 server.js ), 之后修改代码后保存会自动重启服务, 不用再去终端手动执行 node server.js 了 ( windows 执行 npmmon server.js 可能会报错, 使用管理员身份打开 PowerShell, 输入 set-ExecutionPolicy remoteSigned 回车 , 选择 "A全是" 即可)
npm 官网链接 : https://www.npmjs.com/
npm一些常用命令:
安装

  • npm -v    查看 npm 的版本
  • npm init   初始化 package.json 文件, npm init -y 会跳过提问, 直接初始化 package.json 文件
  • npm install / npm i   根据package.json 安装项目所需的依赖包
  • npm install packageName    安装某个模块 (模块记录在 package.json 的 dependencies 中)
  • npm install packageName --save / npm install packageName -S    安装某个模块 (模块记录在 package.json 的 dependencies 中)
  • npm install webpack --save-dev / npm install webpack -D  安装某个模块 (模块记录在 package.json 的 devDependencies 中, 开发环境依赖包)
  • npm install packageName -g    全局安装模块
  • npm install packageName@3.10.0    使用@符号安装指定版本

卸载

  • npm uninstall packageName   卸载模块
  • npm uninstall packageName -g  卸载全局模块

更新

  • npm update packageName --save-dev  更新某个指定的模块
  • npm update packageName -g  更新某个指定的全局模块

设置 npm 淘宝镜像

  • npm install -g cnpm --registry=https://registry.npmmirror.com

接下来尝试在项目中使用一个 npm 包...

//项目根目录初始化 package.json 文件
npm init -y
//安装 一个去重的包 uniq
npm install uniq  // 会生成node_modules 文件夹,并将uniq包加入其中;还会生成packages-lock.json锁定uniq包的版本; 包名会添加到 package.json 文件的 dependencies 中
//引入 node_modules中的 uniq
const uniq = require("uniq")
//使用
const uniqList = uniq([1,1,2,2,3,4,5,5])
console.log(uniqList) // [1,2,3,4,5]