node.js基础入门教程

node.js从了解到入门之模块化、包管理和文件读写

Posted by yishuifengxiao on 2019-03-24

本文章主要是讲述了 node.js 的模块化基础知识以及 node.js 中的包管理和文件读写的简单应用


一 模块化详解

  • Node 是对 ES 标准一个实现,Node 也是一个 JS 引擎

  • 通过 Node 可以使 js 代码在服务器端执行

  • Node 仅仅对 ES 标准进行了实现,所以在 Node 中不包含 DOM 和 BOM
  • Node 中可以使用所有的内建对象
    String Number Boolean Math Date RegExp Function Object Array
    而 BOM 和 DOM 都不能使用但是可以使用 console 也可以使用定时器(setTimeout() setInterval())
  • Node 可以在后台来编写服务器
    Node 编写服务器都是单线程的服务器 - 进程 - 进程就是一个一个的工作计划(工厂中的车间) - 线程 - 线程是计算机最小的运算单位(工厂中的工人),线程是干活的 - 传统的服务器都是多线程的 - 每进来一个请求,就创建一个线程去处理请求 - Node 的服务器单线程的 - Node 处理请求时是单线程,但是在后台拥有一个 I/O 线程池

1.1 暴露变量和方法

模块化

  • 在 Node 中,一个 js 文件就是一个模块
  • 在 Node 中,每一个 js 文件中的 js 代码都是独立运行在一个函数中
    而不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问

我们可以通过 exports 来向外部暴露变量和方法,只需要将需要暴露给外部的变量或方法设置为 exports 的属性即可。

实例代码

1
2
3
4
5
exports.x = "我是02.module.js中的x";
exports.y = "我是y";
exports.fn = function () {

};

1.2 引入外部的模块

在 node 中,通过 require()函数来引入外部的模块

require()可以传递一个文件的路径作为参数,node 将会自动根据该路径来引入外部模块这里路径,如果使用相对路径,必须以.或..开头

使用 require()引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块

我们使用 require()引入外部模块时,使用的就是模块标识,我们可以通过模块标识来找到指定的模块

  • 模块分成两大类

    **核心模块**
    
        由node引擎提供的模块
    
    核心模块的标识就是,模块的名字

文件模块

- 由用户自己创建的模块
- 文件模块的标识就是文件的路径(绝对路径,相对路径)相对路径使用.或..开头

实例代码

1
2
3
4
var math = require("./math");
var fs = require("fs");

console.log(math.add(123,456));

1.3 全局变量

在 node 中有一个全局对象 global,它的作用和网页中 window 类似

在全局中创建的变量都会作为 global 的属性保存

在全局中创建的函数都会作为 global 的方法保存

当模块里的变量没有使用 var 等限制符时,默认是全局变量,即可通过 global.变量名 访问

当 node 在执行模块中的代码时,它会首先在代码的最顶部,添加如下代码

1
function (exports, require, module, __filename, __dirname) {

在代码的最底部,添加如下代码

1
}

实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递进了 5 个实参
exports - 该对象用来将变量或函数暴露到外部

     require
         - 函数,用来引入外部的模块

     module
         - module代表的是当前模块本身
         - exports就是module的属性
         - 既可以使用 exports 导出,也可以使用module.exports导出

     __filename

C:\Users\lilichao\WebstormProjects\class0705\01.node\04.module.js - 当前模块的完整路径

       __dirname

C:\Users\lilichao\WebstormProjects\class0705\01.node - 当前模块所在文件夹的完整路径

即实际的代码为

1
2
3
4
5
function (exports, require, module, __filename, __dirname) {
用户自己定义的函数或变量


}

1.4 exports 和 module.exports

exports 和 module.exports 的区别

  • 通过 exports 只能使用.的方式来向外暴露内部变量
1
2
3
4
exports.xxx = xxx
exports.x1 = xxx
exports.x2 = xxx
exports.x3 = xxx
  • 而 module.exports 既可以通过.的形式,也可以直接赋值
1
2
3
4
5
6
7
8
9
module.exports.x1 = xxxx
module.exports.x2 = xxxx
module.exports = {
xxx:"dfdsf",
xx:11,
xxxx:function(){

}
}

exports 和 module.exports 的使用

