1.什么是模块?
模块化用于解决引入多个js文件时的命名冲突和文件依赖问题。
当我们在多个 JavaScript 文件之间进行通讯时,我们可能会把一个变量挂到 window 上,变成一个全局的变量。当项目变得越来越复杂,这些全局变量也会变得越来越多,很容易出现命名冲突的情况。
为了解决这个问题, 我们把每一个.js
文件都视为一个 模块,模块内部有自己的作用域,不会影响到全局。
2.浏览器开发中的模块
在浏览器开发中为了避免命名冲突, 方便维护等等
我们采用类或者立即执行函数的方式来封装JS代码, 来避免命名冲突和提升代码的维护性
其实这里的一个类或者一个立即执行函数就是浏览器开发中一个模块
let obj = {
模块中的业务逻辑代码
};
;(function(){
模块中的业务逻辑代码
window.xxx = xxx;
})();
存在的问题:
没有标准没有规范
在多人开发中, 有的人可能用类来定义模块, 有的人可能用立即执行的函数来定义模块, 这样整个项目就会显得比较杂乱;在定义模块的时候也没有规定哪些代码是需要暴露给外界使用的, 哪些内容是留给自己使用的, 是不需要暴露给外界的
3.NodeJS开发中的模块
NodeJS采用CommonJS规范实现了模块系统
-
CommonJS规范
CommonJS:一个同步模块规范。这种方式通过一个叫做require的方法,同步加载依赖,然后返导出API供其它模块使用,一个模块可以通过exports或者module.exports导出API。
CommonJS规范中,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,在一个文件中定义的变量,都是私有的,对其他文件是不可见的。简单的说: CommonJS规范规定了如何定义一个模块, 如何暴露(导出)模块中的变量函数, 以及如何使用定义好的模块
- 在CommonJS规范中一个文件就是一个模块
- 在CommonJS规范中每个文件中的变量函数都是私有的,对其他文件不可见的
- 在CommonJS规范中每个文件中的变量函数必须通过
exports
暴露(导出)之后其它文件才可以使用 - 在CommonJS规范中想要使用其它文件暴露的变量函数必须通过
require()
导入模块才可以使用
module.exports
module对象的exports属性是对外的接口。加载某个模块,其实是加载该模块的module.exports属性值。
//06-a.js
let name = "lyz";
function sum(a, b) {
return a +b;
}
module.exports.str= name;
module.expotrs.fn = sum;
// 将name和sum暴露给外界
exports变量
为了方便,Node为每个模块提供一个exports变量,指向module.exports。
这等同在每个模块头部,有一行这样的命令:let exports = module.exports;
造成的结果是,在对外输出模块接口时,可以直接向exports对象添加方法。
//06-a.js
let name = "lyz";
function sum(a, b) {
return a +b;
}
exports.str = name;
exports.fn = sum;
【注意: 不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。】
//不要这样! 这种写法就导致exports不再指向module.exports了。
exports = function(x) {console.log(x)};
下面的这种写法也是无效的:
由于module.exports被重新赋值了,因此fn函数是无法对外输出的。
exports.fn = function(a + b) {
return a + b;
};
module.exports = 'Hello world';
moudle.exports可以通过对象的方式导出多个成员
let name = "lyz";
function sum(a, b){
return a + b;
}
// 通过module.exports对象导出
module.exports = {
str : name,
fn : sum
};
【注意: 千万千万不要通过exports = {}的方式导出多个成员】
和前面的原理一样, 这样等于给exports赋值, 就会破坏exports和moudle.exports之间的关系
所以无论你 exports 中的成员是什么都没用, 因为moudle.exports中没有任何东西, 拿到的只会是空对象
let name = "lyz";
function sum(a, b){
return a + b;
}
exports = {
str: name,
fn: sum
}
let aMoudle = require("./06-a");
console.log(aMoudle); // {}
exports和module.exports区别:
exports只能通过 exports.xxx方式导出数据, 不能直接赋值
module.exports既可以通过module.exports.xxx方式导出数据, 也可以直接赋值
切记: 无论哪种方式都不要直接赋值
如果实在分不清楚 exports 和 module.exports
忘记 exports
而只使用 module.exports
module.exports.xxx = xxx
moudle.exports = {}
global导出模块中的数据(不推荐):
//06-a.js
let name = "lyz";
function sum(a, b) {
return a +b;
}
global.str = name;
global.fn = sum;
由于str和fn是放到了global全局对象上, 所以在用的时候就可以直接使用不用通过global.变量或函数名
let aMoudle = require("./06-b");
console.log(str); // lyz
let res = fn(10, 20);
console.log(res); // 30
require函数
Node使用CommonJS模块规范,内置的require命令用于加载模块文件。
require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
//06-a.js
let name = "lyz";
function sum(a, b) {
return a +b;
}
exports.str = name;
exports.fn = sum;
运行下面的命令,可以输出exports对象。
// 06-b.js
let aMoudle = require("./06-a");
console.log(aMoudle); // { str: 'lyz', fn: [Function: sum] }
我们需要暴露给外界的内容都在aMoudle对象中, 如果我们要拿到这个对象的属性只需通过aMoudle.属性名称
// 06-b.js
let aMoudle = require("./06-a");
console.log(aMoudle.str); // lyz
let res = aMoudle.fn(10, 20);
console.log(res); // 30
注意点:
无论通过哪种方式导出, 使用时都需要先导入(require)才能使用
通过global.xxx方式导出不符合CommonJS规范, 不推荐使用
加载规则
require导入模块时可以不添加导入模块的类型
如果没有指定导入模块的类型, 那么会依次查找.js .json .node
文件, 如果这三种文件都没有找到就会报错
无论是三种类型中的哪一种, 导入之后都会转换成JS对象返回给我们
let aMoudle = require("./06-a");
// 等同于以下代码
// 先找.js文件
let aMoudle = require("./06-a.js");
// 如果没有找到.js文件, 会继续再找.json文件
let aMoudle = require("./06-a.json");
// 如果.js文件和.json文件都没有找到, 最后会去找.node文件
let aMoudle = require("./06-a.node");
导入自定义模块时必须指定路径
require可以导入"自定义模块(文件模块)"、"系统模块(核心模块)"、"第三方模块"
导入"自定义模块"模块时前面必须加上路径, 否则会报错
// 06-b.js
let aMoudle = require("./06-a");
let aMoudle = require("06-a"); // 报错
导入"系统模块"和"第三方模块"是不用添加路径
如果是"系统模块"直接到环境变量配置的路径中查找
如果是"第三方模块"会按照module.paths
数组中的路径依次查找
模块的加载机制
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。
也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
下面是一个模块文件lib.js:
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。
然后,加载上面的模块。
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到counter了。
网友评论