Node.js模块和npm概述

289 阅读10分钟

Node.js模块

  • 模块(包)是Node.js应用程序的基本组成部分。
  • 大部分前端工程化的工具,是以模块的形式存在的。

Node.js模块的对应关系

内置模块

  • fs、path、os、http...
  • 官方提供的,跟随Node.js一起安装
  • 官方链接:nodejs.cn/api/

自定义模块

  • 工程师自己写的

第三方模块

  • Less、Babel、Express...
  • 社区维护的,需要单独下载才能使用
  • www.npmjs.com/

web端的对象

宿主对象

  • document、window、location...

自定义对象

  • 工程师自己写的

第三方库

  • jQuery、Boostrap...

总结:Node.js模块和web端的对象

  • 如下图所示

模块和对象.png

内置模块 也叫核心模块

  • 在官方文件中查找某个函数,可以直接使用ctrl+f调出搜索框,输入需要查找的函数名称。

核心模块 - console

  • 控制台中输出的内容,通过不同的颜色标识不同的变量类型。
  • 控制台中可以一次输出多个变量,多个变量之间,用逗号分隔
  • 官方文档:nodejs.cn/api/console…

console方法的使用

  • console.log()
var obj = {
    name:"zs",
    age:18
}
console.log(obj);  // { name: 'zs', age: 18 }
  • console.table()
var obj = {
    name:"zs",
    age:18
}
console.table(obj); 
// 输出
// ┌─────────┬────────┐
// │ (index) │ Values │
// ├─────────┼────────┤
// │  name   │  'zs'  │
// │   age   │   18   │
// └─────────┴────────┘
  • console.time():启动一个计时器,用以计算一个操作的持续时间。
// 计时函数,需要前后的label相同
console.time("for");
for(let i = 0; i < 1000000; i++){
}
console.timeEnd("for"); //2.346ms

console.time("while");
var i = 0;
while(i < 1000000){
    i++;
}
console.timeEnd("while"); // 1.964ms

// 推荐使用:while循环,要比for循环效率更高些

核心模块 - process

  • process提供了有关当前Node.js进程的信息

process是全局变量

  • (global.process),使用时无需require引入
  • 官方文档:nodejs.cn/api/process…
  • process是个对象,内部使用点调用属性

process.png

process调用属性

  • process.version
// 输出Node.js的当前版本号
console.log(process.version); //v14.16.0
  • process.arch
// 输出操作系统架构
console.log(process.arch);  // x64
  • process.platform
// 输出操作系统平台
console.log(process.platform); // win32
  • process.cwd()
// 输出当前工作目录(文件夹) cwd = current working directory
// 第一种方式
console.log(process.cwd());
// 第二种方式
console.log(__dirname);
  • process.env (env = environment)
// 输出:环境变量
console.log(process.env);
// 自定义环境变量: 用来标识开发环境
process.env.NODE_ENV = 'develop';
console.log(process.env);
  • process.pid
    • 操作系统给每一个进程都分配了唯一的编号(pid)。
    • pid只有在进程运行期间,才能获取。
// 获取进程的编号
console.log(process.pid); // 27296

// 杀死进程 process.kill(进程编号)
console.log(process.kill(5634));

核心模块 - path

  • path模块提供了有关路径操作的函数。
    • 当前目录:./
    • 上一级目录:../
  • 使用之前,需要通过require引入
  • 官方文档:nodejs.cn/api/path.ht…

输出文件目录

  • 代码演示
// 输出当前文件所在的路径
console.log(__dirname);
// 输出当前文件的完整路径(包含当前文件的文件名)
console.log(__filename);

path的方法

// 不是全局变量,需要引入
var path = require("path");
  • path.extname()
// 获取文件的扩展名 ext = extension
console.log(path.extname(__filename)); // .js
  • path.dirname()
// 获取路径中的目录部分
// 与__dirname 输出相同
console.log(path.dirname(__filename));
  • path.basename()
// 获取路径中的文件名
console.log(path.basename(__filename)); // hello.js
  • path.join():可以将多个路径拼接起来
// 将路径拼接起来
// 下面方法含义:返回上一级目录
console.log(path.join(__dirname,'..'));