如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似 class,包含了很多属性和方法),就用 module.exports。

  • exports 和 module.exports 的区别
    • 每个模块中都有一个 module 对象
    • module 对象中有一个 exports 对象
    • 我们可以把需要导出的成员都挂载到 module.exports 接口对象中
    • 也就是:moudle.exports.xxx = xxx 的方式
    • 但是每次都 moudle.exports.xxx = xxx 很麻烦,点儿的太多了
    • 所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:exports
    • exports === module.exports 结果为 trues
    • 所以对于:moudle.exports.xxx = xxx 的方式 完全可以:expots.xxx = xxx
    • 当一个模块需要导出单个成员的时候,这个时候必须使用:module.exports = xxx 的方式
    • 不要使用 exports = xxx 不管用
    • 因为每个模块最终向外 return 的是 module.exports
    • exports 只是 module.exports 的一个引用
    • 所以即便你为 exports = xx 重新赋值,也不会影响 module.exports
    • 但是有一种赋值方式比较特殊:exports = module.exports 这个用来重新建立引用关系的

二 node.js 包管理

CommonJS 的包规范允许我们将一组相关的模块组合到一起,形成完整工具。

CommonJS 的包规范由 的包规范由 包结构包描述文件 两部分组成

包结构

用于组织包中的各种文件

包实际上就是一个压缩文件,解压以后还原为目录。符合规范的目录应包含如下文件:

  • package.json 描述文件
  • bin 可执行的二进制文件
  • lib js 文件
  • doc 文档
  • test 单元测试

包描述文

描述包的相关信息,以供外部读取分析

他是一个 json 格式的文件—— package.json 位于包的根目录的下,是包的重要组成文件。

2.1 npm 包管理

CommonJS 的规范是理论,npm 是其中的一种实现。

对于 node 而言,npm 帮助其万恒了第三方模块的腹部、安装和依赖等。借助 npm,node 与第三方模块间形成了一个很好的生态系统。

由于新版的 nodejs 已经集成了 npm,所以之前 npm 也一并安装好了。同样可以通过输入 “npm -v” 来测试是否成功安装。命令如下,出现版本提示表示安装成功:

1
2
$ npm -v
2.3.0

使用 npm 命令安装模块

npm 安装 Node.js 模块语法格式如下:

1
npm install <Module Name>

以下实例,我们使用 npm 命令安装常用的 Node.js web 框架模块 express:

1
npm install express

安装好之后,express 包就放在了工程目录下的 node_modules 目录中,因此在代码中只需要通过 require(‘express’) 的方式就好,无需指定第三方包路径。

1
var express = require('express');

全局安装与本地安装

npm 的包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差别只是有没有-g 而已,比如

1
2
npm install express          # 本地安装
npm install express -g # 全局安装

本地安装

  1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  2. 可以通过 require() 来引入本地安装的包。

全局安装

  1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  2. 可以直接在命令行里使用。

常用的 npm 命令

1
2
3
4
5
6
7
8
- npm -v 查看npm的版本
- npm version 查看所有模块的版本
- npm search 包名 搜索包
- npm install / i 包名 安装包
- npm remove / r 包名 删除包
- npm install 包名 --save 安装包并添加到依赖中 *****
- npm install 下载当前项目所依赖的包
- npm install 包名 -g 全局安装包(全局安装的包一般都是一些工具)

2.2 npm 包引用

node 在使用模块名字来引入模块时,它会首先在当前目录的 node_modules 中寻找是否含有该模块
如果有则直接使用,如果没有则去上一级目录的 node_modules 中寻找
如果有则直接使用,如果没有则再去上一级目录寻找,直到找到为止
直到找到磁盘的根目录,如果依然没有,则报错

三 node 文件读写

3.1 Buffer(缓冲区)

在 v6.0 之前创建 Buffer 对象直接使用 new Buffer()构造函数来创建对象实例,但是 Buffer 对内存的权限操作相比很大,可以直接捕获一些敏感信息,所以在 v6.0 以后,官方文档里面建议使用 Buffer.from() 接口去创建 Buffer 对象。

Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换。

Node.js 目前支持的字符编码包括:

ascii - 仅支持 7 位 ASCII 数据。如果设置去掉高位的话,这种编码是非常快的。

utf8 - 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。

utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。

ucs2 - utf16le 的别名。

base64 - Base64 编码。

latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。

binary - latin1 的别名。

hex - 将每个字节编码为两个十六进制字符。

1
2
3
4
5
6
7
const buf = Buffer.from('runoob', 'ascii');

// 输出 72756e6f6f62
console.log(buf.toString('hex'));

