1. 无模块化
script标签引入js文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:
<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="main.js"></script>
<script src="other1.js"></script>
<script src="other2.js"></script>
<script src="other3.js"></script>
即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。缺点很明显:
- 污染全局作用域
- 维护成本高
- 依赖关系不明显
2. CommonJS规范
在node中,默认支持的模块化规范叫做CommonJS,它使得 JavaScript 代码可以更好地组织、复用和维护。
关于模块:
- 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
- 在模块中使用global定义全局变量,不需要导出,在别的文件中可以访问到。
- 每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外接口。
- 通过require加载模块,读取并执行一个js文件,然后返回该模块的exports对象。
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
一点说明:
exports 是对 module.exports 的引用。比如我们可以认为在一个模块的顶部有这句代码: exports = module.exports所以,我们不能直接给exports赋值,比如number、function等。
var exports = module.exports 类似于 var obj1 = obj 这里exports只是module.exports的一个引用,所以这里直接给exports直接赋值,并不会影响module.exports,只是指向了新对象而已。 始终要记住:底层实现里最后返回的是module.exports对象重新赋值,解除引用:
此外,这里要注意:一旦给exports重新赋值,便会失去和module.exports的关联,指向新对象,且后期无法使用。
一点优点:解决了依赖、全局变量污染的问题
一点缺点: CommonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS不适合浏览器端模块加载,更合理的方案是使用异步加载,比如AMD规范。
1.module对象
node内部提供一个Module构建函数。所有的模块其实都是Module的实例。
每个模块内部都有一个Module对象,代表当前模块。它有以下属性:
- module.id,模块的识别符,通常是带有绝对路径的模块文件名;
- module.filename,模块的文件名,带有绝对路径;
- module.loaded,返回应boolean值,表示模块是否已经完成加载;
- module.parent,返回一个对象,表示调用该模块的模块;
- module.children,返回一个数组,表示该模块内用到的其他模块;
- module.exports,表示模块对外输出的值。
2. CommonJS 模块的导出
// math.js
const sum = (a, b) => a + b;
const multiply = (a, b) => a * b;
module.exports = { sum, multiply };//暴露这两个函数
const a = 10
const b = 20
const obj = { name: "孙悟空" }
module.exports.a = a
module.exports.b = b
// const {a, b} = require("./m.js")
// math_functions.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
const PI = 3.14159;
module.exports = { add, subtract, PI };
const getName = () => {
return 'Jim';
};
const getLocation = () => {
return 'Munich';
};
const dateOfBirth = '12.01.1982';
exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;
class User {
constructor(name, age, email) {
this.name = name;
this.age = age;
this.email = email;
}
getUserStats() {
return `
Name: ${this.name}
Age: ${this.age}
Email: ${this.email}
`;
}
}
module.exports = User;
3.其他module.exports
和exports
的常见问题
module.exports
和exports
在Node.js
中有什么区别?
module.export
s和exports
都用于从模块中导出值。exports
本质上是对module.exports
的引用,所以您可以任选其一使用。然而,通常建议使用module.exports
以避免潜在问题。
- 我可以在同一个模块文件中同时使用
module.exports
和exports
吗?是的,您可以在同一个模块中同时使用两者。但在这样做时要小心,最好保持一致性,坚持使用一种惯例。
- 在使用
module.exports
和exports
时有哪些常见问题?一个常见的是直接重新赋值
exports
,这可能导致意想不到的结果。最好使用module.exports
以获得更好的一致性,并避免潜在的问题。
- 我可以在浏览器中使用
module.exports
和exports
吗,还是它们只适用于Node.js
?
module.exports
和exports
是特定于Node.js
的,在浏览器的JavaScript
中不可用。在浏览器中,通常使用其他机制,如ES6
模块进行导出和导入。
4. CommonJS 模块的导入
2.1使用 require 函数导入文件模块(用户自定义)
- 使用require(“模块的路径”)函数来引入模块
- 模块名要以./ 或 …/开头
- 扩展名可以省略(除了扩展名是.cjs)
- 在 JavaScript 中,引入模块时可以省略文件扩展名。当引入的模块是 JavaScript 文件(.js)、JSON 文件(.json)和node(.node)时,可以不写扩展名,Node.js 会根据需要自动解析文件类型并加载对应的模块。
- * 如果没有.js 后缀的同名文件它会寻找 .json后缀的。(如果两个后缀名都有,则优先导入后缀名为.js的)
- .js > .json >.node
2.2使用 require
函数导入核心模块(Node.js 内置的模块)
- 直接写核心模块的名字即可
- 也可以在核心模块前添加 node:
// main.js
const math = require('./math.js');
console.log(math.sum(2, 3)); // 输出 5
console.log(math.multiply(2, 3)); // 输出 6
2.3文件夹作为模块
当我们使用一个文件夹作为模块时,文件夹中必须有一个模块作为主文件。如果文件夹中含有package.json文件且文件中设置了main属性,则main属性指定的文件会成为主文件,导入模块时就会导入该文件(主文件)。如果没有package.json,则node会按照index.js、index.node的顺序寻找主文件。
//01module.js
// reauire是用来加载模块的,它会把引进来的东西作为返回值返回
// const result = require('./外部')
const result = require('./文件')//它会执行引入模块内的代码但是它的返回值就是模块内导出(暴露/exports/module.exports)的东西,如果模块未导出(暴露)任何东西,那么它就是空
console.log(result)
被导入的文件夹
index.js
console.log('我是入口')
require('./a')
require('./b')
require('./c')
require('./d')
// console.log('ff')`
a.js
console.log('我是a')
b.js
console.log('我是b')
c.js
console.log('我是c')
d.js
console.log('我是d')
注意此时打印result内容为空,这是因为虽然引入了,但是被引入发的模块没有暴露东西(在模块内部任何变量或其他对象都是私有的,需要手动暴露)
用module.exports暴露后
//index.js
console.log('我是入口')
require('./a')
require('./b')
require('./c')
require('./d')
// console.log('ff')`
const a = 10
const b = 20
module.exports.a = a
module.exports.b = b
再次执行,result对象内为我们暴露的东西
2.4模块的原理
(function(exports, require,module,_filename,__dirname)
let a= 10
let b=20
});
用module.exports导出和用exports导出有什么异同?
答:他俩都是对象,但是exports是module.exports的引用,所以exports只能添加属性(exports直接赋值会改变exports的引用,而不会改变module.exports的引用,从而起不到导出的效果),不能直接赋值,而module.exports可以直接赋值。
3. AMD规范
AMD规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
AMD标准中,定义了下面三个API:
require([module], callback)
define(id, [depends], callback)
require.config()
即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。
先到require.js官网下载最新版本,然后引入到页面,如下:
<script data-main="./alert" src="./require.js"></script>
data-main属性不能省略。
以上分别是定义模块,引用模块,运行在浏览器弹出提示信息。
引用模块的时候,我们将模块名放在[]
中作为reqiure()
的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]
中作为define()
的第一参数。
在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。
一点优点:适合在浏览器环境中异步加载模块、并行加载多个模块
一点缺点:不能按需加载、开发成本大
4. CMD
AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
很明显,CMD是按需加载,就近原则。
5. ES6模块化
在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。
es6在导出的时候有一个默认导出,export default
,使用它导出后,在import的时候,不需要加上{},模块名字可以随意起。该名字实际上就是个对象,包含导出模块里面的函数或者变量。
但是一个模块只能有一个export default
。
6. CommonJs和ES6区别
(1) CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
-
S6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。(2) CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
-
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
-
编译时加载: ES6 模块不是对象,而是通过
export
命令显式指定输出的代码,import
时采用静态命令的形式。即在import
时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
(3)
- exports只能使用语法来向外暴露内部变量:如exportts.xxx =xxx;
- module.exports既可以通过语法,也可以直接赋值一个对象
小节:
1、exports为modules.exports的一个引用 2、最后node底层模块导出的是module.exports 3、底层代码 var module = { exports:{...} } 4、exports.name等价于module.exports.name,但node为了方便书写,使用module.exports导出单个成员,本质为将该子对象重新赋值 5、所以只要给exports赋值,便丢失了module.exports的引用关系,后期便不可用