本文最后更新于 2025-01-05,文章内容可能已经过时。

概念

NodeJs 是一个开源跨平台javascript 运行环境,可以运行Javascript代码Nodejs中内置了V8引擎!

NodeJs 可以帮我们实现,web服务器、开发工具、桌面端应用开发!

1. web服务器: 服务器就是计算机电脑,通过服务应用程序计算机部署安装后便可部署web应用!

2. 开发工具: 比如目前的webpack vite 以及 babel开发工具都是基于Node开发的!

3. 桌面应用: 比如vscodepostman 它们是基于,electron 开发的,而electron又是基于Node开发的!

浏览器和Node

  1. 浏览器:
    • Javascript浏览器环境中有两部分模块组成,分别是 ECMAScriptWebApi(BOM 和 DOM)!
    • js浏览器中的全局对象windowglobalThis;
  2. NodeJs:
    • JavascriptNode环境中也有两部分模块组成,分别是ECMAScriptNode Api!
    • jsnode环境中的全局对象global也可以是globalThis

globalThises2020新增的全局对象,用来兼容浏览器Node环境中的全局变量使用!

注意:Node环境中不能使用BOMDOM 相关 API! 但是可以使用console定时器!

Buffer

Buffer 是一组不可变固定长度内存空间,主要用来处理二进制数据流!

Buffer 可以直接对计算机内存进行操作,性能比较好!

Buffer长度大小无法改变!

Buffer 中的内存空间,每8个bit为1个字节!

创建Buffer

Buffer 是一个全局对象,可以通过这个对象来使用内置的方法,来操作Buffer数据!

  1. alloc:

    • 参数: 指定字节长度,每次创建时之前的缓冲数据都会清零!

      console.log(Buffer.alloc(10));
      

      创建10个字节长度的buffer

  2. allocUnsafe:

    • 参数: 指定字节长度,每次创建时都会保留旧的内存数据!

    • 已经使用过的缓冲数据会在allocUnsafe中继续保留!

      console.log(Buffer.allocUnsafe(10));
      

      创建10个字节长度的buffer

  3. from:

    • 参数: 类型可以是字符串,或者是数组,将两者以Buffer的形式存储!

      console.log(Buffer.from("hello"))
      // 数组形式
      Buffer.from([(105, 108, 111, 118, 101, 121, 111, 117)])
      
    • 这里每一个字母,都会在ASCII码表中找到对应的数字,然后转换为二进制数据进行存储!

    • 控制台打印的时候,不是以二进制数据展示的,而是以16进制显示的!

输出结果如下:

alloc <Buffer 00 00 00 00 00 00 00 00 00 00>
allocUnsafe <Buffer 90 76 81 32 f2 7f 00 00 00 00>
from -> <Buffer 68 65 6c 6c 6f>

allocUnsafe 这个方法创建的Buffer不安全的,每次创建内存时,都会保留旧的内存数据,不会有清空的操作!

注意:Buffer存储的是二进制数据,但在显示时以16进制形式展示!

ASCII码表参照

码表中对应的数字都是十进制数!

Buffer的操作与注意

基本操作

  1. toString:

    • 可以将 buffer 通过 toString的方式转换为字符串,格式为utf-8!
    let buf = Buffer.from([(105, 108, 111, 118, 101, 121, 111, 117)]);
    console.log("buf", buf.toString());
    
  2. 数组下标:

    • 数组下标形式获取字符中的某一项,且返回这个字符对应的ASCII码10进制数!
    let buf = Buffer.from("hello");
    console.log("buf", buf[0]) // h -> 104
    console.log("buf", buf[0].toString(2)) // h -> 所对应的 2进制数据 1101000
    

注意事项

  1. 通过下标的方式修改Buffer数据:

    buf[0] = 95; 
    
    • 这里的9510进制数10进制最大且是255,如果超出这个数值,会有精确显示问题,会舍去高数位!
    buf[0] = 300; 
    
  2. 中文字符:

    • 中文字符 utf-8buffer一个字符占用3个字节,且1个字节为8个bit!
    let buf = Buffer.from("你好"); // 6个字节
    // log buf <Buffer e4 bd a0 e5 a5 bd>
    

计算机基础了解组成

计算机的组成部分主要有,CPU 内存 硬盘 还有主板以及显卡等!

组成部件 描述
CPU CPU计算机的大脑,主要程序运算过程都是靠CPU来完成的!
内存 可以存储数据,主要都是0 和 1二进制的数据断电时,数据会丢失,但是读写速度比较快!
硬盘 可以存储数据断电时不会丢失数据,但是读写数据比较慢!
主板 主板是一块电路板,可以将以上cpu 内存 硬盘等部件通过接口结合在一起使用!
显卡 显卡主要用来外接显示器,可以在显示器中呈现运算后的效果!
风扇 风扇的作用是用来给CPU降温的,在计算过程中遇到复杂大型程序,会对CPU运行负荷加大,发热现象!

计算机的运行过程

当把计算机基本硬件组装完毕后是不能直接运行的,还需要对计算机进行系统安装

系统:是一个应用程序,系统有 windows mac 以及linux系统,在系统中,我们可以通过界面以及命令的形式来操作系统!

比如: 基本的文件存储操作,以及其它程序安装以及运行操作等,最终经过硬件运转流程,呈现在显示器中!

系统 可以看做一个所有程序的集装箱,所有的程序运行等操作都是基于系统来完成的!

一个软件运行的基本流程

