首页 >> js开发 >> JavaScript谈谈node.js中的模块系统
JavaScript谈谈node.js中的模块系统
发布时间: 2021年1月13日 | 浏览:
| 分类:js开发
Node.js 的模块Node.js 的模块Node.js 的模块JavaScript 做为一门为网页添加交互功能的简单脚本语言问世,在诞生时并不包含模块系统,随着 JavaScript 解决问题越来越复杂,把所有代码写在一个文件内,用 function 区分功能单元已经不能支撑复杂应用开发了,ES6 带来了大部分高级语言都有的 class 和 module,方便开发者组织代码
import _ from 'lodash';
class Fun {}
export default Fun;
import _ from 'lodash';
class Fun {}
export default Fun;上面三行代码展示了一个模块系统最重要的两个要素 import 和 export
1. export用于规定模块的对外接口
2. import用于输入其他模块提供的功能exportimport而在 ES6 之前,社区出现了很多模块加载方案,最主要的有 CommonJS 和 AMD 两种,Node.js 诞生早于 ES6,模块系统使用的是类似 CommonJS 的实现,遵从几个原则1. 一个文件是一个模块,文件内的变量作用域都在模块内
2. 使用 module.exports 对象导出模块对外接口
3. 使用 require 引入其它模块module.exports
require circle.js
const { PI } = Math;
module.exports = function area(r) {
PI * r ** 2;
};
const { PI } = Math;
module.exports = function area(r) {
PI * r ** 2;
};上面代码就实现了 Node.js 的一个模块,模块没有依赖其它模块,导出了方法 area 计算圆的面积test.js
const area = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${area(4)}`);
const area = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${area(4)}`);模块依赖了 circle.js,使用其对外暴露的 area 方法,计算圆的面积module.exportsmodule.exportsmodule.exports模块对外暴露接口使用 module.exports,常见的有两种用法:为其添加属性或赋值到新对象test.js
// 添加属性
module.exports.prop1 = xxx;
module.exports.funA = xxx;
module.exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};
// 添加属性
module.exports.prop1 = xxx;
module.exports.funA = xxx;
module.exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};两种写法是等价的,使用时候没区别
const mod = require('./test.js');
console.log(mod.prop1);
console.log(mod.funA());
const mod = require('./test.js');
console.log(mod.prop1);
console.log(mod.funA());还有另外一种直接使用 exports 对象的方法,但是只能对其添加属性,不能赋值到新对象,后面会介绍原因exports
// 正确的写法:添加属性
exports.prop1 = xxx;
exports.funA = xxx;
exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};
// 正确的写法:添加属性
exports.prop1 = xxx;
exports.funA = xxx;
exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};require('id')require('id')require('id')模块类型
模块类型
require 用法比较简单,id 支持模块名和文件路径两种类型模块名
const fs = require('fs');
const _ = require('lodash');
const fs = require('fs');
const _ = require('lodash');示例中的 fs、lodash 都是模块名,fs 是 Node.js 内置的核心模块,lodash 是通过 npm 安装到 node_modules 下的第三方模块,如果出现重名,优先使用系统内置模块因为一个项目内可能会包含多个 node_modules 文件夹(Node.js 比较失败的设计),第三方模块查找过程会遵循就近原则逐层上溯(可以在程序中打印 module.paths 查看具体查找路径),直到根据 NODE_PATH 环境变量查找到文件系统根目录,具体过程可以参考官方文档官方文档此外,Node.js 还会搜索以下的全局目录列表:
$HOME/.node_modules
$HOME/.node_libraries
$PREFIX/lib/node
$HOME/.node_modules$HOME/.node_libraries$PREFIX/lib/node
其中 $HOME 是用户的主目录, $PREFIX 是 Node.js 里配置的 node_prefix。强烈建议将所有的依赖放在本地的 node_modules 目录,这样将会更快地加载,且更可靠文件路径文件路径模块还可以可以使用文件路径加载,这是项目内自定义模块的通用加载方式,路径可以省略拓展名,会按照 .js、.json、.node 顺序尝试
以 '/' 为前缀的模块是文件的绝对路径,按照系统路径查找模块
以 './' 为前缀的模块是相对于当前调用 require 方法的文件,不受后续模块在哪里被使用到影响
以 '/' 为前缀的模块是文件的绝对路径,按照系统路径查找模块以 './' 为前缀的模块是相对于当前调用 require 方法的文件,不受后续模块在哪里被使用到影响
单次加载 & 循环依赖单次加载 & 循环依赖模块在第一次加载后会被缓存到 Module._cache ,如果每次调用 require('foo') 都解析到同一文件,则返回相同的对象,同时多次调用 require(foo) 不会导致模块的代码被执行多次。 Node.js 根据实际的文件名缓存模块,因此从不同层级目录引用相同模块不会重复加载。Module._cache require('foo')require(foo)理解的模块单次加载机制方便我们理解模块循环依赖后的现象
a.js
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');b.js
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');main.js
console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);
console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);当 main.js 加载 a.js 时,a.js 又加载 b.js,此时,b.js 会尝试去加载 a.js为了防止无限的循环会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块,然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块因此示例的输出是
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true看不懂上面的过程也没关系,日常工作根本用不到,即使看懂了也不要在项目中使用循环依赖!工作原理
工作原理工作原理Node.js 每个文件都是一个模块,模块内的变量都是局部变量,不会污染全局变量,在执行模块代码之前,Node.js 会使用一个如下的函数封装器将模块封装
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
__filename:当前模块文件的绝对路径
__dirname:当前模块文件据所在目录的绝对路径
module:当前的模块实例
require:加载其它模块的方法,module.require 的快捷方式
exports:导出模块接口的对象,module.exports 的快捷方式
__filename:当前模块文件的绝对路径__dirname:当前模块文件据所在目录的绝对路径module:当前的模块实例require:加载其它模块的方法,module.require 的快捷方式exports:导出模块接口的对象,module.exports 的快捷方式
回头看看最开始的问题,为什么 exports 对象不支持赋值为其它对象?把上面函数添加一句 exports 对象来源就很简单了
const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});其它模块 require 到的肯定是模块的 module.exports 对象,如果吧 exports 对象赋值给其它对象,就和 module.exports 对象断开了连接,自然就没用了在 Node.js 中使用 ES Module在 Node.js 中使用 ES Module在 Node.js 中使用 ES Module随着 ES6 使用越来越广泛,Node.js 也支持了 ES6 Module,有几种方法babel 构建
babel 构建使用 babel 构建是在 v12 之前版本最简单、通用的方式,具体配置参考 @babel/preset-env@babel/preset-env.babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}原生支持原生支持在 v12 后可以使用原生方式支持 ES Module
开启 --experimental-modules
模块名修改为 .mjs (强烈不推荐使用)或者 package.json 中设置 "type": module
开启 --experimental-modules--experimental-modules模块名修改为 .mjs (强烈不推荐使用)或者 package.json 中设置 "type": module
.mjs
"type": module这样 Node.js 会把 js 文件都当做 ES Module 来处理,详情参考官方文档官方文档以上就是谈谈node.js中的模块系统的详细内容,关于node.js 模块的资料请关注其它相关文章!
import _ from 'lodash';
class Fun {}
export default Fun;
import _ from 'lodash';
class Fun {}
export default Fun;上面三行代码展示了一个模块系统最重要的两个要素 import 和 export
1. export用于规定模块的对外接口
2. import用于输入其他模块提供的功能exportimport而在 ES6 之前,社区出现了很多模块加载方案,最主要的有 CommonJS 和 AMD 两种,Node.js 诞生早于 ES6,模块系统使用的是类似 CommonJS 的实现,遵从几个原则1. 一个文件是一个模块,文件内的变量作用域都在模块内
2. 使用 module.exports 对象导出模块对外接口
3. 使用 require 引入其它模块module.exports
require circle.js
const { PI } = Math;
module.exports = function area(r) {
PI * r ** 2;
};
const { PI } = Math;
module.exports = function area(r) {
PI * r ** 2;
};上面代码就实现了 Node.js 的一个模块,模块没有依赖其它模块,导出了方法 area 计算圆的面积test.js
const area = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${area(4)}`);
const area = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${area(4)}`);模块依赖了 circle.js,使用其对外暴露的 area 方法,计算圆的面积module.exportsmodule.exportsmodule.exports模块对外暴露接口使用 module.exports,常见的有两种用法:为其添加属性或赋值到新对象test.js
// 添加属性
module.exports.prop1 = xxx;
module.exports.funA = xxx;
module.exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};
// 添加属性
module.exports.prop1 = xxx;
module.exports.funA = xxx;
module.exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};两种写法是等价的,使用时候没区别
const mod = require('./test.js');
console.log(mod.prop1);
console.log(mod.funA());
const mod = require('./test.js');
console.log(mod.prop1);
console.log(mod.funA());还有另外一种直接使用 exports 对象的方法,但是只能对其添加属性,不能赋值到新对象,后面会介绍原因exports
// 正确的写法:添加属性
exports.prop1 = xxx;
exports.funA = xxx;
exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};
// 正确的写法:添加属性
exports.prop1 = xxx;
exports.funA = xxx;
exports.funB = xxx;
// 赋值到全新对象
module.exports = {
prop1,
funA,
funB,
};require('id')require('id')require('id')模块类型
模块类型
require 用法比较简单,id 支持模块名和文件路径两种类型模块名
const fs = require('fs');
const _ = require('lodash');
const fs = require('fs');
const _ = require('lodash');示例中的 fs、lodash 都是模块名,fs 是 Node.js 内置的核心模块,lodash 是通过 npm 安装到 node_modules 下的第三方模块,如果出现重名,优先使用系统内置模块因为一个项目内可能会包含多个 node_modules 文件夹(Node.js 比较失败的设计),第三方模块查找过程会遵循就近原则逐层上溯(可以在程序中打印 module.paths 查看具体查找路径),直到根据 NODE_PATH 环境变量查找到文件系统根目录,具体过程可以参考官方文档官方文档此外,Node.js 还会搜索以下的全局目录列表:
$HOME/.node_modules
$HOME/.node_libraries
$PREFIX/lib/node
$HOME/.node_modules$HOME/.node_libraries$PREFIX/lib/node
其中 $HOME 是用户的主目录, $PREFIX 是 Node.js 里配置的 node_prefix。强烈建议将所有的依赖放在本地的 node_modules 目录,这样将会更快地加载,且更可靠文件路径文件路径模块还可以可以使用文件路径加载,这是项目内自定义模块的通用加载方式,路径可以省略拓展名,会按照 .js、.json、.node 顺序尝试
以 '/' 为前缀的模块是文件的绝对路径,按照系统路径查找模块
以 './' 为前缀的模块是相对于当前调用 require 方法的文件,不受后续模块在哪里被使用到影响
以 '/' 为前缀的模块是文件的绝对路径,按照系统路径查找模块以 './' 为前缀的模块是相对于当前调用 require 方法的文件,不受后续模块在哪里被使用到影响
单次加载 & 循环依赖单次加载 & 循环依赖模块在第一次加载后会被缓存到 Module._cache ,如果每次调用 require('foo') 都解析到同一文件,则返回相同的对象,同时多次调用 require(foo) 不会导致模块的代码被执行多次。 Node.js 根据实际的文件名缓存模块,因此从不同层级目录引用相同模块不会重复加载。Module._cache require('foo')require(foo)理解的模块单次加载机制方便我们理解模块循环依赖后的现象
a.js
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');b.js
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');main.js
console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);
console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);当 main.js 加载 a.js 时,a.js 又加载 b.js,此时,b.js 会尝试去加载 a.js为了防止无限的循环会返回一个 a.js 的 exports 对象的 未完成的副本 给 b.js 模块,然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块因此示例的输出是
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true
main 开始
a 开始
b 开始
在 b 中,a.done = false
b 结束
在 a 中,b.done = true
a 结束
在 main 中,a.done=true,b.done=true看不懂上面的过程也没关系,日常工作根本用不到,即使看懂了也不要在项目中使用循环依赖!工作原理
工作原理工作原理Node.js 每个文件都是一个模块,模块内的变量都是局部变量,不会污染全局变量,在执行模块代码之前,Node.js 会使用一个如下的函数封装器将模块封装
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
__filename:当前模块文件的绝对路径
__dirname:当前模块文件据所在目录的绝对路径
module:当前的模块实例
require:加载其它模块的方法,module.require 的快捷方式
exports:导出模块接口的对象,module.exports 的快捷方式
__filename:当前模块文件的绝对路径__dirname:当前模块文件据所在目录的绝对路径module:当前的模块实例require:加载其它模块的方法,module.require 的快捷方式exports:导出模块接口的对象,module.exports 的快捷方式
回头看看最开始的问题,为什么 exports 对象不支持赋值为其它对象?把上面函数添加一句 exports 对象来源就很简单了
const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});其它模块 require 到的肯定是模块的 module.exports 对象,如果吧 exports 对象赋值给其它对象,就和 module.exports 对象断开了连接,自然就没用了在 Node.js 中使用 ES Module在 Node.js 中使用 ES Module在 Node.js 中使用 ES Module随着 ES6 使用越来越广泛,Node.js 也支持了 ES6 Module,有几种方法babel 构建
babel 构建使用 babel 构建是在 v12 之前版本最简单、通用的方式,具体配置参考 @babel/preset-env@babel/preset-env.babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}原生支持原生支持在 v12 后可以使用原生方式支持 ES Module
开启 --experimental-modules
模块名修改为 .mjs (强烈不推荐使用)或者 package.json 中设置 "type": module
开启 --experimental-modules--experimental-modules模块名修改为 .mjs (强烈不推荐使用)或者 package.json 中设置 "type": module
.mjs
"type": module这样 Node.js 会把 js 文件都当做 ES Module 来处理,详情参考官方文档官方文档以上就是谈谈node.js中的模块系统的详细内容,关于node.js 模块的资料请关注其它相关文章!
相关文章:
- js解决vue watch数据的方法被调用了两次的问题js大全
- js解决VueCil代理本地proxytable无效报错404的问题js大全
- js解决Element中el-date-picker组件不回填的情况js大全
- JavaScriptnuxt.js添加环境变量,区分项目打包环境操作
- JavaScriptNuxt.js nuxt-link与router-link的区别说明
- jsVue路由权限控制解析js大全
- js详解Vue中的watch和computedjs大全
- js你不知道的SpringBoot与Vue部署解决方案js大全
- jsvue 避免变量赋值后双向绑定的操作js大全
- js解决vue 使用axios.all()方法发起多个请求控制台报错的问题js大全