核心模块 - fs

  • fs(file system)提供了文件操作的API
  • 使用之前,需要通过require引入
  • 官方文档:nodejs.cn/api/fs.html
  • 目录就是文件夹,在程序的世界里,叫做目录。

文件操作(上半部分)

var fs = require("fs");
  • fs.writeFile():如果文件只读,不能写入,就会报错
// fs.writeFile('文件路径','写入内容',回调函数)
fs.writeFile('./txt1.txt','今天天气真好!',(err) => {
    // 如果文件只读,不能写入,就会报错
    if(err) throw err;
    console.log("写入成功");
});
  • fs.readFile():如果读取的文件不存在,会报错
// 读文件
// readFile('文件路径',回调函数)
fs.readFile('./txt1.txt',(err,data) => {
    if(err) throw err;
    // 输出:<Buffer e4 bb 8a e5 a4 a9 e5 a4 a9 e6 b0 94 e7 9c 9f e5 a5 bd ef bc 81>
    console.log(data);
});

// 直接输出data,输出的是十六进制数
// data其实在计算机中存储的时候是二进制的,输出的时候默认是十六进制的
// 利用方法toString(),将其转换成字符串输出
fs.readFile('./txt1.txt',(err,data) => {
    if(err) throw err;
    // 输出:<Buffer e4 bb 8a e5 a4 a9 e5 a4 a9 e6 b0 94 e7 9c 9f e5 a5 bd ef bc 81>
    // 直接输出data,输出的是十六进制数
    // data其实在计算机中存储的时候是二进制的,输出的时候默认是十六进制的
    // console.log(data);
    // 利用方法,将其转换成字符串输出
    // 输出结果:今天天气真好!
    console.log(data.toString()); 
});
  • fs.unlink():文件不存在的时候,删除会报错
// 删除文件
// fs.unlink('文件路径',回调函数);
fs.unlink('./txt1.txt',(err) => {
    if(err) throw err;
    console.log('删除成功');
});
  • fs.appendFile():
    • appendFile与writeFile的区别:writeFile只会将最后一次写入的内容加入到文件中,而不是追加内容到文件(appendFile)。

        // fs.appendFile('文件路径','写入内容','回调函数');
        fs.appendFile('./txt1.txt','今天天气真好',(err) => {
            if(err) throw err;
            // 多次执行,后会变成:今天天气真好今天天气真好今天天气真好....
            console.log("文件追加成功");
        });
      
    • 不想每次追加的文字都显示在一行,想让其换行(添加\n)

        // fs.appendFile('文件路径','写入内容','回调函数');
        fs.appendFile('./txt1.txt','今天天气真好\n',(err) => {
            if(err) throw err;
            console.log("文件追加成功");
        });
      
    • 文件路径,使用合并路径方法(__dirname + '/txt1.txt')

        // 使用拼接
        fs.appendFile(__dirname + '/txt1.txt','今天天气真好\n',(err) => {
            if(err) throw err;
            console.log("文件追加成功");
        });
      
    • 循环将一个数组中的数据,写入到文件中

        // 写入一个数组中的数据
        var arr = ['今天出太阳了','好舒服','出金光吧!'];
        for(var k of arr){
            fs.appendFile('./txt1.txt',k + '\n',(err) => {
                if(err) throw err;
            });
        }
      

文件操作(中场部分)

  • fs.mkdir()
// 创建目录(文件夹)
// fs.mkdir('目录路径',回调函数);
fs.mkdir('./d1',(err) => {
    if(err) throw err;
    console.log("创建目录成功");
});
  • fs.rmdir()

    • 删除空目录 成功
        // 删除目录
        // fs.rmdir('目录路径',回调函数)
        fs.rmdir('./d1',(err) => {
            if(err) throw err;
            console.log("删除目录成功");
        });
      
    • 删除非空目录,会报错: directory not empty
    • 解决问题(删除非空目录)
      • 1.先删除目录下的普通文件(清空目录)
      • 2.通过rmdir删除空目录
      // 删除非空目录:两步走
      // 1.先删除目录下的普通文件
      fs.unlink('./d1/1.txt',(err) => {
          if(err) throw err;
          console.log("文件删除成功");
      });
      // 2.再删除目录
      fs.rmdir('./d1',(err) => {
          if(err) throw err;
          console.log("目录删除成功");
      });
    
  • fs.rename(): 出现有同名目录时,就会报错

