其他章节请看:

​前端学习 node 快速入门 系列​


模块(module)

模块的导入

核心模块

在 ​​初步认识 node​​​ 这篇文章中,我们在读文件的例子中用到了 ​​require('fs')​​​,在写最简单的服务器的例子中用到了 ​​require('http')​​,除了 fs 和 http,node 提供了很多核心模块,例如:path(路径)、os(操作系统)、events(事件)、url 等等。

如果我们需要使用​核心模块​的功能,就使用 ​​require(模块名)​​ 方法进行引入。

第三方模块

在 ​​npm​​ 一文中,我们知道了如何用 npm 下载包。如果我们需要使用​第三方的模块​,也可以像引入核心模块那样。请看示例:

// 首先得下载包。后续不再提醒
$ npm install underscore

let _ = require('underscore')

console.log(_.VERSION)
console.log(typeof _.clone)
console.log(typeof _.find)

/*
输出:
1.12.0
function
function
*/

​:由于核心模块和第三方模块(npm 下载的包)都是通过​模块名​加载,所以它们的名字不会相同。也就是说 node 不允许第三方模块占用 node 核心模块的名字。

require() 方法加载第三方模块有这么一套​规则​:

  1. 找到执行文件所属目录的 node_modules 文件夹
  2. 找到 node_modules/第三方模块名/package.json
  3. 找到 main 字段指向的入口文件
  • 加载入口文件
  1. 找不到 package.json 或找不到 main 字段,又或者找不到 main 指向的文件,就加载 index.js
  2. 以上都失败,则去上一级找 node_modules 目录,找不到又去上一级,上一级,直到根目录都没有找到,就报错处理

我们用 underscore 这个第三方模块验证一下上面的规则。请看示例:

首先准备环境,导入 underscore。

// 进入项目(D:\实验楼\node-study\test2)

// 快速生成 package.json
$ npm init -y
// 下载 underscore
$ npm install underscore

// 创建 test2/index.js
let _ = require('underscore')
console.log(_.VERSION)

// 运行
$ node index
1.12.0
function

现在模块已经正常导入。

我们首先验证一下:加载的是否是 main 字段指向的文件。具体步骤请看:

// 修改 package.json 中的 main 字段。目录是:test2\node_modules\underscore\package.json
{
"main": "underscore.js",
// 改为
"main": "a.js",
}

// 新建 a.js 文件。目录是:test2\node_modules\underscore\a.js
exports.name = 'ph'

// 修改 index.js 内容如下
let _ = require('underscore')
console.log(_)

$ node index
{ name: 'ph' }

重置 package.json 中 main 字段,将 underscore 模块的主入口文件改为 a.js,最终我们拿到的 underscore 确实是我们返回的 ​​{ name: 'ph' }​​。

接着将 a.js 改为 index.js,执行 ​​node index​​​,输出的还是 ​​{ name: 'ph' }​​。说明 require() 找不到 main 指向的入口文件 a.js,于是就去找 index.js。

最后将 node_modules 文件夹剪切至 D 盘根目录中,执行 ​​node index​​​ 仍旧输出 ​​{ name: 'ph' }​​。

自定义模块

在 node 中,每个文件都被视为一个​单独的模块​。

浏览器可以通过 script 标签执行多个 js 文件,但 node 只能执行​一个文件​,不过我们可以通过 require() 方法加载其他 js 文件,js 文件又加载其他 js 文件,如此循环,最终就形成了一个大大的模块。请看示例:

// index.js 内容
let m = require('./a') // {1}
console.log(m.sum(1,2))

// a.js 内容
let sum = (v1, v2) => v1 + v2
module.exports.sum = sum

// 运行
$ node index
3

index.js 中加载了 a.js。相当于运行了 2 个文件。

如果将 ​​require('./a')​​​ 改成 ​​require('a')​​​(行{1}),运行则会报错:Error: Cannot find module 'a'。因为 ​​require('a')​​ 会直接去核心模块和第三方模块中找,结果又没有找到对应的 a 模块。只有传入 require(id) 方法中的 id 以 ​'/'、'./'或'../'开头​,才会从自定义模块中找。

通常 '/' 很少会用到。请看示例:

// D:\1.js
console.log('hello')

// D:\实验楼\node-study\index.js
require('/1')

$ node index
hello

运行 ​​require('/1')​​ 会到文件的根目录(D 盘)中找 1.js。如果别人用你的项目,还得在根目录下也存一份同样的 1.js 文件吗?这是很困难的。

到现在,我们已经知道 require() 方法能导入模块,也能执行模块。其实它还有一个​重要特性​:优先从缓存中加载,重复导入也只会执行一次。

// 1.js
var a = 1
let two = require('./2')
let twoCopy = require('./2')
console.log(a)
console.log(`two a = ${two.a}`)
console.log(`twoCopy a = ${twoCopy.a}`)

// 2.js
var a = 2
console.log(`我是 2.js`) // {1}
exports.a = a;

// 运行
$ node 1
我是 2.js
1
two a = 2
twoCopy a = 2

在 1.js 中导入了两次 2.js,但只输出了一次 ​​我是 2.js​​(行{1})。

请接着看:

// index.js
require('./a')
require('./b')

// a.js
require('./b')

// b.js
console.log('hello')

$ node index
hello

模块 b.js 同样被导入了两次,但也只执行了一次。

模块的导出

每个文件都有一个 ​module​ 的变量。就像 require() 方法,无需加载就可以直接使用。请看示例:

// index.js
console.log(typeof require)
console.log(module)

// 运行
$ node index
function
Module {
id: '.',
...
exports: {}, // {1}
...
}

我们看到 module 里面有一个 ​exports​ 的对象(行{1})。

如果我们的模块需要对外提供接口,就可以使用 ​module.exports​。请看示例:

// index.js
let m = require('./1')
console.log(`name = ${m.name} age = ${m.age}`)

// 1.js
let name = 'ph'
let age = 'age'
module.exports.name = name // {1}

$ node index
name = ph age = undefined

模块 1.js 只导出了 name 属性,所以 index.js 只能读取到 name ,而读不到 age。

module.exports 还提供了一个​快捷方式​:直接使用 exports。例如将 ​​module.exports.name = name​​​(行{1}) 改成 ​​exports.name = name​​ 效果也是一样的。

​:由于 exports 是 module.exports 的引用,就像任何变量一样,如果将新值分配给 exports,则它不再绑定到 module.exports。请看示例:

// index.js
let m = require('./1')
console.log(m)

// 1.js
module.exports = 'ph' // {1}

$ node index
ph

如果将 ​​module.exports = 'ph'​​​(行{1}) 换成 ​​exports = 'ph'​​​,输出结果是:​​{}​​,说明导出失败。

模块的作用域

node 中没有全局作用域,只有​模块作用域​。内部访问不了外部,外部访问不了内部。请看示例:

// 1.js
var a = 1
var a = 2
console.log(a)

执行 ​​$ node 1​​ 输出 2。稍微改一下:

// 1.js
var a = 1
require('./2')
console.log(a)

// 2.js
var a = 2

再次执行 ​​$ node 1​​​,这次输出的是 1。说明 2.js 中的 变量 ​​a​​​ 没能影响到 1.js 中的变量 ​​a​​。继续:

// 1.js
require('./2')
console.log(a)

// 2.js
var a = 1

再次运行 ​​$ node 1​​​,输出报错信息 ​​ReferenceError: a is not defined​​,说明 1.js 不能访问 2.js 中的变量。这就是外部访问不了内部。


其他章节请看:

​前端学习 node 快速入门 系列​



作者:​​彭加李​

欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。