// 输出 cnVub29i
console.log(buf.toString('base64'));
Buffer(缓冲区)
    - Buffer的结构和数组很像,操作的方法也和数组类似
    - 数组中不能存储二进制的文件,而buffer就是专门用来存储二进制数据
    - 使用buffer不需要引入模块,直接使用即可
    - 在buffer中存储的都是二进制数据,但是在显示时都是以16进制的形式显示
        buffer中每一个元素的范围是从00 - ff   0 - 255
        00000000 - 11111111

        计算机 一个0 或一个1 我们称为1位(bit)

        8bit = 1byte(字节)
        1024byte = 1kb
        1024kb = 1mb
        1024mb = 1gb
        1024gb = 1tb

        buffer中的一个元素,占用内存的一个字节

Buffer 的大小一旦确定,则不能修改,Buffer 实际上是对底层内存的直接操作

3.2 文件读写

Node.js 提供一组类似 UNIX(POSIX)标准的文件操作 API。 Node 导入文件系统模块(fs)语法如下所示:

1
var fs = require("fs")

fs 是核心模块,无需下载

文件系统(File System)
    - 文件系统简单来说就是通过Node来操作系统中的文件
    - 使用文件系统,需要先引入fs模块,fs是核心模块,直接引入不需要下载

同步文件的写入

  • 手动操作的步骤

1.打开文件
fs.openSync(path, flags[, mode])

  • path 要打开文件的路径
  • flags 打开文件要做的操作的类型

    r 只读的

    w 可写的

  • mode 设置文件的操作权限,一般不传
  • 返回值:
    该方法会返回一个文件的描述符作为结果,我们可以通过该描述符来对文件进行各种操作

2.向文件中写入内容

fs.writeSync(fd, string[, position[, encoding]])

  • fd 文件的描述符,需要传递要写入的文件的描述符
  • string 要写入的内容
  • position 写入的起始位置
  • encoding 写入的编码,默认 utf-8

3.保存并关闭文件
fs.closeSync(fd)

  • fd 要关闭的文件的描述符

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

3.3 简单文件写入

参见 API

1
2
3
4
5
6
7
8
9
10
11
12
13
fs.writeFile(file, data[, options], callback)

fs.writeFileSync(file, data[, options])

- file 要操作的文件的路径
- data 要写入的数据
- options 选项,可以对写入进行一些设置
- callback 当写入完成以后执行的函数

- flag
r 只读
w 可写
a 追加

示例

1
2
3
4
5
6
7
fs.writeFile("D:/demo.txt","简单文件写入",{flag:"w"} , function (err) {
if(!err){
console.log("===》 写入成功");
}else{
console.log(err);
}
});

在此模式下,无需打开文件和关闭文件操作。

3.4 流式文件写入

同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出

API

  1. 创建一个可写流
1
2
3
4
fs.createWriteStream(path[, options])
- 可以用来创建一个可写流
- path,文件路径
- options 配置的参数

示例代码

1
var ws = fs.createWriteStream("hello3.txt");
  1. 通过监听流的 open 和 close 事件来监听流的打开和关闭
on(事件字符串,回调函数)
    - 可以为对象绑定一个事件

once(事件字符串,回调函数)
    - 可以为对象绑定一个一次性的事件,该事件将会在触发一次以后自动失效

示例代码

1
2
3
4
5
6
7
ws.once("open",function () {
console.log("=========》 流打开");
});

ws.once("close",function () {
console.log("===》 流关闭了");
});

由于 open 是一个一次性时间,故可以用 once 绑定,以提高性能。

注意
最后需要关闭流,需要用 end 异步关闭

1
2
//关闭流
ws.end();

3.5 流式文件读取

流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var fs = require("fs");

//创建一个可读流
var rs = fs.createReadStream("D:/demo.doc");
//创建一个可写流
var ws = fs.createWriteStream("aaa.doc");

//监听流的开启和关闭
rs.once("open",function () {
console.log("====> 可读流打开");
});

rs.once("close",function () {
console.log("===========> 可读流关闭");
//数据读取完毕,关闭可写流

ws.end();
});

ws.once("open",function () {
console.log("====> 可写流打开");
});

ws.once("close",function () {
console.log("=====> 可写流关闭");
});

//如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,它会自动开始读取数据
rs.on("data", function (data) {
//console.log(data);
//将读取到的数据写入到可写流中
ws.write(data);
});

pipe()可以将可读流中的内容,直接输出到可写流中

rs.pipe(ws);