// 重命名目录
// fs.rename('旧名称','新名称',回调函数);
fs.rename(__dirname + '/d1',__dirname + '/d2',(err) => {
    if(err) throw err;
    console.log("目录重命名成功");
});
  • fs.readdir()

    • 常规输出
      // 读目录,读文件夹中所有的内容
      // fs.readdir('目录路径',回调函数);
      fs.readdir(__dirname,(err,data) => {
          if(err) throw err;
          // 输出结果:[ '1_前端工程化.md', 'd1', 'd2', 'hello.js', 'img', 'txt1.txt' ] 
          console.log(data);
      });
      
    
    * 使用map遍历(和foreach有点相似),一个一个输出
     ```js
       // 换行输出
       fs.readdir(__dirname,(err,data) => {
           if(err) throw err;
           data.map((d) => {
               console.log(d);
           });
       });
    
    • 读目录,并判断读取的是:目录 还是 文件

文件操作(下半部分)

fs - 同步函数(synchronization)

  • 在主程序中自上而下运行
  • 例如:去火车站排队买票
  • 当任务有先后顺序,用同步函数(sync)
    • 例如:删除文件之前要先确保文件是存在的
        // 删除文件之前,要先确保文件是存在的
        // 返回值:true\false
        if(fs.existsSync(__dirname + './t1.txt')){
            // 删除文件
            fs.unlinkSync(__dirname + './t1.txt',(err) => {
                if(err) throw err;
            });
        }else {
            console.log("文件不存在");
        }
      

异步函数

  • 通过回调函数在事件队列中运行
  • 例如:委托黄牛买票,买好票后通知我(无需等待,可以做其他事)

总结(同步和异步函数)

  • 同步函数名和异步函数名的区别,就是:同步函数名有sync

同步与异步函数.png

  • 主程序(单线程)是同步执行,事件队列是异步执行。

主程序与事件队列.png

fs实现文件复制

  • 将d1/txt1.txt 复制到 d2/t.txt(d2目录存在),那么文件复制成功
  • 将文件复制到不存在的目录中,复制不会成功,需要先创建新目录
// fs实现文件复制
// 将d1/txt1.txt 复制到 d2/t.txt  文件复制成功
// 将文件复制到不存在的目录中,复制不会成功,需要先创建新目录
// 1.读文件
if(fs.existsSync(__dirname + '/d1/txt1.txt')){
    fs.readFile(__dirname + '/d1/txt1.txt',(err,data) => {
        if(err) throw err;
        else {
            if(!fs.existsSync(__dirname + '/d3')){
                // 2.创建新目录
                fs.mkdir(__dirname + '/d3',(err) => {
                    if(err) throw err;
                    console.log("目录创建成功");
                });
            }
            // 3.写文件
            fs.writeFile(__dirname + '/d3/t.txt',data,() => {
                if(err) throw err;
                console.log("写入成功");
            });
        }
    });
}

fs实现文件压缩

// fs实现文件压缩
// 使用正则表达式
// 去除空格
// 1.读取文件内容
var path = require("path");
// var ph = __dirname + '/d1/test.css';
var ph = path.join(__dirname,'d1/test.css');
if(fs.existsSync(ph)){
    fs.readFile(ph,(err,data) => {
        if(err) throw err;
        else{
            // 2.将内容中的空格 替换成 空字符串
            // 还需要将注释也去掉 \s 表示 空白字符 \S 表示 非空白字符
            // 不能写^ $ 限制,因为一旦写了就必须是这个开头和结尾,而不是在一段字符串中寻找。
            var dataA = data.toString().replace(/\s+/g,'').replace(/\/\*{1,2}[\s\S]*\*\//g,'');
            // 3.再次写入到文件中
            fs.writeFile(ph,dataA,() => {
                if(err) throw err;
                console.log("写入成功");
            });
        }
    });
}

文件流(缓冲vs流)

文件操作 - 缓冲方式

  • 源文件 -> 内存缓冲 -> 目标文件

  • 一直将内存填充满,操作系统才会将其给目标文件。

缓冲方式.png

  • 比喻:缓冲就像一个桶一样,和尚挑水要看桶的大小,桶要是小的话,就需要挑多次。

文件操作 - 流方式

  • 源文件 -> 管道 -> 目标文件

流方式.png

  • 比喻:流就相当于水龙头。
// 流的写法
var rs = fs.createReadStream('./d1/test.css');
var ws = fs.createWriteStream('./d2/test.css');
// 接通读文件和写文件之间的管道
rs.pipe(ws);

为什么选择流

  • 内存效率提高
    • 无需加载大量数据(缓冲要足够大才行)
    • 流把大数据切成小块,占用内存更少
  • 时间效率提高
    • 接获数据后立即开始处理
    • 无需等到内存缓冲填满

核心模块 - http

http创建服务器

  • 代码演示(注意:有中文的话,一定要设置响应字符集,否则会乱码)
// 引入http
const http = require("http");
// 1.创建服务器
const server = http.createServer((req,res) => {
    res.statusCode = 200;
    // text/html是以html的形式输出,比如就会在页面上显示一个文本框。
    // text/plain形式就会在页面上原样显示这段代码。
    res.setHeader('Content-Type','text/plain; charset=utf-8');
    res.end('你好:Node.js');
});

// 2.发布web服务
const port = 3000;
const host = 'localhost';
// 在浏览器中访问http://localhost:3000 然后能看到效果
server.listen(port,host,() => {
    console.log(`服务器运行在 http://${host}:${port}`);
})

