本章主要介绍前端模块化,希望能通过本章来掌握模块化开发相关知识
CommonJS
用module.exports
定义当前模块对外输出的对象,用require
加载模块
// fs.js
let man = {
name: 'zhangsan',
phone: '123456',
sex: '1'
}
module.exports = man
// CommonJS模块
let { name, phone, sex } = require('fs');
// 等同于
let _fs = require('fs')
let name = _fs.name
let phone = _fs.phone
let sex = _fs.sex
上面代码的实质是整体加载fs
模块(即加载fs
的所有方法),生成一个对象(_fs
),然后再从这个对象上面读取 3 个变量。这种加载称为运行时加载,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”
ES6 模块
编译时加载主要又import,export两个命令组成 export可以输出变量,函数,类等
let phone = '123456'
export let name = 'zhangsan'
export {phone}
export {phone as ph}
import {name, phone, ph} from './file.js'
console.log('name', name, phone, ph)
-
export
导出:export导出一个接口export let a = 1 // 正确 let a = 1 export {a} // 正确 export 1 // 错误 let a = 1 export a // 错误
export可以出现在模块的任意位置,但必须是顶层,不可以是块级作用域内,因为是编译时运行
-
import
加载模块import {name, phone as ph2, ph} from './file.js' console.log('name', name, ph2, ph)
大括号里面的变量名,必须要和被倒入模块对外接口名称相同,如果想重新去一个变量名,可以使用
as
,import
后面的from
指定模块文件路径可以时绝对路径,也可以是相对路径,如果不带路径,要有配置文件,告诉js引擎模块位置。import
命令有变量提升的效果,会提升到头部执行,和export一样,在编译阶段执行,所以不可以放在代码块内。 使用两次或者多次import只会执行一次 -
整体加载 除了指定加载某个输出外,可以使用一个*指定一个对象,所有输出都加载这个对象上
import * as x from './file.js' console.log('xx', x.name)
因为时静态分析,所以不要给他赋值(x.name = 'a')
-
默认输出
export default
一个模块只能有一个默认输出,对应的import不需要用大括号,可以任意起名字// fs.js export default 1
import num from 'fs.js'
-
export 和import 复合
export { name, ph } from 'fs'; // 可以简单理解为 import { name, ph } from 'fs'; export { name, ph };
当结合成一句时,就不可以用name,ph等变量了,相当于转发了这两个接口 接口改名
export {ph as ph2} from 'fs'
接口的转发,相当于把fs中的接口全部进行了转发(除了fs中的default)
export * from 'fs'
-
import()
函数,支持动态加载模块场景:按需加载,条件加载,动态模块路径 import()加载成功后,使用
.then
或者async
await
可以使用结构获取输出接口,如果有default
可以直接获取if (true){ import('./file.js').then((module) => { console.log('---', module.phone) console.log('---', module.default) }) const a = await import('./file.js') console.log('aa', a.name) }
在浏览器中的使用
当浏览器遇到script标签,会停下来等到执行完毕在渲染,如果是外部标签,还会有下载的时间通过加入defer或者async来允许浏览器异步加载
defe
r:渲染完在执行,会按照页面出现顺序加载
async
:下载完就执行,不保证按照顺序
浏览器通过设置type="module"
让浏览器知道加载一个es6模块,相当于打开了defer,即渲染完在执行,按照页面出现顺序加载,如果在标签上在加上async,则变为下载完在执行,不保证顺序。
commonjs 和es6模块的异同
- CommonJs模块输出的是值的一个拷贝(对引用类型来说是浅拷贝),es6输出的是值的引用
- CommonJs模块是运行时加载,es6是编辑时输出接口
- CommonJs模块的require()是同步的,es6的import命令是异步的 第一个异同点:
// file.js
let a = 1
setTimeout(() => {
a = 2
console.log('222')
}, 1000);
module.exports = {
a
}
// commonjs.js
let a = require('./file.js')
console.log('aaa',a.a)
setTimeout(() => {
console.log('aaa2', a.a)
}, 3000);
// aaa 1
// 222
// aaa2 1
当commonjs文件加载以后,模块内部的变化影响不到输出,因为是一个原始类型会被缓存
// file2.js
export let a = 1
setTimeout(() => {
a = 2
console.log('---')
}, 1000);
// es6.js
import {a} from './file2.js'
console.log('aa',a)
setTimeout(() => {
console.log('aaa2',a)
}, 2000);
// aa 1
// ---
// aaa2 2
es6会动态引用值,不会缓存
Node.js的模块加载
CommonJs模块是node专用,于es6不兼容,可以通过,改后缀名为.mjs或者在项目的package.js文件中type="module"两种方法,如果还用使用CommonJs模块,可以改为.cjs后缀。 总结:.mjs:以es6模块加载;.cjs:以commonjs加载;.js以package.json文件中type类型加载(默认CommonJs)