本文章主要是讲述了 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 | exports.x = "我是02.module.js中的x"; |
1.2 引入外部的模块
在 node 中,通过 require()函数来引入外部的模块
require()可以传递一个文件的路径作为参数,node 将会自动根据该路径来引入外部模块这里路径,如果使用相对路径,必须以.或..开头
使用 require()引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块
我们使用 require()引入外部模块时,使用的就是模块标识,我们可以通过模块标识来找到指定的模块
模块分成两大类
**核心模块**
由node引擎提供的模块
核心模块的标识就是,模块的名字
文件模块
- 由用户自己创建的模块
- 文件模块的标识就是文件的路径(绝对路径,相对路径)相对路径使用.或..开头
实例代码
1 | var math = require("./math"); |
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 | function (exports, require, module, __filename, __dirname) { |
1.4 exports 和 module.exports
exports 和 module.exports 的区别
- 通过 exports 只能使用.的方式来向外暴露内部变量
1 | exports.xxx = xxx |
- 而 module.exports 既可以通过.的形式,也可以直接赋值
1 | module.exports.x1 = xxxx |
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
结果为true
s- 所以对于:
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 | $ npm -v |
使用 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 | npm install express # 本地安装 |
本地安装
- 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
- 可以通过 require() 来引入本地安装的包。
全局安装
- 将安装包放在 /usr/local 下或者你 node 的安装目录。
- 可以直接在命令行里使用。
常用的 npm 命令
1 | - npm -v 查看npm的版本 |
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 | const buf = Buffer.from('runoob', 'ascii'); |
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 | fs.writeFile(file, data[, options], callback) |
示例
1 | fs.writeFile("D:/demo.txt","简单文件写入",{flag:"w"} , function (err) { |
在此模式下,无需打开文件和关闭文件操作。
3.4 流式文件写入
同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出
- 创建一个可写流
1 | fs.createWriteStream(path[, options]) |
示例代码
1 | var ws = fs.createWriteStream("hello3.txt"); |
- 通过监听流的 open 和 close 事件来监听流的打开和关闭
on(事件字符串,回调函数) - 可以为对象绑定一个事件 once(事件字符串,回调函数) - 可以为对象绑定一个一次性的事件,该事件将会在触发一次以后自动失效
示例代码
1 | ws.once("open",function () { |
由于 open 是一个一次性时间,故可以用 once 绑定,以提高性能。
注意
最后需要关闭流,需要用 end 异步关闭
1 | //关闭流 |
3.5 流式文件读取
流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
1 | var fs = require("fs"); |
pipe()可以将可读流中的内容,直接输出到可写流中
rs.pipe(ws);