软件(qq 微信),通过官方网站下载后,会存放到电脑的硬盘当中!

运行软件,软件运行文件载入到内存中,然后通过CPU 去计算,分配任务,如果遇到显示任务 ,会触发指令,将显示任务转移给显卡去做处理,最终呈现在屏幕中,如果遇到声音任务,则会将声音指令,转移给声卡去处理,最终由音响设备去播放

进程与线程

进程: 在系统中,每运行一个程序时,都会产生一个进程!

线程: 线程进程中的子集,每个进程下都会有多个线程执行任务!

NodeApi

异步同步

NodeApi中,有异步方法同步方法!

异步代码: 表示同一时间内可以做多件事,比如以上文件写入的代码就是异步的,写入文件的同时,不影响下面同步代码执行!

同步代码: 表示在执行异步代码时下面代码无法执行,是阻塞状态,需要等异步代码执行有结果时,才能继续执行!

Node Api 中,一般方法名带有Sync后缀的,都是同步方法!

异步代码块

// 异步代码
fs.writeFile(`${__dirname}/test.txt`, "三人行,必有我师焉!", (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("写入成功"); // 其次打印
  }
});
console.log("11111!") // 先打印 以上异步代码 不影响这一行代码输出

同步代码块

fs.writeFileSync(`${__dirname}/test.txt`,"三人行,必有我师焉!");
console.log("11111!") // 会等待 以上文件内容写入完毕后,才会打印!

fs模块

fs模块,主要用来操作文件的,可以在硬盘上创建文件,以及写入文件删除文件等相关操作!

文件创建与写入

writeFile 异步

writeFile方法,可以对文件内部写入指定内容,如果指定的文件不存在,则会创建这个文件,并写入内容!

返回值: undefined!

参数 类型 描述
filePath String 文件的路径,包含文件后缀,若该文件不存在,则会在路径下创建这个文件!
fileContent String 文件内容
options(可选) Object 配置对象 ,如果不写,可以直接写callback
callback Function 回调函数,如果文件创建失败时,会有个err回调参数

fs.writeFile(`${__dirname}/test.txt`, "三人行,必有我师焉!", (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("写入成功");
  }
});

writeFileSync 同步

writeFileSync 该方法与writeFile 方法一样,只不过该方法,是同步的,会阻塞其它代码运行!

let result = fs.writeFileSync(`${__dirname}/test.txt`, "三人行,则必有我师焉!");
console.log("result", result); // 返回值 undefined

文件内容追加写入

appendFile 异步追加

参数 类型 描述
filePath String 文件的路径,包含文件后缀,若该文件不存在,则会在路径下创建这个文件!
fileContent String 文件内容
options(可选) Object 配置对象 ,如果不写,可以直接写callback
callback Function 回调函数,如果文件创建失败时,会有个err回调参数

fs.appendFile(`${__dirname}/test.txt`, " \r\n天下无双", (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("追加成功");
  }
});

文本内容中加入转移字符实现换行\r\n!

appendFileSync 同步追加

fs.appendFileSync(`${__dirname}/test.txt`, " \r\n天下无双");

writeFile也可以追加文本内容,不过需要,设置配置项!

fs.writeFile(`${__dirname}/test.txt`, "\r\n writeFile追加内容", { flag: "a" }, err => {
  if (err) {
    console.log(err);
  } else {
    console.log("追加成功");
  }
})

{ flag: "a" } a 表示 append追加的意思,除了a(append),还有w(write)默认r(read)!

文件流式写入

文件流式写入,适用于批量文本内容写入,该方法会开启一个通道,当我们需要写入内容时,可以借助通道,将内容进行写入

const fs = require('fs');

// 打开文件流
const writeStream = fs.createWriteStream(`${__dirname}//观沧海.txt`);

// 写入数据
writeStream.write("观沧海\r\n");
writeStream.write("东临碣石,以观沧海。\r\n");
writeStream.write("水何澹澹,山岛竦峙。\r\n");
writeStream.write("树木丛生,百草丰茂。\r\n");
writeStream.write("秋风萧瑟,洪波涌起。\r\n");

// 关闭文件流
writeStream.end();

文件内容读取

文件内容读取用到了readFile API , 它可以通过指定路径文件,来读取文件中的内容!

const fs = require('fs');

// 使用异步方法 读取文件内容
fs.readFile(`${__dirname}/观沧海.txt`, 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});

// 使用同步方法 读取文件内容
try {
  const data = fs.readFileSync(`${__dirname}/观沧海.txt`, 'utf8');
  console.log(data);
} catch (err) {
  console.error(err);
}

文件流式读取

createReadStream

文件的流式读取,是将文件中的内容进行分块儿处理,每一次读取65536(64kb)字节大小,当读取完毕时,会触发end回调函数事件!

const fs = require('fs');

// 读取文件内容
const rs = fs.createReadStream(`${__dirname}/登录信息.txt`, { encoding: 'utf8' });

// 监听data事件,每次读取一行数据
rs.on('data', (chunk) => {
  // 如果是 媒体类型的数据 还是不要 toString()
  console.log(chunk.toString());
});

// 监听end事件,文件读取完毕
rs.on('end', () => {
  console.log('文件读取完毕');
}); 

不过可以试着读取下mp4文件类型的数据,返回二进制文件流,针对媒体类型而使用toString时,内容会显示乱码!

文件复制

首先需要引入的模块process 可以获取运行的内存占用情况!