自定义模块(程序员自己写的Node.js模块)

  • Node.js中每一个单独的.js文件,就是一个模块。
  • 每个模块中都有一个module变量,其代表当前模块。(相当于function中的this)

自定义模块书写与使用

  • 自定义模块写完之后,需要通过exports暴露出来,才能够被外部使用。
  • 没有暴露出来的属性和方法,就成为其私有的属性和方法,外部没有办法调用。
  • 自定义模块书写 代码演示
// 自定义模块
const PI = 3.14;
// 1.计算圆的周长
function perimeter (r){
    return 2 * PI * r;
}
// 2.计算圆的面积
function area (r){
    //  return PI * r * r;
    return PI * Math.pow(r,2);
}

// 3.通过exports将属性和方法暴露
module.exports = {
    // 在ES6中,属性名和属性值相同时,可以只写一遍
    perimeter
}
  • 外部调用自定义模块的方法(引入写入自定义模块的路径)
// 引入自定义的模块(引入写入自定义模块的路径)
var circle = require("./1_自定义模块"); 

// 自定义半径变量的值
const r = 10;
// 调用自定义模块中的周长函数
console.log('周长:',circle.perimeter(r));
// 会报错,因为area()并没有通过exports暴露出来,因此属于自定义模块的私有方法,不能被外界调用
console.log('面积:',circle.area(r));
  • 注意:在ES6中,属性名和属性值相同时,可以只写一遍

模块的加载逻辑(即:模块的引入)

按组织方式划分模块

  • 文件模块:一个独立的.js文件。
  • 目录模块:将多个.js文件放在一个目录中。

目录模块的加载逻辑

// 目录结构
> dir01
--- a.js
--- b.js
--- c.js

const dir01 = require('./dir01');
  • 步骤:
    • 1.确定入口文件
    • 2.引入入口文件
    • 3.然后在入口文件中引入其他文件

通过组织方式来详细讲解

文件模块

以路径开头引入

  • require('./circle')
  • 引入自定义的模块,后缀名.js可以省略

不以路径开头引入

  • require('path')
  • 引入官方核心模块

目录模块

路径开头引入

  • require('./dir01')
  • 找到指定路径下的dir01目录,然后引入入口文件。
  • 入口文件:dir01目录下存在index.js
// ----------index.js--------------
// 这是d1文件夹的入口文件
function info(){
    console.log('d1文件夹的入口文件');
}

module.exports = {
    info
}

// -----------test.js---------------
// 现在引入目录模块中的自定义模块
// 通过路径引入:去d1文件夹中寻找入口文件
var dir1 = require('./d1');

console.log(dir1.info()); //d1文件夹的入口文件
  • 入口文件:dir01目录下存在package.json
