关键字:script,ESModule,commonjs
前端发展至今已有十来年,充当的角色也变化很大,从web1.0时代简单的点击跳转到web2.0时代的万物互联;前端变得越来越不可或缺。
随着业务越来越大,不可能把所有代码写在一个地方了,毕竟变量名不注意就取重了😅,这个时候就必须模块化,把每个功能隔离开来,变量互不影响,一个模块一个模块把业务搭建起来。
什么是模块化
将一个大的项目分成有限几块,每个小块相互独立又相互依存,每个小块对外暴露自己的一些功能供其他小块使用
举个例子:网购桌子商家不会给你发完整的桌子,而是把桌子拆成桌子腿,桌板等小块,桌子腿和桌子腿是独立的,但会暴露顶部给螺丝拧紧,这就是模块化
模块化解决了什么问题
- 全局作用域:隔绝了命名冲突和作用域污染问题
- 自动化测试:每个模块都可以单独测试,无需从头到尾
- 性能优化:模块可以重复使用,减少打包体积
模块化1——Script
普通函数引用
通过script
引入不同的文件来实现变量的引用,如以下代码,虽然实现了test模块中的代码,但是却没有隔绝作用域,造成了污染;尽管可以通过不同变量名或者多套几层的方式避免污染,但本质上是一样的,无法隔绝
// test.js
function getName(){
name = '张三'
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模块化</title>
</head>
<body>
<script src="test.js"></script>
<script>
var name = '李四'
getName()
console.log(name) //张三
</script>
</body>
</html>
// 多套几层
function getName(){
var my_test_name = {
name = '张三'
}
}
立即执行函数引用
通过闭包的方式将变量控制在name函数作用域中,再通过window挂载将getName函数暴漏出来。
这种方式对于一些简单的项目是可以的,但是当业务复杂起来,一大连串的立即执行函数是非常难理解的
// iffe.js
(function name(){
var age = 12
window.getName = function getName(){
var name = '张三'
}
})()
console.log(age) //error
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模块化</title>
</head>
<body>
<script src="iffe.js"></script>
<script>
var name = '李四'
getName()
console.log(name) //李四
</script>
</body>
</html>
模块化2——Commonjs
****通过<script>
标签引入代码会显得杂乱无章、人们只能通过命名空间等方式人为限制约束代码、以达到安全的目的。
经历十多年的发展、社区为JavaScript制定了相应的规范、其中CommonJS
规范就是最重要的里程碑
规范
commonjs
的规范很简单,就三点
- 模块定义
- 在
Commonjs
中每一个 js 文件都是一个单独的模块,我们可以称之为module
- 模块引用
- 在
Commonjs
规范中存在require()方法、参数为模块标识、以此引入一个模块到当前上下文中。
- 模块标识
- 标识就是传递给require()方法的参数、必须符合小驼峰命名的字符串、或者以 .、 ..开头的相对路径或者绝对路径。
导出与引入
举个例子
var a = 1;
setTimeout(() => {
console.log("异步:", a); // 异步: 1
}, 2000);
exports.a = a;
const AA = require('./module/a.js')
AA.a = 2
console.log(AA) // { a: 2 }
上面的例子我们可以得出几个结论
- 通过exports 导出,exports是一个对象
- 通过require 导入,导入的是一个对象
- 导入之后修改对象属性值,不会影响 模块内的 值
动态同步加载
require 可以在任意的上下文,动态加载模块,会阻塞进程
let index = 1
while(index <2) {
console.log("开始")
const a = require('./module/a')
console.log("index:",a) // {a:1}
console.log("结束")
index++
}
// 开始
// index: { a: 1 }
// 结束
// 异步: 1
模块化3——ESModule
ESModule 规范是基于文件的,每个文件都是一个独立的模块。在浏览器中,可以使用<script type="module">
标签来加载 ESModule 模块。在 Node.js 中,可以使用 import 关键字来加载 ESModule 模块。
在nodejs环境中必须在package.json 中设置type:"module"
,node版本最好在14以上
规范
- ESM 模块输出的是值的引用
- 通过export 或者 export default 导出
- ESM 模块是编译时输出接口
- 不能再块级作用域中运行,因为不是运行时加载的
- ESM 支持异步加载
- 不阻塞进程
我们根据以上规范,举个例子证明
导出与引用
import {obj} from './esmodule/a.js';
obj.name = 'jack'
console.log(obj)
export const obj = {
name:'linda'
}
setTimeout(() => {
console.log("aaa:",obj.name) // jack
}, 2000);
通过import 引入值后修改,原有的值也修改,说明是同一个值,是值的引用
let aa = 1
if(a) {
import {obj} from './esmodule/a.js'; //error
obj.name = 'jack'
console.log(obj)
}
异步加载
import
返回的是一个promise
,返回then
的是一个异步函数
const obj = 111
console.log(import('./esmodule/b.js').then(res => {res.asyncFun()}))
console.log(obj)
// Promise { <pending> }
// 111
// async
const asyncFun = () =>{
console.log("async")
}
export {
asyncFun
}
总结
除了上面这三种模块化,还有AMD,CMD;但基本没用到过,这里不做阐述,主要是commonjs和esmodule,这里总结一下两种模块化方法的特点
commonjs
是对值的拷贝,不是用一个对象了,esmodule
是引用还是同一个对象require()
是同步导入,阻塞进程;import()
是异步导入,不阻塞进程
下篇会详细阐述commonjs的导入导出机制