const fs = require('fs');
const process = require("process");
  1. 使用readFileSyncwriteFileSync来实现

    const data = fs.readFileSync(`{__dirname}/71685787471_.pic_hd.jpg`);
    fs.writeFileSync(`{__dirname}/71685787471_.pic_hd_copy.jpg`, data);
    const memoryUsage = process.memoryUsage();
    
    // rss 内存使用的总量
    console.log(`Memory usage: ${Math.round(memoryUsage.rss / 1024 / 1024)} MB`);
    
  2. 使用流式读写形式实现复制操作

    const rs = fs.createReadStream(`{__dirname}/71685787471_.pic_hd.jpg`);
    const ws = fs.createWriteStream(`{__dirname}/71685787471_.pic_hd_copy.jpg`)
    
    rs.on("data", (chunk) => {
       ws.write(chunk);
    })
    
    // rs.pipe(ws); 或者使用管道与写入流建立通道,写入完毕时,将数据传输给写入流!
    
    rs.on("end", () => {
       const memoryUsage = process.memoryUsage();
    
       // rss 内存使用的总量
       console.log(`Memory usage: ${Math.round(memoryUsage.rss / 1024 / 1024)} MB`);
    })
    
    • rs.pipe(ws): 使用管道与写入流建立通道读取完毕时,将数据传输给写入流!

文件重命名移动

文件重命名移动,用到了rename API ,该方法可以完成重命名移动文件位置操作!

重命名: 保持和原有文件路径位置不变,只需要改变名字即可完成!

移动文件: 需要改变文件移动的目标路径,也可以移动同时改变文件的名称!

const fs = require('fs');

// 重命名文件
/* fs.rename(`${__dirname}/test.txt`, `${__dirname}/text_rename.txt`, (err) => {
  if (err) throw err;
  console.log("文件重命名成功!");
}); */

// 移动文件 
fs.rename("./text_rename.txt", `${__dirname}/text.txt`, (err) => {
  if (err) throw err;
  console.log("文件移动成功!");
});

第一个参数为源文件路径,第二个参数为修改或移动目标路径,第三个参数就是回调参数操作完毕后会执行回调函数!

同样,rename API 也有同步的版本 renameSync!

文件删除

文件删除有两种方法,unlinkrm 两个方法,rmNode 14.4 版本新出的,所以注意版本问题!

const fs = require("fs");

fs.unlink(`${__dirname}/text.txt`, (err) => {
  if (err) {
    console.error(err);
  } else {
    console.log("文件删除成功!");
  }
});

// node v14.4
/* fs.rm(`${__dirname}/test.txt`, (err) => {
  if (err) {
    console.error(err);
  } else {
    console.log("文件删除成功!");
  }
}); */

文件夹操作

文件夹操作,包含文件夹创建(mkdir)、读取(readdir)、删除(rmdir)相关操作,三个方法都有对应的同步方法Sync!

参数

参数 类型 描述
folderPath String 文件夹路径,可以是多个,递归创建(需要配置)
options Object 配置项
callback Function 回调函数创建结果回调函数

引入fs模块

const fs = require("fs");

  1. 创建文件夹mkdir:

    fs.mkdir(`${__dirname}/testFolder`, err => {
      if( err ) {
        console.log("创建失败!")
        return;
      }
      console.log("创建成功!")
    })
    
    • 递归创建文件夹,需要增加配置项, recursive设置为true!
    fs.mkdir(`${__dirname}/testFolder/a/b`,{ recursive: true }, err => {
     if( err ) {
       console.log("创建失败!")
       return;
     }
     console.log("创建成功!")
    })
    
  2. 读取文件夹readdir:

    fs.readdir(`${__dirname}/`, (err, files) => {
      if( err ) {
        console.log("读取失败!")
        return;
      }
      console.log("读取成功!", files)
    })
    
  3. 文件夹删除rmdirrm方法替代:

    fs.rmdir(`${__dirname}/testFolder/`,{ recursive: true }, err => {
      if( err ) {
        console.log("删除失败!")
        return;
      }
      console.log("删除成功!", files)
    })
    
    • recursivetrue时,可递归删除多层文件夹!
    • 注意事项: rmdir需要替换为rm方法,rmdir估计已经被移除!

获取文件信息

获取文件信息,需要通过stat方法来完成,它可以获取文件大小创建时间以及修改时间等状态信息!

const fs = require('fs')

// 获取文件信息
fs.stat(`${__dirname}/71685787471_.pic_hd.jpg`, (err, data) => {
  if( err ) {
    console.log("获取文件信息失败")
    return 
  }
  console.log("判断是否文件", data.isFile());
  console.log("判断是否目录", data.isDirectory());
});

/* 
  birthtime: 创建时间
  ctime: 最后一次修改文件状态的时间
  mtime: 最后一次修改时间
  atime: 最后一次访问时间
  size: 文件大小
*/

stat文件信息内容

{
  dev: 16777234,
  mode: 33060,
  nlink: 1,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 52017648,
  size: 1421100,
  blocks: 2776,
  atimeMs: 1735563016333.3337,
  mtimeMs: 1735563015153.2031,
  ctimeMs: 1735563015154.8784,
  birthtimeMs: 1735563015151.1604,
  atime: 2024-12-30T12:50:16.333Z,
  mtime: 2024-12-30T12:50:15.153Z,
  ctime: 2024-12-30T12:50:15.155Z,
  birthtime: 2024-12-30T12:50:15.151Z
}

批量修改文件名案例

const fs = require('fs');

