NodeJs 初识 (一)

NodeJS

NodeJS 核心

概述:Node.js 是一个基于 Chrome v8 引擎的 js 运行环境。

Node API

Node 全局对象(global)

提供类似部分 webApi 的方法和其他属性:

  1. setTimeout

  2. setInterval

  3. setImmediate

  4. console

  5. __dirname 当前模块的目录名(看似是全局变量,实质是模块变量(module))

  6. __filename 当前模块文件名(同上) ———— 当前的模块文件的绝对路径

  7. Buffer 处理二进制数据的

    Buffer 类是 JavaScript 语言内置的 Uint8Array 类的子类 → 中文文档

  8. 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 获取当前的操作系统

Node模块化细节

模块的查找
  • 根据绝对路径直接加载模块
  • 根据相对路径(相对于当前模块)加载模块(以 ./../ 开头),内部也会转换为绝对路径来加载模块
  • 相对路径(没以 ./ 或 …/ 开头的模块)查找加载规则:
    ① 检查是否是内置模块,如 fs / path
    ② 检查当前目录中的 node_modules 目录下有没有
    ③ 检查上级目录中的 node_modules 目录
    ④ 找到则转换为绝对路径(否则报错,找不到这个模块)
    ⑤ 加载模块
  • 若不写后缀名,node 会自动补全(补全顺序为: js → json → node → mjs)
  • 若只提供目录而没有提供文件名,则自动在该目录下寻找 index.js (默认);若修改过 package.json 中的 main 字段,则根据此字段指定的文件加载
require函数
// 某模块 (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;
}

因而上题的理解过程为:

  1. 内部导出的 module.exports === exports;
  2. 新增 c 属性为 3, 即 module.exports = exports = this = { c: 3 };
  3. 对 module.exports 重新赋值为一个新对象 { a: 1, b: 2 }
  4. this 与 exports 均指向原来的对象
  5. require 函数返回的对象是: module.exports 即新对象
  6. 所以,b.js 导入 a.js 打印的结果就是 { a: 1, b: 2 }

Node中的ES模块化

目前,Node中的 ES 模块化仍然处于实践阶段

模块要么是 CommonJS,要么是 ES(一个文件中不能混用):

  1. 默认情况下,均是 CommonJS;
  2. ES 模块处理办法:
    • 单个文件使用 ES module,更改其文件名后缀为 .mjs
    • 全部使用 ES 模块化,在 package.json 文件中加入 "type": "module"

当使用 ES 模块化运行时,命令行必须加上 --experimental-modules 语句,如:

node --experimental-modules index.mjs

基本内置模块

OS

const os = require(‘os’);

  1. os.EOL 一行结束的分隔(换行)符(不同的操作系统,换行符不同,所以可以使用此常量)
  2. os.arch() 获取 cpu 的架构名(主要是 x64 / x32)
  3. os.cpus() 获取 cpu 核的信息(几核芯片)
  4. os.freemem() 获取当前空闲内存量(单位是字节)
  5. os.homedir() 获取当前用户的主目录的字符串路径
  6. os.hostname() 获取当前操作系统主机名
  7. os.tmpdir() 获取操作系统的默认临时文件目录
path

const path = require(‘path’);

  1. path.basename(path[, ext]) 法返回 path 的最后一部分,参数 path 中之前的路径内容将会被忽略
    path <string> 文件路径
    ext 可选文件扩展名
    如果 path 不是字符串、或给定了 ext 但不是字符串,则抛出 TypeError

  2. path.delimiter 提供平台特定的路径定界符

  3. path.dirname 返回 path 的目录名

    const path = require('path');
    console.log(path.dirname('asdas/sfdasf/fasf/a.js'));
    // 返回 "asdas/sfdasf/fasf"
    
  4. path.extname() 返回文件名后缀名

  5. path.join(fullpath) 拼接多个字符串为一个路径

    const path = require('path');
    const fullpath = path.join('a', 'b', '../', 'c.js');
    console.log(fullpath);
    // 输出:   a\c.js      (windows系统下)
    
  6. path.normalize() 规范化给定的 path,解析 ‘…’ 和 ‘.’ 片段

    path.normalize('C:\\temp\\\\foo\\bar\\..\\');
    // 返回: 'C:\\temp\\foo\\'
    
  7. path.relative(from, to) 根据当前工作目录返回 from 到 to 的相对路径

  8. 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
    
  9. path.sep 提供平台特定的路径片段分隔符 (windows 系统返回 \)

url

URL图示

  1. 由 url 生成对象

    const URL = require('url');
    const url = new URL.URL('http://user:pass@sub.example.com:8080/p/a/t/h?query=name#hash');
    // 或者调用 URL.parse() 方法
    // const url = URL。parse('http://user:pass@sub.example.com:8080/p/a/t/h?query=name#hash');
    console.log(url);
    // 输出为一个对象
    URL {
        href: 'http://user:pass@sub.example.com: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
    
  2. 由对象生成url字符串 URL.format()

    // 如上例生成的 URL 对象
    const urlObj = {
        href: 'http://user:pass@sub.example.com: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
  1. 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 状态的传值
    */
    
  2. 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 (input / output)

外部设备的输入输出

如磁盘、网卡、显卡、打印机等
I/O 速度往往低于内存和CPU的交互速度

fs 模块

常用api:

  1. 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); // 与上面得到的结果相同
    
  2. 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'
    });
    
  3. fs.promises.copyFile(src, dest[, mode]) 复制文件

    • src 要拷贝的源文件名(string/buffer/url)
    • dest 拷贝操作的目标文件名
    • mode 用于拷贝操作的修饰符(默认值: 0)

    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();
    
  4. 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 ; 因为目录就存一个指针,指向其内部存的文件(或文件夹位置)
    
  5. 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();
    
  6. 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();
    
  7. 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();
    
  8. fs.unlink / fs.promises.unlink 删除文件

  9. 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 ! 第一天就到这里咯…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值