前言
现在我们开发都实现模块化开发,引入一个模块,即引入一个可以全局使用的文件,在项目编写的过程中,我们不可能把所有文件都写到一个文件里面,所以将文件拆分开来
什么是模块化
- 将代码或文件按一定的规则进行封装成一个模块,模块与模块之间相互独立
- 每一个模块内部的数据和方法都是私有的,只允许暴露出想要外部可以使用的数据才可以使用
解决了啥问题
- 提高了代码的复用性,更好维护
- 可以实现按需加载,每个模块都独立
- 避免了空间相互污染,命名冲突,形成隔离
常用的有
common.js
使用require()函数导入其他模块,通过设置Export对象的属性或者完全替换module.exports对象来导出公共的API
导出模块
Node定义了一个全局的exports对象{},这个对象始终会有定义,如果一个模块要导出多个值,可以直接把这些值设置为exports对象的属性
// a.js
console.log("这是a.js文件");
const sum = (a, b) => {
return a + b;
};
const name = "你捉不到的this";
// 利用exports对象导出
exports.sumFun = sum; // 导出sum函数
exports.userName = name; // 导出name
exports.toFixedNumber = (number) => {
return number.toFixed(2);
};
module.exports 的默认值和exports引用的是同一个对象。即 module.exports == exports
所以,在导出的时候,可以直接导出一个对象,而不是用exports属性的方式一个个导出
const sum = (a, b) => {
return a + b;
};
const name = "你捉不到的this";
module.exports = {
sum, // 导出sum 函数
userName: name // 导出name, 并将其重命名为userName
}
导入模块
利用require导入导出的文件模块, 参数是其他要导入模块的名称,返回值是该导入模块的导出值
const FileA = require('./a'); // 可以将整个文件导入
// FileA 是一个对象,里面包含了导出的属性
FileA.sum // function
FileA.userName // '你捉不到的this'
// 也可以通过解构赋值的方式,只导入打算使用的特定属性
const { userName } = require('./a');
const { sum: sumFun } = require('./a'); // 也可在重命名
导出的是一个对象值的拷贝
一旦输出一个值,模块内部的变化就影响不到这个值了,因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成
// a.js
let name = '你捉不到的this';
let changeName = () => {name = 'lalala'}
module.exports = {
name,
changeName,
}
// index.js
const fileA = require("./a");
console.log(fileA.name); // '你捉不到的this'
fileA.changeNum();
// name 并没有变成lalala
console.log(fileA.name) // '你捉不到的this'
注意: 这里是一个值的浅拷贝。如果导出的是一个对象。还是会修改的
// a.js
let name = {age: '你捉不到的this'};
let changeName = () => {name.age = 'lalala'}
module.exports = {
name,
changeName,
}
// index.js
const fileA = require("./a");
console.log(fileA.name); // {age: '你捉不到的this'}
fileA.changeNum();
// name.age 变成lalala
console.log(fileA.name) // {age: 'lalala'}
导入的时候是同步加载
(require的内容会阻塞后续代码的执行,只有加载完成才会继续执行)
从导入的文件依次同步执行代码, 比如依次导入a.js, b.js
// a.js
console.log('a.ja')
// b.js
console.log('a.ja')
// index.js
const fileA = require("./a"); // 这里fileA是{}
console.log('1---->')
const fileB = require("./b"); // {} // 动态加载
console.log('index.js')
// 最终会先输出 a.js, 然后输出'1--->',然后输出b.js, 最后才输出index.js
运行时候加载(动态导入)
只有等代码运行的时候才能确定导入。不能进行tree shaking,所以可以在运行的代码中require
先整体加载a.js文件,加载里面的所有方法,生成一个对象_a,然后从这个对象上面去读取两个方法,只有在运行时候才能得到这个对象,所以没办法在编译的时候做静态优化
ES6模块
ES6 为JavaScript添加了import 和export 关键字
在浏览器中支持度不同,所以,es6语法会被babel 转换为common,js语法,但是因为为浏览器环境中并没有 module、 exports、 require 等环境变量。所以common.js语法在浏览器中不被允许,还需要we b pa c k进行打包,webpack 通过模拟这些变量进行打包执行
ES6导出
只需要在需要导出的声明前面加上export关键字就可以
export const name = '九思'
export const changeName = () => {}
或者只用一个export 语句申明真正要导出的值
const name = '九思'
const changeName = () => {}
const age = '18'
export {
name,
changeName,
age as userAge, // 导出标识符重命名
}
注意这里的花括号实际上不会定义对象字面量,这个语法只是要求在一对花括号中给出一个逗号分隔的标识符列表
如果要导出一个值,可以使用默认导出,使用默认导出可以导出任意表达式,包括匿名函数和匿名类表达式,或者导出对象字面量
每一个模块都只能设置一个默认的导出值
const changeName = () => {}
export default changeName;
es6模块输出的是值的引用
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import 加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块
export let name = '你捉不到的this';
export let changeName = () => {name = 'lalala'}
// index.js
const {name, changeName } = require("./a");
console.log(name); // '你捉不到的this'
changeNum();
// name 变成lalala
console.log(name) // 'lalala'
ES6导入
使用import 关键字可以将导出的模块在另一个模块中访问,import的语句有两个部分,一个是要导入的标识符和标识符应当从哪个模块导入,import命令具有提升效果,会提升到整个模块的头部,首先执行。
import { name, changeName } from './a.js';
// 也可以在导入的时候重新命名
import { name as userName } from './a.js';
导入后无法在此模块定义另一个重名变量,并且也无法在import语句前使用标识符,也不能给导入的绑定重新赋值
import { name } from './a.js';
name = 'lalala' // 抛出错误
// 如果name 是一个对象。该变去name的属性是可以的
也可以将整个模块作为一个单一的对象导入,然后模块里面的导出都可以按照对象的属性访问
import * as modelTest from './a.js';
// modelTest.name
不管import 语句中把一个模块写多少次,该模块只会执行一次,导入模块的代码执行后,实例化的模块被保存在内存中,不管引用多少次都是使用相同的模块实例
import { name } from './a.js';
import { changeName } from './a.js';
模块中即导出了默认值,也导出了其他的时候,当引用的时候,默认值必须排在非默认值前面
// a.js
export const name = 'lalala';
export default function(a, b) {
return a + b
}
// index.js
import sum, { name } from './a.js'
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
export { sum, name as userName } from './a.js';
不可动态导入导出
它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。让他确定哪些可以导出导入
import 函数
因import和export 语句是在编译时候,所以提出了import()函数,来支持动态加载模块,返回一个promise 对象,它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块
console.log('la la la')
if (name === 'lalala') {
import('./a.js').then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
}
比如react 中用到的利用react.lazy 配合suspense, 或者路由懒加载
如有错误,还请指出,共同进步~
看到这里啦,点个赞吧~