// 批量修改文件名
let files = fs.readdirSync(`${__dirname}/`);
let needModifyFiles = files.filter(file => file.endsWith('.jpg'));
needModifyFiles.forEach((file, index) => {
  let [ name ] = file.split(".jpg");
  console.log("newFileName", `${name}_img_${index}`);
  fs.renameSync(`${__dirname}/${file}`, `${__dirname}/${name}_img_${index}.jpg`);
});

path模块

path模块 主要是用来处理路径的,文件路径拼接,路径信息获取,获取路径中的文件名等相关操作!

API 描述
path.resolve 用来拼接路径的,第一个参数为绝对路径,后面的参数为相对路径,最后做路径拼接!
path.sep 获取当前系统的分隔符,在windows下分隔符为\,在linux系统下为/!
path.parse 根据指定绝对路径进行解析,返回路径对象信息
path.basename 根据指定绝对路径,返回当前路径中的文件名
path.dirname 获取路径的目录名
path.extname 获取路径文件的扩展名
const path = require('path');

// resolve() 方法用于将多个路径片段组合成一个完整路径。
console.log(path.resolve('/foo', 'bar', 'baz')); // /foo/bar/baz

// join() 方法用于连接多个路径或路径片段,并规范化生成的路径。
console.log(path.join('/foo', 'bar', 'baz')); // /foo/bar/baz

// dirname() 方法用于返回路径的目录名。
console.log(path.dirname('/foo/bar/baz')); // /foo/bar

// basename() 方法用于返回路径的最后一部分。
console.log(path.basename('/foo/bar/baz')); // baz

// extname() 方法用于返回路径的扩展名。
console.log(path.extname('/foo/bar/baz.js')); // .js

// parse() 方法用于将路径字符串解析为对象。
console.log(path.parse('/foo/bar/baz.js')); // { root: '/', dir: '/foo/bar', base: 'baz.js', ext: '.js', name: 'baz' }

http模块

http网络请求有关,是浏览器服务器之间的一个约定!

网络基础 IP

IP地址每台设备唯一标识,通过这个唯一标识,就能在互联网中和其它设备进行通信!

IP地址 192.168.1.1 这是一个十进制数字,其实背后是32位2进制数字组成,且每8位为一个字节!

IP地址 分为局域网IP公网IP!

局域网IP: 比如家庭环境,通过路由,将IP分配每一台设备,这样在局部内的设备可以进行文件传输通信等!

公网IP: 当需要访问外网时,需要通过厂商携带光纤宽带网线等工具,进行安装后便会生成一个公网的IP,这时便可以对外网进行访问!

网络基础 端口

端口每一个应用程序的一个标识主要作用实现服务器相互不同应用之间的通信手段!

http协议中的默认端口为80默认端口,在输入ip地址发送请求时,不需要书写端口号,则默认为80端口!

例如 端口9000 http://localhost:9000/

默认端口 http://localhost:80 则 80可以省去不写

http协议

协议就是一种约定关系,需要互相遵守双方约定,而这里的双方就是浏览器服务器之间的约定!

http (Hypertext transfer protocol): 超文本传输协议!

http请求报文

http报文,是浏览器服务器之间传输时所携带的一些文本信息,包含请求行、请求头、请求体等相关信息!

请求行

请求行主要包含,请求方法、请求URL还有HTTP版本号组成!

请求方法

方法 描述
Get 用于获取数据
Post 用于新增数据
Put/Patch 用于修改数据
Delete 用于删除数据

请求URL

请求地址: https://www.baidu.com/search?name=张三&age=18

组成部分: http协议、主机地址域名、端口号、查询参数!

http协议: https://

主机地址域名: www.baidu.com 也称之为统一资源定位符,最终会被解析为IP地址,获取主机的位置!

查询参数: ?name=张三&age=18,向服务器携带的一些额外参数!

search: 路径,表示这个服务器内部的资源!

http版本

http版本号目前有,1.0 、 1.1、 2.0、 3.0!

请求头

请求头,包含了浏览器的基本信息,其内容都是以键值对儿的形式保留的!

例如: 请求的域名,以及发送请求的平台信息以及cookie等相关信息!

https://developer.mozilla.org/zh-CN/docs/Glossary/Request_header
请求体

请求体,是向服务器发送请求时,所携带的一些数据参数,通常POST请求会将数据通过请求体的方式,向服务器传输数据!

http响应报文

响应报文,就是从服务端浏览器回应的文本信息,主要包含响应行、响应头、响应体!

响应行

响应行,主要包含,http版本号、状态码、描述等信息!

状态码

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
状态码 描述
200 请求成功
403 拒绝请求
404 访问资源不存在
500 服务器内部错误

响应头和响应体

响应头

https://developer.mozilla.org/zh-CN/docs/Glossary/Response_header

响应头,包括了响应内容描述信息,例如,响应内容的格式,以及内容长度等信息!

content-type 为响应内容的格式content-length为响应内容的大小单位为字节!

响应体

响应体 根据响应(MIME)类型来返回不同结果, MIME类型有很多种,包含文本,媒体,图片,json,html,css,js类型格式!

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/MIME_types/Common_types

创建http服务

创建http服务,需要用到http模块,引入后,调用createServer方法来创建一个本地服务!

const http = require('http');

创建http服务

const server = http.createServer((request, response) => {
   response.end("hello");
})

server.listen(9000, ()=> {
  console.log("服务已启动 9000端口!")
})

listen 方法会给本地服务添加一个端口,当浏览器客户端,访问本地服务加端口时,就会调用服务内部的回调函数,可以获取客户端请求报文信息,并通过response客户端响应请求内容!

