Node是CommonJS在服务器端一个具有代表性的实现;
webpack打包工具具备对CommonJS的支持和转换;
- exports和module.exports可以负责对模块中的内容进行导出;
- require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
exports - require
//导出.js
const UTIL_NAME = "util-name"
function formatCount(){
return '200'
}
function formatDate(){
return '2022-10-10'
}
exports.UTIL_NAME = UTIL_NAME
exports.formatCount = formatCount
exports.formatDate = formatDate
//导入.js
const util = require('./util.js')
// 或者使用结构
const {
UTIL_NAME
formatCount,
formatDate
} = require('./util.js')
console.log(util.UTIL_NAME)
console.log(util.formatCount())
console.log(util.formatDate())
exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出
exprots导出的原理:
exports导出一个对象,require导入这个对象,它们指向的是同一个对象,在一个文件中对该对象进行修改,另一个文件中也会受到影响。
验证:
//导出.js
let name = 'bar'
exports.name = name
//新增代码
setTimeout(() => {
console.log(exports.name) //会被修改
},4000)
//导入.js
const bar = require('./bar.js')
console.log(bar.name)
//新增代码
setTimeout(() => {
bar.name = 'kobe' //在引用的地方修改值,看源文件会不会被修改
},2000)
module.exports导出
上面的exports.name = name 导出方式用的很少
使用 module.exports.name = name 也可以实现导出,这里和上面的导出并没有什么区别
let name = 'bar'
let age = 18
module.exports.name = name
module.exports.age = age
module.exports原理:
module是一个对象,exports也是一个对象。在node中每一个模块都是Module的一个实例,那么这个实例上会有一个属性exports,即module.exports
,并且module.exports
的属性值是个对象,指向exports对象。
module.exports和exports有什么关系或者区别呢?
CommonJS中是没有module.exports的概念的;
但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module;
所以在Node中真正用于导出的其实根本不是exports,而是module.exports;
因为module才是导出的真正实现者;
但是,为什么exports也可以导出呢?
这是因为module对象的exports属性是exports对象的一个引用;
也就是说 module.exports = exports = 一个对象;
exports === module.exports // true
开发中最常见的写法:
module.exports = {
}
我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。
并且在引入文件时,我们可以省略文件名后缀,为什么?require的查找规则是怎么样的呢?
导入格式如下:require(X)
直接返回核心模块,并且停止查找
情况二:X是以./ 或…/ 或/(根目录)开头的
第一步:将X当做一个文件在对应的目录下查找;
1.如果有后缀名,按照后缀名的格式查找对应的文件
2.如果没有后缀名,会按照如下顺序:
第二步:没有找到对应的文件,将X作为一个目录
查找目录下面的index文件
查找X/index.js文件
查找X/index.json文件
查找X/index.node文件
如果没有找到,那么报错:not found
情况三:直接是一个X(没有路径),并且X不是一个核心模块
自定义模块,先在当前目录的node_modules
里找这个模块,如果没有,它会往上一级目录查找,查找上一级的node_modules
,依次往上,直到根目录下都没有, 就抛出错误。
LinDaiDaideMBP:commonJS lindaidai$ node test.js
[
'/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules',
'/Users/lindaidai/codes/test/CommonJS和ES6/node_modules',
'/Users/lindaidai/codes/test/node_modules',
'/Users/lindaidai/codes/node_modules',
'/Users/lindaidai/node_modules',
'/Users/node_modules',
'/node_modules'
]
模块的加载过程
结论一:模块在被第一次引入时,模块中的js代码会被运行一次
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次
结论三:如果有循环引入,那么加载顺序是什么?
let exports = module.exports
{
id: '', //模块名,唯一
exports: { //模块输出的各个接口
...
},
filename: '/absolute/path/to/entry.js', // 当前模块的绝对路径
loaded: true, //模块的脚本是否执行完毕
children: [], // 被该模块引用的模块
parent: '', // 第一个引用该模块的模块
paths: [ // 模块的搜索路径
'/absolute/path/to/node_modules',
'/absolute/path/node_modules',
'/absolute/node_modules',
'/node_modules'
]
}
以后用到这个模块时,就会到对象的exports
属性中取值。即使再次执行require
命令,也不会再次执行该模块,而是到缓存中取值。
CommonJS模块
是加载时执行,即脚本代码在require
时就全部执行。一旦出现某个模块被“循环加载/引用”,就只输出已经执行的部分,没有执行的部分不会输出。
ES6模块与CommonJS有本质区别,ES6模块对导出变量,方法,对象是动态引用,JS 引擎对脚本静态分析的时候,遇到模块加载命令
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
ES Module和CommonJS的模块化有一些不同之处:
ES Module模块采用export和import关键字来实现模块化:
export负责将模块内的内容导出;
import负责从其他模块导入内容;
采用ES Module将自动采用严格模式:use strict
虽然大部分主流浏览器支持 ES6 模块,但是和引入普通 JS 的方式略有不同,需要在对应 script 标签中将属性 type 值设置为“module”才能被正确地解析为 ES6 模块;
并且,我们不允许将该html的文件直接在浏览器中打开: 如果你通过本地加载 HTML 文件 (比如一个 file://
路径的文件), 你将会遇到 CORS 错误,因为 JavaScript 模块安全性需要。你需要通过一个服务器来测试。一般使用 Live Server 插件。
导入与导出
//导出.js
const name = 'zs'
const age = 18
export {
name,
age
}
export {
name as fName,
age as fAge,
foo as fFoo
}
export const name = "pjy";
//导入.js
import { name, age } from './foo.js' //注意这里必须要加文件后缀名
import {name as fName,age as fAge} from "./foo.js"
import * as foo from "./foo.js"
//在使用时
console.log(foo.name);
console.log(foo.age);
在一个模块中,只能有一个默认导出(default export)。
//foo.js
const foo = "foo value";
export default foo;
//main.js
import pjy from "./foo.js"
console.log(pjy);//foo value
通过import加载一个模块,是不可以在其放到逻辑代码中的
但是某些情况下,我们确确实实希望动态的来加载某一个模块:
这个时候我们需要使用 import() 函数来动态加载;
import函数返回一个Promise,可以通过then获取结果;
let flag = true;
if(flag){
import('./moudles/aaa.js').then(res => {
console.log(res.name, res.age)
})
} else {
import('./moudles/aaa.js').then(aaa => {
//...
})
}
ES6 模块输出的是值的引用,export default 也是对值的引用
// a.js
export default {
value: 10,
}
// b.js
import defA from './a';
export default function logA() {
console.log(defA);
}
// c.js
import defA from './a';
import logA from './b';
defA.value = 20;
logA();
// {value: 20}
import的单个值是只读的
输入的模块变量是不可重新赋值的,它只是个可读引用,不过却可以改写属性,例子在上面已经写了
// a.js
export let a = 10;
// b.js
import { a } from './a';
a = 20;
// ReferenceError: a is not defined
在经过webpack打包后在浏览器中将报出引用错误。
换一种方式看看能不能修改a的值,在c.js中用import as来获取a.js输出的值,再修改a的值
// c.js
import * as A from './a';
A.a = 20;
// TypeError: Cannot set property a of #