【前端35_Node】基础:起步安装、Node的模块化、Node 常用内置模块:fs、buffer、Stream

本文主要是介绍【前端35_Node】基础:起步安装、Node的模块化、Node 常用内置模块:fs、buffer、Stream,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Node.js
    • 起步
      • 什么是 Node?
      • 安装
      • 常见概念解释
      • 上手:用 Node 启动简单的服务
    • 模块化
      • 模块中的变量导出
      • 模块中的变量引入
      • 整合引入
      • 历史上的模块化的集中形式(了解)
        • amd `sea.js`
        • cmd `require.js`
    • Node 常用内置模块
      • fs 文件操作 *
        • 文件操作
          • 写入文件
          • 读取文件
          • 修改文件名
          • 删除文件
          • 复制文件
        • 目录操作
          • 创建目录
          • 修改目录名
          • 读取目录
          • 删除空文件夹
          • 删除非空文件夹
            • 我写的版本
        • 通用方法
          • 判断文件或者文件夹是否存在
          • 获取文件或者目录的详细信息 / 判断文件、目录
        • 速查表格
      • buffer 模块
        • 概念
        • 创建 buffer
      • stream 流
        • 为啥要用流?
        • chunk 能有多大(验证)?
        • 如何判断流完成?
        • 管道 pipe

Node.js

起步

什么是 Node?

灵魂一问:什么是 Node
官网上说:Node 是 基于 V8 引擎的 JavaScript 运行时。

说白了,就是用来跑 JavaScript 语言的一个环境,当然,前期可以这样去理解。

个人理解:有兴趣的话可以去深入学学 AST 抽象语法树 还有 V8 引擎的一些基本原理,引擎是如何解析 JavaScript 这门语言的?不管是什么语言,都是一堆字符串组成的,通过不同的引擎去解释关键字,变量之类的,从而运行相应的功能。

这些功能不知道也不要紧,不影响我们继续学习。先学吃饭在学养家!基础还是要打好的,没错说的就是我。


安装

Node.js官网

windows 注意:配置环境变量,网上查一下,很多的,这里就不在赘述了。可以参考知乎:Node环境变量

查看是否安装好 node:终端运行 node -v,如果出现版本号,说明安装成功了。否则的话,重新在安装一遍吧
在这里插入图片描述


常见概念解释

Q:普通 jsnode.js 有啥区别?
看这个 js 在哪里执行了,如果在 node 里执行了,那么你可以叫这个 jsnodejs

Q:什么是客户端,什么是服务端?
启动服务的叫服务端,访问的那一端叫客户端


上手:用 Node 启动简单的服务

首先建一个 .js 文件,不要以 node 为文件名。然后把下面的文字复制到文件中

// http.js// node 官网上写的一个例子
const http = require('http');const requestListener = function (req, res) {res.writeHead(200);res.end('Hello, World!');
}const server = http.createServer(requestListener);
server.listen(8080);

写好后保存,再当前文件夹下打开终端,输入node 文件名.js,即可开启一个服务。

服务端改了东西,想要生效的话,需要重启服务
结束当前服务:在终端中,按住,Ctrl + C 。结束任务后,再 node 文件名开启服务就可以了。

nodemon
如果嫌麻烦,可以用nodemon 避免重启服务,nodemon 有热更新的功能,就是修改文件后可以帮你重新起服务
安装nodemon的命令命令:终端里输入:npm install nodemon - g-g代表安装到全局,后面会详细记录 npm 的常用命令
启动服务:nodemon 文件名.js (跟node启动服务类似的)


模块化

问题:随着项目的不断扩大,变量名也会越来越多,那么总会有可能有些变量命名重复了,那么我们有什么好的办法去解决这个问题?
大佬们就设计了 commonJS 规范,概括的思想就是,一个文件就是一个作用域。
node 遵循 commonJS 规范

模块中的变量导出

模块化的优点
防止作用域的污染、提高代码复用性、降低维护成本

// 第一种方法
module.exports = {a
}// 第二种方法
exports.a = a;
// exports 是 module.exports 的一个引用
// module.exports = exports;// 错误的方法
exports = {a
}
// 直接改 export 对象是没有效果的,虽然不会报错
// 原因是上面说过的:exports 是 module.exports 的一个引用