response.end("hello") , end 代表结束请求,并向客户端返回响应内容 hello!

获取http请求报文

createServer回调函数中, 回调函数的第一个形参request请求对象 ,可以帮我们获取有关请求报文的信息!


/* 
  获取请求报文信息
*/

const http = require("http");

const server = http.createServer((req, res) => {
  res.setHeader("Content-Type", "text/plain;charset=utf-8");

  res.end(`
    请求方法: ${req.method}
    请求路径: ${req.url}
    请求头: ${JSON.stringify(req.headers, null, 2)}
    请求参数: ${JSON.stringify(req.query)}
    请求ip: ${req.ip}
    请求地址: ${req.socket.remoteAddress}
    请求端口: ${req.socket.remotePort}
  `);
});

server.listen(9000, () => {
  console.log("Server is running on port 9000");
});

获取请求体,当客户端参数放入请求体,向服务端发送请求时,服务端需要通过data来监听数据的获取!

const http = require("http");

const server = http.createServer((req, res) => {
  res.setHeader("Content-Type", "text/plain;charset=utf-8");
  if (req.method === "POST") {
    let body = "";
    req.on("data", (chunk) => {
      // chunk 是一个buffer数据
      body += chunk.toString();
    });

    req.on("end", () => {
      req.body = body;
      res.end(`
          POST请求成功
          请求主体: ${req.body}
        `);
    });

    return;
  }
});

server.listen(9000, () => {
  console.log("Server is running on port 9000");
});

获取url查询参数

使用nodejs中内置的url模块来解析url参数! 已经弃用,建议使用URL对象!

/* 
  查询参数提取
*/
const http = require("http");
const url = require("url");

const server = http.createServer((req, res) => {
  /* 
    @param {string} req.url 请求的url
    @param {boolean} true 解析查询参数s
    @return {object} url.parse(req.url, true).query 查询参数对象
  */
  res.setHeader("Content-Type", "text/plain;charset=utf-8");
  const urlObj = url.parse(req.url, true);
  res.end(`查询参数提取
   查询参数对象 ${JSON.stringify(urlObj.query)}
   查询路径 ${urlObj.pathname}`);
});

server.listen(9000, () => {
  console.log("http://localhost:9000");
});

这里用到了url 模块url模块主要用来处理路径,可以获取路径名以及查询参数等!

使用URL对象,获取查询字符串!

/* 
  查询参数提取
*/
const http = require("http");

const server = http.createServer((req, res) => {
   res.setHeader("Content-Type", "text/plain;charset=utf-8");
   // 第一个参数: /search?id=92389892183
   // 第二个参数: http://localhost:9000
   const urlObj = new URL(req.url, "http://localhost:9000");
   res.end(`查询参数提取
   查询参数对象 ${JSON.stringify(urlObj.searchParams.get("id"))}
   查询路径 ${urlObj.pathname}`);
});

server.listen(9000, () => {
  console.log("http://localhost:9000");
});

设置响应报文

设置响应状态码

设置响应状态码,通过statusCode属性来设置!

response.statusCode = 404; // 200 201 500

设置响应头

设置响应类型,具体响应类型可查看MIME相关内容!https://developer.mozilla.org/zh-CN/docs/Web/HTTP/MIME_types/Common_types

response.setHeader("content-type", "text/plain;charset=utf-8");

设置自定义响应头!

response.setHeader("myHeader", "test test");

设置多个同样的响应头

response.setHeader("myHeader", ["a","b","c"])

设置响应体

设置响应体,有两种方法,writeend方法!

writeend同时出现时,会将结果进行拼接返回响应内容!

write 可以调用多次,且end只能调用一次!

response.write("响应内容!")
response.end("响应内容!")

静态资源和动态资源

  1. 静态资源: 一般文件内容不会变动的资源,称为静态资源!

  2. 动态资源: 文件内容根据场景需求经常发生内容变动的情况,可称为动态资源!

静态服务器搭建

静态资源服务,根据路径返回不同路径下的静态文件内容!

目录page文件夹内的静态文件!

page > css > app.css

├── css
│   └── app.css
├── image
│   └── img.jpg
├── index.html
└── js
    └── app.js
const http = require("http");
const fs = require("fs");
const path = require("path");

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, `/page/${req.url}`);
  // console.log(req.url, filePath);
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404, { "Content-Type": "text/plain;utf-8" });
      res.end("Not Found");
      return;
    }
    res.end(data);
  });
});
server.listen(9000, () => {
  console.log("Server is running at http://localhost:9000");
});

启动服务后,访问 http://localhost:9000/image/img.jpg ,则node服务会根据/image/img.jpg 路径去查找返回文件内容!

网页URL之相对路径

代码中书写的相对路径地址映射浏览器URL路径表现!

http://localhost:9000/currentPath/index.html

相对路径 URL路径
./css/app.css http://localhost:9000/currentPath/css/app.css
js/app.js http://localhost:9000/currentPath/js/app.js
../img/image.jpg http://localhost:9000/img/image.jpg
../../img/image.jpg http://localhost:9000/img/image.jpg

网页中URL场景使用

  • a 标签的href属性

  • link 标签的href属性

  • script 标签的src属性

  • img 标签的 src 属性

  • video audio 标签的src属性

MIME类型

MIME类型 是一种响应各种类型标准和规范,例如有js文件、css文件、html文件、媒体视频和图片相关文件等!

