概述:Node.js 是一个基于 Chrome v8 引擎的 js 运行环境。
Node API
提供类似部分 webApi 的方法和其他属性:
setTimeout
setInterval
setImmediate
console
__dirname 当前模块的目录名(看似是全局变量,实质是模块变量(module))
__filename 当前模块文件名(同上) ———— 当前的模块文件的绝对路径
Buffer 处理二进制数据的 类
Buffer 类是 JavaScript 语言内置的 Uint8Array
类的子类 → 中文文档
process 进程
process.arch 操作系统的 CPU 架构 (一般我们使用的电脑是 x32 / x64)
process.argv 获取命令中的所有参数
// 如仅在 index.js 输出下面的属性
console.log(process.argv);
/**
[
'D:\\软件\\Node.js_setup\\node.exe',
'e:\\FontEnd_File\\JavaScript\\NodeJS\\pro_demo\\index.js'
]
*/
process.env 返回环境变量对象
process.cwd() 返回 Node.js 进程的当前工作目录(绝对路径)
process.exit([code]) 退出状态 code 指示 Node.js 同步地终止进程(code 默认值为 0)
process.kill(pid[, signal]) 根据 pid 杀死进程 (pid 为进程 ID;signal 将发送的信号,默认值:‘SIGTERM’)
process.platform 获取当前的操作系统
./
或 ../
开头),内部也会转换为绝对路径来加载模块fs
/ path
等js → json → node → mjs
)index.js
(默认);若修改过 package.json 中的 main 字段,则根据此字段指定的文件加载// 某模块 (a.js) 内代码为:
exports.c = 3;
module.exports = {
a: 1,
b: 2
}
this.m = 4;
↓
// 由另一模块 (b.js) 导入结果是什么?
const result = require('./a.js');
console.log(result); // ???
// 结果:
result = {
a: 1,
b: 2
}
上题结果来源需理解导入函数 require
内部实现, 如下所示:
// require函数大致处理流程(非其内部真正代码)
function require(modulePath){
// 1. 将modulePath转换为绝对路径 './src' -> 'D:\code_file\NodeJS\Node_Modules\index.js'
// 2. 判断该模块是否已有缓存(生成的绝对路径会作为缓存对象内部的键,值为模块代码)
/**
if(require.cache['D:\\code_file\\NodeJS\\Node_Modules\\index.js']){
return require.cache['D:\\code_file\\NodeJS\\Node_Modules\\index.js'].result;
}
*/
// 3. 读取文件内容(没有缓存执行此步,否则直接拿缓存结果)
// 4. 将读取的文件内容包裹到一个函数中
/** 大致效果(伪代码)
function __temp(module, exprots, require, __dirname, __filename){
// 读取的文件内容(即拿到的代码)
}
*/
// 5. 创建module对象
/**
module.exports = {};
const exports = module.exports;
__temp.call(module.exports, module, exports, require, module.path, module.filename);
// 这里绑定了 this === module.exports
*/
return module.exports;
}
因而上题的理解过程为:
- 内部导出的 module.exports === exports;
- 新增 c 属性为 3, 即 module.exports = exports = this = { c: 3 };
- 对 module.exports 重新赋值为一个新对象 { a: 1, b: 2 }
- this 与 exports 均指向原来的对象
- require 函数返回的对象是: module.exports 即新对象
- 所以,b.js 导入 a.js 打印的结果就是
{ a: 1, b: 2 }
目前,Node中的 ES 模块化仍然处于实践阶段
模块要么是 CommonJS,要么是 ES(一个文件中不能混用):
CommonJS
;.mjs
package.json
文件中加入 "type": "module"
当使用 ES 模块化运行时,命令行必须加上 --experimental-modules
语句,如:
node --experimental-modules index.mjs
const os = require(‘os’);
const path = require(‘path’);
path.basename(path[, ext]) 法返回 path 的最后一部分,参数 path 中之前的路径内容将会被忽略
path
文件路径
ext
可选文件扩展名
如果 path 不是字符串、或给定了 ext 但不是字符串,则抛出 TypeError
path.delimiter 提供平台特定的路径定界符
path.dirname 返回 path 的目录名
const path = require('path');
console.log(path.dirname('asdas/sfdasf/fasf/a.js'));
// 返回 "asdas/sfdasf/fasf"
path.extname() 返回文件名后缀名
path.join(fullpath) 拼接多个字符串为一个路径
const path = require('path');
const fullpath = path.join('a', 'b', '../', 'c.js');
console.log(fullpath);
// 输出: a\c.js (windows系统下)
path.normalize() 规范化给定的 path,解析 ‘…’ 和 ‘.’ 片段
path.normalize('C:\\temp\\\\foo\\bar\\..\\');
// 返回: 'C:\\temp\\foo\\'
path.relative(from, to) 根据当前工作目录返回 from 到 to 的相对路径
path.resolve([…paths]) 将路径或路径片段的序列解析为绝对路径
给定的路径序列从右到左进行处理,每个后续的 path 前置,直到构造出一个绝对路径
// 当前工作目录是:E:\FontEnd_File\JavaScript\NodeJS\pro_demo
console.log(path.resolve('wwwjooo', 'dashia/mnjj', './index'));
// 返回: E:\FontEnd_File\JavaScript\NodeJS\pro_demo\wwwjooo\dashia\mnjj\index
console.log(path.resolve('/foo/bar', './bax'));
// 返回: e:\foo\bar\bax
path.sep 提供平台特定的路径片段分隔符 (windows 系统返回 \
)
由 url 生成对象
const URL = require('url');
const url = new URL.URL('http://user:[email protected]:8080/p/a/t/h?query=name#hash');
// 或者调用 URL.parse() 方法
// const url = URL。parse('http://user:[email protected]:8080/p/a/t/h?query=name#hash');
console.log(url);
// 输出为一个对象
URL {
href: 'http://user:[email protected]:8080/p/a/t/h?query=name#hash',
origin: 'http://sub.example.com:8080',
protocol: 'http:',
username: 'user',
password: 'pass',
host: 'sub.example.com:8080',
hostname: 'sub.example.com',
port: '8080',
pathname: '/p/a/t/h',
search: '?query=name',
searchParams: URLSearchParams { 'query' => 'name' },
hash: '#hash'
}
// 判断查询值是否有某一属性:
url.searchParams.has('name'); --> 返回 false
url.searchParams.has('query'); --> 返回 true
// 拿到 query 的值
url.searchParams.get('query'); --> 返回 name
由对象生成url字符串 URL.format()
// 如上例生成的 URL 对象
const urlObj = {
href: 'http://user:[email protected]:8080/p/a/t/h?query=name#hash',
origin: 'http://sub.example.com:8080',
protocol: 'http:',
username: 'user',
password: 'pass',
host: 'sub.example.com:8080',
hostname: 'sub.example.com',
port: '8080',
pathname: '/p/a/t/h',
search: '?query=name',
hash: '#hash'
}
const url = URL.format(urlObj);
console.log(url); // http://sub.example.com:8080/p/a/t/h?query=name#hash
util.callbackify(original) original -> async 异步函数
将 async
异步函数转换为回调函数
const util = require('util');
async function delay(duration = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(duration);
}, duration)
});
}
// 常规调用
delay(500).then( res => {
console.log(res);
});
// 转换式
const delayCallback = util.callbackify(delay);
// 调用
delayCallback(500, (err, res) => {
console.log(res);
});
/**
* 转换后的函数,传值参数从第一位开始,最后一位即为回调函数位
* 回调函数的第一个参数为异步函数出现错误时的传值 -> reject 推向 rejected 状态传递过来的值
* 第二个参数即为 resolve 推向 resolved 状态的传值
*/
util.promisify(original)
与上个方法相反,将回调模式函数转换为异步模式函数
function delayCallback(duration, callback){
setTimeout(() => {
callback(null, duration);
}, duration);
}
const delay = util.promisify(delayCallback);
delay(500).then(res => console.log(res));
如磁盘、网卡、显卡、打印机等
I/O 速度往往低于内存和CPU的交互速度
常用api:
fs.readFile
读取一个文件
// 文件路径
// 相对路径是相对于命令提示符的相对路径
// 因而,用绝对路径确保万无一失
const path = require('path');
const fs = require('fs');
const filename = path.resolve(__dirname, './files/1.txt');
// 异步读取
fs.readFile(filename, (err, content) => {
console.log(content); // 得到 buffer 格式的内容
// console.log(content.toString('utf-8')); 转换成 utf-8 格式文本信息
});
// 或者下面这种方式(第二个参数设置 utf-8 编码格式,也可以配置成对象{encoding: 'utf-8'})
fs.readFile(filename, "utf-8", (err, content) => {
console.log(content); // 得到字符串格式的内容
});
// 同步读取
const content = fs.readFileSync(filename, "utf-8");
// Sync 函数是同步的,会导致 JS 运行阻塞
// 一般,在程序启动的时候运行有限的次数即可
fs.promises
// node 12 版本之后添加新api (`fs.promises`) => promise 实现
// 如上例:
async function readFile(filename){
const content = await fs.promises.readFile(filename, "utf-8");
console.log(content);
}
readFile(filename); // 与上面得到的结果相同
fs.writeFile
/ fs.promises.writeFile
写文件
const path = require('path');
const fs = require('fs');
const filename = path.resolve(__dirname, './files/1.txt');
async function writeFile(filename){
// 第二个参数是要写入的内容
// 第三个参数是编码格式( 默认 utf-8 ) -> 字符串或对象配置 encoding: 'utf-8'
await fs.promises.writeFile(filename, "Test Prograph");
console.log("写入成功!");
}
readFile(filename); // 写入成功(但是属于覆盖原文件的内容)
// 覆盖原内容写入
// 若没有匹配到目标文件,会新建文件并写入
// 若目录(文件夹)不存在,则报错
// 若要追加内容,而不是覆盖
// 若要换行输入,使用 os.EOL 常量。用于匹配不同系统
await fs.promises.writeFile(filename, "Test Prograph", {
// encoding: 'utf-8', 这是默认值,可不配置
flag: 'a' // 追加内容
});
// 或者使用 buffer 格式
const buffer = Buffer.from('Test content', 'utf-8');
await fs.promises.writeFile(filename, buffer, {
flag: 'a'
});
fs.promises.copyFile(src, dest[, mode])
复制文件
mode 是一个可选的整数,指定拷贝操作的行为。 可以创建由两个或更多个值按位或组成的掩码(比如 fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE)。
fs.constants.COPYFILE_EXCL
- 如果 dest 已存在,则拷贝操作将失败。fs.constants.COPYFILE_FICLONE
- 拷贝操作将尝试创建写时拷贝(copy-on-write)链接。如果平台不支持写时拷贝,则使用后备的拷贝机制。fs.constants.COPYFILE_FICLONE_FORCE
- 拷贝操作将尝试创建写时拷贝链接。如果平台不支持写时拷贝,则拷贝操作将失败。// 复制文件
const fs = require('fs');
const path = require('path');
async function copy() {
const fromFilename = path.resolve(__dirname, './src/url.png');
const buffer = await fs.promises.readFile(fromFilename); // 读文件 -> buffer 格式
const toFilename = path.resolve(__dirname, './src/1.png');
await fs.promises.writeFile(toFilename, buffer); // 将读到的 buffer 格式文件写入;即可完成文件复制
}
copy();
fs.promises.stat()
文件状态
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname, './src/url.png');
async function demo() {
const stat = await fs.promises.stat(filename);
console.log(stat);
console.log("是否是目录:", stat.isDirectory()); // 是否是目录: false
console.log("是否是文件:", stat.isFile()); // 是否是文件: true
}
demo();
// 结果:
Stats {
dev: 170777962,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 8162774324768403,
size: 23667, // 大小(字节)
blocks: 48,
atimeMs: 1589633926043.3086,
mtimeMs: 1589614160734.891,
ctimeMs: 1589633931956.2944,
birthtimeMs: 1589633926042.3042,
atime: 2020-05-16T12:58:46.043Z, // 上次访问时间
mtime: 2020-05-16T07:29:20.735Z, // 上次修改时间
ctime: 2020-05-16T12:58:51.956Z, // 上次文件状态被修改时间
birthtime: 2020-05-16T12:58:46.042Z // 文件创建时间
}
// 也可查看目录状态:
// 目录的 size 值为 0 ; 因为目录就存一个指针,指向其内部存的文件(或文件夹位置)
fs.promises.readdir
获取目录下的直接子文件和直接子目录(返回一个数组)
const fs = require('fs');
const path = require('path');
const filename = path.resolve(__dirname, './src/'); // 只能是目录,若是文件,则会报错
async function demo() {
const paths = await fs.promises.readdir(filename);
console.log(paths); // [ '1.png', 'url.png' ]
console.log(Array.isArray(paths)); // true
}
demo();
fs.promises.mkdir()
创建目录
const fs = require('fs');
const path = require('path');
const dirname = path.resolve(__dirname, './src/test');
async function demo() {
const paths = await fs.promises.mkdir(dirname);
console.log("创建目录成功");
}
demo();
已弃用 (且没有 fs.exists()
方法)fs.promises.exists()
const fs = require('fs');
const path = require('path');
const dirname = path.resolve(__dirname, './src/test');
// 手写 exists() 方法
async function exists(filename){
try {
await fs.promises.stat(filename);
return true;
} catch (err) {
if(err.code === 'ENOENT'){
// 文件不存在
return false;
}
throw err;
}
}
async function test(){
const result = await exist(dirname);
if(result){
console.log("目录已存在,无需操作");
} else {
const paths = await fs.promises.mkdir(dirname);
console.log("目录创建成功");
}
}
test();
fs.unlink
/ fs.promises.unlink
删除文件
fs.rmdir
/ fs.promises.rmdir
删除目录
综上 API 的学习,进行一个简单的练习: 获取一个目录下的所有子目录和文件
格式为:
// 文件格式
let obj = {
// 属性:
name: '文件名',
ext: '文件后缀名',
isFile: '是否是一个文件',
size: '文件大小',
createTime: '文件创建时间',
updateTime: '文件更新时间',
// 方法:
getChildren() {
// 得到目录的所有直接子文件对象
// 若其本身就是文件,返回 []
},
getContent(isBuffer = false) {
// 读取文件内容
// 若是目录,返回 null
}
}
实现如下:
const fs = require('fs'); // File System 模块
const path = require('path'); // path 模块
class File {
constructor(filename, name, ext, isFile, size, createTime, updateTime) {
this.filename = filename;
this.name = name;
this.ext = ext;
this.isFile = isFile;
this.size = size;
this.createTime = createTime;
this.updateTime = updateTime;
}
// 异步函数读取文件内容
async getContent(isBuffer = false) {
if (this.isFile) {
if (isBuffer) {
return await fs.promises.readFile(this.filename);
} else {
return await fs.promises.readFile(this.filename, "utf-8");
}
}
return null;
}
// 异步函数获取所有子文件
async getChildren() {
// 文件没有子文件
if (this.isFile) {
return [];
}
let children = await fs.promises.readdir(this.filename);
children = children.map(name => {
const result = path.resolve(this.filename, name);
return File.getFile(result);
});
// 等所有 promise 全部成功完成才能返回
return Promise.all(children);
}
// 静态方法 --- 返回实例对象
static async getFile(filename) {
// 获取文件属性
const stat = await fs.promises.stat(filename),
name = path.basename(filename),
ext = path.extname(filename),
isFile = stat.isFile(),
size = stat.size,
createTime = stat.birthtime,
updateTime = stat.mtime;
return new File(filename, name, ext, isFile, size, createTime, updateTime);
}
}
// 获取dirname下的所有目录
async function readDir(dirname) {
// 得到 File 实例对象,并通过 dirname 挂载上所有属性和方法
const file = await File.getFile(dirname);
return await file.getChildren();
}
// 测试一下
async function test() {
const dirname = path.resolve(__dirname, './src');
const res = await readDir(dirname);
const writeFile = path.resolve(__dirname, "../srcDir.json");
// 干脆转换成 json 写到文件里...
// srcDir.json 与 src 是同级的 json 文件,这里直接是覆盖模式,没设置成追加模式
await fs.promises.writeFile(writeFile, JSON.stringify(res));
}
test(); // ---> 开干!!!
OK ! 第一天就到这里咯…