想了解更多 exports 和 module.exports 的区别可以看这里。
在这里插入图片描述

模块中的变量引入

如果模块是在node_modules 下的文件夹,那么可以直接引入

require("mytest")

如果不在node_modules 下的文件夹,那么,文件引入时写路径的 ./ 是不能省略的,但是文件后缀名可以省略。比方 require("./Mb");

在这里插入图片描述

整合引入

我们可以写一个入口文件,专门存放一些引入的,比方说下图中的 index.js
在这里插入图片描述

历史上的模块化的集中形式(了解)

amd sea.js

略…

cmd require.js
<!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><script src="https://cdn.bootcss.com/require.js/2.3.6/require.js"></script>
</head>
<body></body>
<script>// 引入 a.js 并获取 a 模块中导出的值 objrequire(['a'],(obj)=>{console.log("我是 a.js 中的 obj :>>", obj);console.log(a);});</script>
</html>// 打印如下
a.js:2 我是a.js
b.js:2 我是b.js
a.js:4 我是 b.js 中的变量:>> {str: "我是b.js的变量"}
index.html:16 我是 a.js 中的 obj :>> {name: "蔡徐坤"}
index.html:17 1
// a.js
let a = 1; // 放在define 外边的话,这个变量就算是全局变量了
console.log("我是a.js");
define(["b.js"], function (obj) {console.log("我是 b.js 中的变量:>>", obj);return {name: "蔡徐坤",};
});
define( function() {console.log('我是b.js');let str = '我是b.js的变量'return {str}
});

以上就是些有用的基本知识啦,接下来是一些常用内置模块的介绍


Node 常用内置模块

大体有如下模块,乌泱泱一堆啊。

Buffer,C/C++Addons,Child Processes,Cluster,Console,Crypto,Debugger,DNS,Domain,Errors,Events,File System,Globals,HTTP,HTTPS,Modules,Net,OS,Path,Process,Punycode,Query Strings,Readline,REPL,Stream,String De coder,Timers,TLS/SSL,TTY,UDP/Datagram,URL, Utilities,V8,VM,ZLIB;

内置模块不需要安装,外置模块需要安装;

// 内置模块直接引入即可
require("http");

fs 文件操作 *

文件操作分为两个大类

  1. 文件操作
  2. 目录操作

顾名思义,操作的目标不同而已

所有的操作都分同步和异步的,区别就是是否加 Sync

文件操作
写入文件
/*第三个参数,添加配置flag 的值有a 追加写入w 写入:会把文件重写覆盖掉r 读取
*/
fs.writeFile("1.txt", "我是写入的文字", { flag: "a" }, function (err) {if (err) {console.log(err);}console.log("写入成功");
});
读取文件
/*** 文件读取* 参数:文件路径,读取格式,回调函数*/
fs.readFile('1.txt',"utf8",(err,data)=>{if(err){console.log(err);}console.log(data);
})
// buffer 可以通过 toString() 来转换成文字,如果 buffer 本身是文字的话

如果这些操作没有添加Sync,都是异步的。

如何修改成同步的呢?只需添加上Sync即可,同步的话就没有回调函数了,下面就是例子

let rst = fs.readFileSync('1.txt');
console.log(rst.toString());
修改文件名
fs.rename("1.txt", "2.txt", (err) => {if (err) {return console.log(err);}// 如果没有错误,那么err等于nullconsole.log("修改成功");
});
删除文件
fs.unlink("2.txt", (err) => {if (err) {return console.log(err);}console.log("删除成功");
});
复制文件

复制文件有两个办法

  1. 先读文件,再写文件
  2. 使用 copyFile
// 1、根据思路可以自己封装一个方法
const myCopy = (src,dest)=>{fs.writeFileSync(dest,fs.readFileSync(src));
}
myCopy("2.txt","3.txt")// 2、直接使用现成的
fs.copyFile('2.txt','2_copy.txt',err=>{if(err){return console.log(err);}console.log('复制成功');
})
目录操作
创建目录
/*** 创建目录*/
fs.mkdir('1',err=>{if(err){return console.log(err);}console.log('创建成功');
})
修改目录名
/*** 修改目录名*/
fs.rename("1", "2", (err) => {if (err) {return console.log(err);}console.log("修改文件夹名称成功");
});
读取目录