text/javascript 、text/css 、text/html、image/png

MIME格式: [type]:[subtype]

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/MIME_types/Common_types

常见的MIME类型如下:

{
  html: "text/html",
  css: "text/css",
  js: "text/javascript",
  png: "image/png",
  jpg: "image/jpeg",
  gif: "image/gif",
  mp4: "video/mp4",
  mp3: "audio/mpeg",
  json: "application/json"
}

注意: 对于未知类型的资源,可以选择application/octet-stream类型,当浏览器遇到此类型,会将内容进行下载!

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

const MIME = {
  html: "text/html",
  css: "text/css",
  js: "text/javascript",
  png: "image/png",
  jpeg: "image/jpeg",
  gif: "image/gif",
  mp4: "video/mp4",
  mp3: "audio/mpeg",
  json: "application/json",
};

const server = http.createServer((req, res) => {
  const rootPath = "/page";
  const filePath = path.join(
    __dirname,
    rootPath,
    req.url === "/" ? "index.html" : req.url
  );
  // ".html".slice(1); "html"
  const extname = path.extname(filePath).slice(1);
  fs.readFile(filePath, (err, data) => {
    if (err) {
      console.log(err);
      res.writeHead(404, { "Content-Type": "text/plain;utf-8" });
      res.end("文件读取失败!");
      return;
    }
    /* 
      如果是未知的资源,使用application/octet-stream作为类型返回,浏览器会根据该类型,对内容进行本地下载
    */
    let mimeType =
      (MIME[extname] && MIME[extname]) || "application/octet-stream";
    res.writeHead(200, { "Content-Type": `${mimeType};utf-8` });
    res.end(data);
  });
});

server.listen(9000, () => {
  console.log("Server is running at http://localhost:9000");
});

案例

登录和注册案例

根据路径返回不同响应login返回登录页register返回注册页,否则404!

/* 
  get 请求示例
  /login 时响应 登录页面
  /register 时响应 注册页面
*/
const http = require("http");

const server = http.createServer((request, response) => {
  const { method } = request;
  const { pathname } = new URL(request.url, `http://${request.headers.host}`);
  // 设置响应头 相应类型为  html
  response.setHeader("Content-Type", "text/html;charset=utf-8");
  if (method === "GET" && pathname === "/login") {
    response.end("<h1>登录页面</h1>");
  } else if (method === "GET" && pathname === "/register") {
    response.end("<h1>注册页面</h1>");
  } else {
    response.end("<h1>404 Not Found</h1>");
  }
});

server.listen("9000", () => {
  console.log("Server running at http://localhost:9000");
});
响应4行3列的表格
const http = require("http");
const fs = require("fs");
const path = require("path");

function getHtmlContent(){
  const templatePath = path.resolve(__dirname, "./template.html");
  const html = fs.readFileSync( templatePath, "utf-8" );
  return html;
}

const server = http.createServer((request, response) => {
  // 设置响应类型 为html文本
  response.setHeader("Content-type", "text/html;utf-8");
  const html = getHtmlContent();
  response.end(html);
});

server.listen("9000", ()=> {
  console.log("server at http://localhost:9000 ")
})
响应html内部引入外部资源路径

html文件解析过程,内部遇到外部资源(.js .css img等)时,会向服务发送请求,获取对应资源!


/* 
  响应html文件内部并引入外部资源路径
*/

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://${req.headers.host}`);
  // html 文件路径
  const filePath = path.join(__dirname, "./template.html");
  // 读取 html 文件内容
  const html = fs.readFileSync(filePath, "utf8");

  if (pathname.endsWith(".css")) {
    // 引入外部 css 文件
    const cssfilePath = path.join(__dirname, "./index.css");
    const css = fs.readFileSync(cssfilePath, "utf8");
    res.end(css);
  } else if (pathname === "/") {
    res.end(html);
  } else {
    res.statusCode = 404;
    res.end("Not Found");
  }

  // res.end(html);
  // 这里不能直接返回html,当html文件内部引入外部资源(.js .css img等)html解析时,会想服务发送请求获取资源,这里如果直接响应html时,则结果是不对的
});

server.listen(9000, () => {
  console.log("Server is running on port 9000");
});

模块化

模块化是一种规范,通常将一个比较臃肿的文件模块,根据业务类型拆分成多个子模块!

模块之间代码都是私有独立的,可以通过特定的语法,将模块里数据进行暴露和导出,以便提供给其它所依赖的模块使用!

模块化的好处:

  1. 大型文件模块拆分,根据业务类型拆分多个子模块可读可维护好!
  2. 每个模块之间的代码都是独立的,避免变量命名冲突污染的问题!
  3. 每个模块之间可以将重复的代码块抽离出一个子模块,供其它模块使用,可复用代码逻辑减少代码量的开发!

模块暴露

通过module.exports 可以实现模块中单个功能变量的暴漏,也可以对模块中多个变量进行统一暴漏!

  1. 单个功能模块暴漏:

    • utils.js
    function add(a, b) {
      return a + b;
    }
    module.exports = add;
    
    • 这里将utils.js中的add方法暴漏出去!
    • index.js
    • 使用require方法来对模块的引用!
    const add = require("./utils.js");
    
    let result = add(12,20);
    console.log("result", reuslt);
    
  2. 多个功能模块进行统一暴漏;

    • utils.js
    function add(a, b) {
      return a + b;
    }
    
    function minus(a, b) {
      return a - b;
    }
    
    module.exports = {
      add,
      minus
    };
    
    • index.js
    // 这里获取的是个对象,只不过这里用到了对象结构
    const { add, minus } = require("./utils.js");
    
    let addResult = add(15, 20);
    let minusResult = minus(20, 15);
    console.log(addResult, minusResult); // 35
    