// ----------------package.json-------------------
{
    "main":"1_自定义模块.js"
}
// ---------------test.js----------------------
// 现在引入目录模块中的自定义模块
// 通过路径引入:去d1文件夹中寻找入口文件
var dir1 = require('./d1');
console.log(dir1.info()); //d1文件夹的入口文件

不以路径开头引入

  • 到当前目录下的node_modules目录中寻找dir02;
  • 如果找不到会到上一级目录寻找,直到顶层目录;假如到顶层还没有找到,就会报错。

module中的path.png

  • 找到目录模块dir02后,引入dir02中的入口文件。

如何确定入口文件?

  • 在目录中寻找package.json文件,入口文件通过其main属性指定。
  • 如果找不到,则**默认引入index.js.
  • 注:package.json是目录模块的描述文件。

如何在入口文件中引入其他的自定义模块文件

// ----------index.js--------------
// 这是d1文件夹的入口文件
// 引入其他自定义模块
const circle = require('./1_自定义模块');
const r = 10;
// 这是d1文件夹的入口文件
function info(){
    console.log('inex.js文件中:' + circle.area(r));
    console.log('d1文件夹的入口文件');
}

module.exports = {
    info,
    area: circle.area
}

// -----------test.js---------------
// 现在引入目录模块中的自定义模块
// 通过路径引入:去d1文件夹中寻找入口文件
var dir1 = require('d1');

// 自定义半径变量的值
const r = 10;
// 调用自定义模块中的周长函数
console.log('面积:',dir1.area(r));  // 输出:面积: 314
// 输出:inex.js文件中:314
console.log(dir1.info()); // 输出:d1文件夹的入口文件

第三方模块(社区维护的Node.js模块)

  • 前端工程化的大部分工具,都是第三方模块
  • 想要使用Node.js的第三方模块,需要通过单独安装。

npmjs.com

  • npmjs.com就是第三方模块集中的管理平台
  • 网址:www.npmjs.com/

npm(Node Package Manager)概述

  • 是Node.js的包管理工具
  • 包就是一坨代码,就是Node.js的第三方模块。

npm是一个命令

  • 跟随Node.js一起安装
  • npm可以下载(安装)包和包的依赖,例如Boostrap是依赖jQuery,那么下载的时候,会将其一起下载安装。

npm的作用

  • 手动下载:1.打开网站 2.找到资源 3.点击下载
  • npm安装:npm install <package-name>

npm镜像源

  • npm从镜像源下载包

npm从镜像源下载.png

  • npm下载类比之应用商店

应用商店下载.png

修改npm镜像源

国外镜像源(响应时间过长)

国内镜像源

修改命令(在命令行进行修改)

使用npm安装包

  • npm install

全局安装

  • 每个项目都能用到(将包当作全局工具使用)
  • npm install --global
  • 简写:npm i -g

步骤

  • 1.明确你的需求
  • 2.找到合适的包
  • 3.通过npm安装包
  • 4.使用包

项目(局部)安装

  • 只在当前项目中使用
  • npm install --save
  • 简写:npm i -S

步骤

  • 1.创建项目目录(mkdir project)
  • 2.进入项目目录(cd project)
  • 3.初始化项目(npm init)
  • 4.在项目中安装包(注意目录)

局部安装和全局安装区别

  • 局部安装:要初始化项目 npm init

初始化项目.png

  • 局部安装:要在项目中执行局部安装命令,就会出现node_modules文件夹

    • minify的命令:node_modules/.bin/minify下面执行命令
  • .bin文件夹中的是 minify和minify所依赖的包

minify.png

.\node_modules\.bin\minify .\index.css > ./index.min.css

--save与--save-dev

npm安装命令的参数

  • --save / -S :安装的包,开发和上线都需要
    • 例如: jQuery
  • --save-dev / -D:安装的包,只在开发环境使用
    • 例如:minify
  • 在项目中的package.json文件中的区别

save_dev.png

  • dependencies是开发和上线都需要依赖的包
  • devdependencies只是开发需要的包

npm安装包的方式

  • 全局安装
    • npm i -g
  • 项目安装
    • npm i -S
    • npm i -D

附录

  • 快捷键打开任务管理器: ctrl+shift+esc
  • 在ES6中,属性名和属性值相同时,可以只写一遍
  • Bootstrap封装了前端最常使用的一些效果。