默认只读一层目录,什么意思呢,就是你读取的目录下也可能有文件夹是吧?那么这个文件夹中的内容就不会读了。

/*** 读取目录:能够读取到目录和文件,其中目录没有后缀,文件有后缀*/
fs.readdir('2',(err,data)=>{if(err){return console.log('err');}console.log('读取目录:>>',data);   //读取目录:>> [ '1.html', 'index.js' ]
})
删除空文件夹
/*** 删除目录:前提,一定是空目录*/fs.rmdir('2',err=>{if(err){return console.log(err);}console.log('删除成功:>>',data);})
删除非空文件夹

/*** 删除非空文件夹* 思路:先把目录中的文件删除,之后删除空目录* 注意:node删除的文件不会放到回收站,所以 注意备份*/
function removeDir(path) {let data = fs.readdirSync(path);for (let i = 0; i < data.length; i++) {// 判断是文件或者是目录// 文件:直接删除// 目录:继续查找let url = path + "/" + data[i];let stat = fs.statSync(url);if(stat.isDirectory()){// 继续查找,递归removeDir(url)}else{// 文件删除fs.unlinkSync(url);}}// 删除空目录fs.rmdirSync(path);
}
removeDir('2 copy');
我写的版本

刚开始我没看他写的,自己想了想,我自己写的一个版本,差了一点。

第一版:比较可惜,就差一点点没写出来,就是最后删文件夹那一步

我还在想如何去删空的文件夹,其实当我们把当前文件夹下的文件都删除掉了,就可以直接删除空文件夹了。

function df(dirName) {let dirArr = fs.readdirSync(dirName);dirArr.forEach((item) => {console.log(item);const path = `${dirName}/${item}`;fs.stat(path, (err, stat) => {console.log(path);if (err) {return err;}if(stat.isDirectory() === true) {df(path)}if (stat.isFile() === true) {fs.unlinkSync(path)}});});
}

第二版:fs.stat 不能写成异步的

// 报错信息:Error: ENOTEMPTY: directory not empty, rmdir 'newDir'
// 思路:报错信息说不是空文件夹,就要思考为什么不是空文件夹!!!原来是异步导致的,所以只需要改成同步的即可
function df(dirName) {let dirArr = fs.readdirSync(dirName);dirArr.forEach((item) => {const path = `${dirName}/${item}`;fs.stat(path, (err, stat) => {if (stat.isDirectory() === true) {df(path);} else {fs.unlinkSync(path);}});});fs.rmdirSync(dirName);
}

最终版

function df(dirName) {let dirArr = fs.readdirSync(dirName);dirArr.forEach((item) => {const path = `${dirName}/${item}`;let stat = fs.statSync(path);if (stat.isDirectory() === true) {df(path);} else {fs.unlinkSync(path);}});fs.rmdirSync(dirName);
}
通用方法
判断文件或者文件夹是否存在
fs.exists('a.txt',(isexists)=>{console.log(isexists);
})
获取文件或者目录的详细信息 / 判断文件、目录
/*** 获取文件或者目录的详细信息,判断是否是文件(夹)*/
fs.stat('index.html',(err,stat)=>{if(err){return console.log(err);}// 判断是否是一个文件let isFile = stat.isFile();console.log(isFile);// 判断是否是一个文件夹let isDirectory = stat.isDirectory()console.log(isDirectory);stat.isFile();
})
速查表格
功能函数
文件操作
写入文件fs.writeFile
读取文件fs.readFile
修改文件名fs.rename
删除文件fs.unlink
复制文件fs.copyFile
目录操作
创建目录fs.mkdir
修改目录名fs.rename
读取目录fs.readdir(path)
删除文件夹fs.rmdir
通用方法
判断文件或者文件夹是否存在fs.exists
获取文件或者目录的详细信息fs.stat
判断文件stat.isFile()
判断是否是目录stat.isDirectory()

buffer 模块

概念

在文件读取的时候,如果不声明 utf8 的时候,我们打印出来的是 buffer ,那么什么是 buffer 呢?

可以理解成为一个数据格式

为啥要用buffer
因为二进制文件,底层传输的快。

创建 buffer

指定创建 10 字节的 buffer

// 字节大小,默认填 0
// 呈现:是用两位 16 进制呈现的
let buffer = Buffer.alloc(10);		// 创建10字节的buffer流
console.log(buffer); //<Buffer 00 00 00 00 00 00 00 00 00 00>

创建内容为文字的 buffer

let buffer1 = Buffer.from('大家好')
console.log(buffer1);   // <Buffer e5 a4 a7 e5 ae b6 e5 a5 bd>

可以观察并推测到一个文字对应三个 buffer 字节

我们还可以通过数组的形式来创建

let buffer2 = Buffer.from([0xe5,0xa4,0xa7,0xe5,0xae,0xb6,0xe5,0xa5,0xbd]);
console.log(buffer2);   // <Buffer e5 a4 a7 e5 ae b6 e5 a5 bd>
console.log(buffer2.toString());    // 大家好

转换成字符串 toString

拼接二进制流可以用concat方法

let buffer3 = Buffer.from([0xe5,0xa4,0xa7,0xe5]);
let buffer4 = Buffer.from([0xae,0xb6,0xe5,0xa5,0xbd]);
console.log(buffer3.toString());    // 大�let newbuffer = Buffer.concat([buffer3,buffer4]);
console.log(newbuffer.toString());  // 大家好

从以上的例子我们可以推断出,一个汉字占用3个字节,可以看到大后面的乱码,就是因为没有字节去描述这个汉字了,所以就乱码了…

或者可以通过node给的方法 StringDecoder,此方法会把第一个多余的buffer存起来,然后跟第二个拼接起来,所以不会出现乱码。

let {StringDecoder } = require('string_decoder');
let decoder = new StringDecoder();
let buffer3 = Buffer.from([0xe5,0xa4,0xa7,0xe5]);
let buffer4 = Buffer.from([0xae,0xb6,0xe5,0xa5,0xbd]);
let res3 = decoder.write(buffer3);
let res4 = decoder.write(buffer4);
console.log(res3);  // 大
console.log(res4);  // 家好

stream 流

为啥要用流?

为啥要用流呢?
我来描述一个场景,假如你的电脑内存是1g,而你要传输一个2g的文件,那么直接读取2g并传输,内存就爆仓了
所以有了一个解决方案:把2g的文件切成很小的小块,然后在传输
这样也会有其他的好处,比方说你的带宽不够时,一下子传递很大的文件会很吃力,而分成小块儿去传递的话,轻松了不少,侧面上来讲,提高了性能

也就是把大象装冰箱里,总共分几步的问题~

首先创建二进制流并写入文件

// 创建 65kb 的 buffer
let buffer = Buffer.alloc(65 * 1024);// 写入(保存成文件)
fs.writeFile("65kb", buffer, (err) => {if (err) {return console.log(err);}console.log("写入成功!");
});

在这里插入图片描述

然后创建流读取 fs.createReadStream()

const fs = require("fs");
let num = 0;
let rs = fs.createReadStream("65kb"); // 每次分成 64kb 的小块
rs.on("data", (chunk) => {++num;console.log(chunk,num);
});

输出结果如下:
在这里插入图片描述

chunk 能有多大(验证)?

那么流会每次会把文件分成多大份呢?我们来做一个实验

我们在上面的操作图片中能够看到,65kb大小的文件,是分成两个流的
那么我们创建一个64kb 的文件,然后记录一下分流的次数

在这里插入图片描述

所以我们推断,分流每次切成64字节的chunk

如何判断流完成?

可以通过 on 中的end 来判断

const fs = require("fs");
let num = 0;
let rs = fs.createReadStream("65kb"); // 每次分成 64kb 的小块
rs.on("data", (chunk) => {++num;console.log(chunk,num);
});rs.on('end',()=>{console.log("流完成");
})

在这里插入图片描述

管道 pipe

pipe 译为管道,我们呢创建好读取流之后,通过管道,我们把chunk 写入某文件中并拼接,跟赋值的操作差不多啊,这样的操作如下~

const fs = require('fs')
let rs = fs.createReadStream("65kb");   // 创建读取流
let ws = fs.createWriteStream('ws.txt');  // 创建写入流
rs.pipe(ws);    // 管道,rs 读取完之后,通过管道,写进 ws 中

在这里插入图片描述

这篇关于【前端35_Node】基础:起步安装、Node的模块化、Node 常用内置模块:fs、buffer、Stream的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/976075

相关文章

一文详解如何查看本地MySQL的安装路径

《一文详解如何查看本地MySQL的安装路径》本地安装MySQL对于初学者或者开发人员来说是一项基础技能,但在安装过程中可能会遇到各种问题,:本文主要介绍如何查看本地MySQL安装路径的相关资料,需... 目录1. 如何查看本地mysql的安装路径1.1. 方法1:通过查询本地服务1.2. 方法2:通过MyS

电脑软件不能安装到C盘? 真相颠覆你的认知!

《电脑软件不能安装到C盘?真相颠覆你的认知!》很多人习惯把软件装到D盘、E盘,刻意绕开C盘,这种习惯从哪来?让我们用数据和案例,拆解背后的3大原因... 我身边不少朋友,在使用电脑安装软件的时候,总是习惯性的把软件安装到D盘或者E盘等位置,刻意避开C盘。如果你也有这样的习惯,或者不明白为什么要这么做,那么我

Python logging模块使用示例详解

《Pythonlogging模块使用示例详解》Python的logging模块是一个灵活且强大的日志记录工具,广泛应用于应用程序的调试、运行监控和问题排查,下面给大家介绍Pythonlogging模... 目录一、为什么使用 logging 模块?二、核心组件三、日志级别四、基本使用步骤五、快速配置(bas

CSS3 布局样式及其应用举例

《CSS3布局样式及其应用举例》CSS3的布局特性为前端开发者提供了无限可能,无论是Flexbox的一维布局还是Grid的二维布局,它们都能够帮助开发者以更清晰、简洁的方式实现复杂的网页布局,本文给... 目录深入探讨 css3 布局样式及其应用引言一、CSS布局的历史与发展1.1 早期布局的局限性1.2

使用animation.css库快速实现CSS3旋转动画效果

《使用animation.css库快速实现CSS3旋转动画效果》随着Web技术的不断发展,动画效果已经成为了网页设计中不可或缺的一部分,本文将深入探讨animation.css的工作原理,如何使用以及... 目录1. css3动画技术简介2. animation.css库介绍2.1 animation.cs

CSS引入方式和选择符的讲解和运用小结

《CSS引入方式和选择符的讲解和运用小结》CSS即层叠样式表,是一种用于描述网页文档(如HTML或XML)外观和格式的样式表语言,它主要用于将网页内容的呈现(外观)和结构(内容)分离,从而实现... 目录一、前言二、css 是什么三、CSS 引入方式1、行内样式2、内部样式表3、链入外部样式表四、CSS 选

使用雪花算法产生id导致前端精度缺失问题解决方案

《使用雪花算法产生id导致前端精度缺失问题解决方案》雪花算法由Twitter提出,设计目的是生成唯一的、递增的ID,下面:本文主要介绍使用雪花算法产生id导致前端精度缺失问题的解决方案,文中通过代... 目录一、问题根源二、解决方案1. 全局配置Jackson序列化规则2. 实体类必须使用Long封装类3.

Spring Boot 常用注解整理(最全收藏版)

《SpringBoot常用注解整理(最全收藏版)》本文系统整理了常用的Spring/SpringBoot注解,按照功能分类进行介绍,每个注解都会涵盖其含义、提供来源、应用场景以及代码示例,帮助开发... 目录Spring & Spring Boot 常用注解整理一、Spring Boot 核心注解二、Spr

ubuntu20.0.4系统中安装Anaconda的超详细图文教程

《ubuntu20.0.4系统中安装Anaconda的超详细图文教程》:本文主要介绍了在Ubuntu系统中如何下载和安装Anaconda,提供了两种方法,详细内容请阅读本文,希望能对你有所帮助... 本文介绍了在Ubuntu系统中如何下载和安装Anaconda。提供了两种方法,包括通过网页手动下载和使用wg

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4