exports注意点

使用module.exports某个值进行暴漏时需要注意的问题,就是不能直接使用exports = "value" 作为一个值的暴漏!

exports = "value";

exports 是一个对象,它的值等同于module.exports,而module.exports值默认是一个空的对象,且它们的关系就是共同指向引用地址对象!

exports === module.exports // true

因此直接通过exports = "value"时 ,通过require 获取到的便是一个"{}"空对象 ,原因,导出的值,是默认将module.exports中的值向外进行暴露的,这里我们只是改的exports,所以,module.exports还是空对象!

总而言之,就是两个变量共同引用了同一个对象的引用地址,其中向外暴漏的值,是module.exports中的值!

exports.value = "absd";

这样是可以的,因为这里是向空对象添加了一个属性为value的值!

既然exportsmodule.exports 对象引用地址是共用的,那么同样module.exports中的对象也会存储一个叫value属性的值!

模块导入

导入模块是通过require 函数来实现的,可接受一个文件路径名,是一个相对路径!

const module = require("./test.js")

这里导入了test.js 模块!

导入模块的注意事项

  1. 引入路径中的./../不能省略,否则会找不到模块!

  2. .js.json后缀文件可以省略!

  3. 导入其它文件格式的文件,会以js形式处理文件内容!

  4. 如果.js文件和.json文件名一样,则优先导入js模块!

  5. 如果require中引入的是一个文件夹,而不是文件时!

    const module = require("./module")
    
    • module文件夹内部查找package.json中的main属性是否有提供文件模块路径,如果没有,则会找index.js或者是index.json,否则就会报错!
    • 这里文件查找规则,在包管理工具时会用到,导入第三方模块,都是以文件夹方式去导入的!

require 除了能够引入,自定义模块文件,也可以用在引入第三方包管理的相关模块!

自定义模块导入流程

自定模块导入流程,通常会调用require("./xxx.js")函数传递一个相对路径的模块文件,最后返回一个module.exports对象!

具体流程如下

  1. 相对路径的文件路径,转换为绝对路径!
  2. 判断缓存中是否有当前模块所对应的缓存值,如果有直接返回缓存中的值!
  3. 如果缓存中没有,就会读取文件模块中的代码,并包裹成函数执行(自执行函数)!
  4. 缓存返回值!
  5. 返回module.exports的值!
const path = require("path");
const fs = require("fs");
function myRequire( filePath ) {
  // 1. 解析文件路径转换为绝对路径
  const absoltePath = path.resolve(__dirname,filePath);
  // 2. 判断缓存中的文件是否存在
  if (caches[absoltePath]){
    return caches[absoltePath];
  }
  // 3. 读取文件内容
  const content = fs.readFileSync(absoltePath,"utf-8");
  // 4. 解析文件内容,生成模块对象
  const module = {
    exports: {}
  };
  // 5. 执行模块代码,将模块的 exports 属性指向 module.exports
  const fn = new Function("exports",content);
  fn(module.exports);
  // 6. 缓存模块对象
  caches[absoltePath] = module.exports;
}

const module = myRequire("./module.js");

包管理工具

包管理工具(npm),全称(node package manager),是一个第三方资源仓库,可以根据需求,通过npm安装这些第三方资源包!

初始化项目

npm init

init 命令可以帮我们以命令交互方式来创建一个node项目,过程会提示,项目的名称、描述、以及入口文件等相关配置,最终都会以package.json的形式来表示!

package.json包管理配置文件,不仅有项目的基本配置,还有安装的第三方依赖包等相关配置!

➜  npm-init npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (npm-init)
version: (1.0.0)
description: npm 学习
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/miaojiangwei/Study/FrontEnd/Node/code/包管理工具/npm-init/package.json:

{
  "name": "npm-init",
  "version": "1.0.0",
  "description": "npm 学习",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)
{
  "name": "npm-init", // 包的名称
  "version": "1.0.0", // 包的版本
  "description": "npm 学习", // 项目描述
  "main": "index.js", // 入口文件
  "scripts": { // 脚本配置
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "", // 作者
  "license": "ISC" // ISC开源证书
} 

注意事项

  1. package name: 不能是大写,也不能是中文名,不写默认为当前目录下的文件夹名!

  2. version: 版本号, 书写形式为x.x.x 必须为数字1.0.0 !

通过npm init -y 可以快速创建一个node项目减少交互提示行为!

搜索包

通过命令的方式搜索包信息:

npm s calendar

通过 s(search)指令,根据关键字,查询对应的第三方包信息!

➜  ~ npm s calendar
NAME                      | DESCRIPTION          | AUTHOR          | DATE       | VERSION  | KEYWORDS
calendar                  | calendar generator   | =ramalho        | 2019-11-04 | 0.1.1    |
rc-calendar               | React Calendar       | =yiminghe…      | 2020-06-06 | 9.15.11  | react react-calendar r
rmc-calendar              | React Mobile…        | =silentcloud…   | 2018-06-27 | 1.1.4    |
js-calendar               | JavaScript calendar… | =sergiocrisost… | 2017-12-27 | 1.2.3    |
v-calendar                | A clean and…         | =nathanreyes    | 2023-10-13 | 2.4.2    | vue vuejs plugin calen
react-calendar            | Ultimate calendar…   | =wojtekmaj…     | 2024-10-23 | 5.1.0    | calendar date date-pic
react-big-calendar        | Calendar! with…      | =monastic.panic… | 2024-12-19 | 1.17.1   | scheduler react-compo
vue-calendar-component    | 基于vue2.0的一款日…  | =zwhgithub      | 2019-02-18 | 2.8.2    | vue-calendar calendar vue-da
@react-types/calendar     | Spectrum UI…         | =devongovett…   | 2024-11-21 | 3.5.0    |
@nextui-org/calendar      | A calendar displays… | =juniorgarciad… | 2024-12-24 | 2.2.8    | calendar
@react-stately/calendar   | Spectrum UI…         | =devongovett…   | 2024-11-21 | 3.6.0    |
d3-time                   | A calculator for…    | =mbostock…      | 2022-12-02 | 3.1.0    | d3 d3-module time inte
@react-aria/calendar      | Spectrum UI…         | =devongovett…   | 2024-11-21 | 3.6.0    |
react-native-calendars    | React Native…        | =wix.mobile…    | 2024-09-18 | 1.1307.0 |
boom-calendar             | Powerful and…        | =azaryan        | 2024-10-30 | 1.6.20   |
tui-calendar              | TOAST UI Calendar    | =nhnent         | 2022-02-17 | 1.15.3   | nhn nhnent toast tui c
vue-full-calendar         | FullCalendar…        | =brockreece     | 2020-06-21 | 2.8.1-0  | vue fullcalendar calen
@fullcalendar/core        | FullCalendar core…   | =arshaw         | 2024-07-12 | 6.1.15   | calendar event full-si
@types/react-big-calendar | TypeScript…          | =types          | 2024-11-22 | 1.16.0   |
angular-calendar          | A calendar…          | =mattlewis92    | 2024-04-19 | 0.31.1   | angular angular2 calen

在网页中使用官方地址进行搜索

https://npmmirror.com/?spm=a2c6h.24755359.0.0.1b1f5dc84SNGx0https://www.npmjs.com/search?q=calendar

下载和安装包

npm install uniq

通过 install 命令,后面跟一个包的名称,即可下载安装!

npm i uniq

也可以使用简写方式!

安装成功后,会多出一个文件夹node_modules文件夹内部便是安装后存放的依赖文件包!

同时也会新增一个文件package-lock.json ,主要用来锁定包版本信息的!

并且在package.json内部,会增加安装依赖包的配置项!

{
   "dependencies": {
    "uniq": "^1.0.1"
   }
}

在代码中使用uniq依赖包插件!

index.js

const uniq = require("uniq");

let arr = [1, 2, 3, 2, 4, 5, 3, 6];

let result = uniq(arr);

console.log(result); // [1, 2, 3, 4, 5, 6]

require引入依赖包流程

const uniq = require("uniq");
// const uniq = require("./node_modules/uniq/uniq.js");

基本流程是,找到当前目录下的node_modules下的uniq文件夹,找到后,看package.jsonmain属性对应的入口文件如果没有则向上级文件夹,看上层的node_modules 有没有,直到找到磁盘根目录!

开发依赖和生产依赖

开发依赖和生产依赖,是指的是在开发环境下所使用的依赖包,而生产依赖,指的是构建发布后线上环境所使用的依赖包!

  1. 开发环境依赖包安装:

    npm install uniq --save-dev
    npm i uniq -D # 简写形式
    
    • 安装完后在package.json会增加devDependencies属性对象,其内部包含了开发环境相关的依赖!
  2. 生产环境依赖包安装:

    npm install uniq --save
    npm i uniq -S # 简写形式
    
    • 安装完后package.json会增加dependencies属性对象,其内部包含了生产线上环境的依赖!

全局安装

npm i -g nodemon

使用-g 命令后面跟一个依赖包名可进行全局安装,一般全局安装的工具都是可通过命令来进行一系列操作!

nodemon 是一个插件工具,在使用http服务时,每次改动都需要手动重新启动服务,使用nodemon 工具可以避免这个问题 ,实现文件内容改变时,可以自动重启服务!

npm root -g

以上命令,可以查看全局安装的目录位置!

指定版本安装

npm i jquery@1.11.2

删除依赖

npm r jquery
# npm remove jquery
# npm uninstall jquery

配置脚本简化命令操作

package.json中,有一个属性名为scripts, 该属性,可以配置一些别名,将比较复杂的命令,通过script 别名配置后,来进行简化操作!

{
  "name": "npm-init",
  "version": "1.0.0",
  "description": "npm 学习",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "serve": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "uniq": "^1.0.1"
  }
}

命令行中使用时,不需要node index.js启动服务,只需要运行 npm run serve 或者是 npm run start即可!

npm run 自动向上查找的特性,当前文件夹所执行的文件不存在时,会向上查找匹配文件的特性!

npm run start 其中 run 可以省略 npm start,一般用于启动项目!

yarn包管理工具

yarn 是一个包管理工具,其特点下载速度快,对包的完整性检测比较好!

全局安装yarn

npm install -g yarn

yarn常用命令

命令 描述
初始化 yarn init / yarn init -y
安装包 yarn add uniq 生产环境 / yarn add uniq --dev 开发环境
全局安装 yarn global add nodemon
删除依赖 yarn remove uniq / yarn global remove nodemon 全局依赖删除

发布包

发布包,包的名称不能带有test等字眼,npm会有垃圾检测,否则无法发布!