说在前面
最早期的JavaScript并没有像现在一样承担着众多的“责任”,通常只需要在<script>
标签中加入部分Javascript代码就可以满足页面的交互需求。随着Javascript的逐渐成熟,他所承担的工作也越来越多,之前那种“没有章法”的写法导致了页面逻辑混乱,维护起来也是异常吃力,所以大家提出了模块化编程的解决方案,说白了,也就是将代码分割成不同的模块,便于复用、维护以及按需加载。
发展历程
CommonJS
模块化技术最早应用于服务器端编程,因为早期认为网页程序复杂度有限,但是在服务器端就不一样了,为了与操作系统和其他应用程序互动,如果没有模块化将举步维艰。node.js
的模块系统,就是参照CommonJS规范实现的。浏览器对于CommonJS 是不兼容的,node.js提供了四个环境变量:
- module 代表当前模块,该变量是一个对象
- exports module变量的属性,外界加载的内容,其实是module.exports的属性
- require 用于加载模块
- global 使用global定义的变量,可以被所有文件读取
新建A.js,B.js文件:
// A.js
var app = {
name:'app',
version:'1.0.0',
sayName:function(name) {
console.log(this.name);
}
}
module.exports = app;
// B.js
var app = require('./A.js')
console.log(app.name + ' ' + app.version)
当我们尝试将此代码在浏览器上运行时,新建index.html
:
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LEAFLET</title>
</head>
<body>
<div id="app">ap</div>
<script src="./A.js"></script>
<script src="./B.js"></script>
</body>
</html>
在浏览器中直接打开index.html
文件:
这也印证了我们之前所说的话,浏览器环境是不具备这些环境变量的。
AMD(Asynchronous Module Definition)
服务器端的模块化得到了不错的反馈,但是由于CommonJS是同步加载的,对于浏览器来说,加载的时间受限于网速,而等待的期间,浏览器也不会有任何的“反应”,所以**AMD(异步模块加载机制)**应运而生。
实现了AMD规范的主要有require.js
和curl.js
,这里不介绍用法,总而言之,他能实现加载第三方资源库时,浏览器不会失去响应。
ES6
ES6在语言标准的层面上,实现了模块功能,可以取代CommonJS, AMD规范,成为浏览器和服务器通用的模块解决方案。
相较于之前,使用的方法从require - module.exports
变成了 import - export
。
从MDN的介绍来看,import
只能在声明了type="module"
的script
标签中使用。
// A.js
var app = {
name:'app',
version:'1.0.0',
sayName:function(name) {
console.log(this.name);
}
}
export default app;
export var e = '123'
--------------------------------------------------------------------------------------------------
// B.js
import app,{e} from './A.js'
console.log(app.name)
console.log(e)
-------------------------------------------------------------------------------------------------
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LEAFLET</title>
</head>
<body>
<div id="app">ap</div>
<script src="./A.js" type="module"></script>
<script src="./B.js" type="module"></script>
</body>
</html>
值得注意的是,使用了type="module"
后,在浏览器中直接打开会报错跨域,这是因为file协议并不支持跨域请求,所以要在服务器中部署,然后通过http(s)来访问index.html文件。比如http://127.0.0.1:5500/index.html
以vue项目举例,为什么我们使用了这么多的import
,而且我们在index.html
里面也没有添加type="module"
项目也能正常执行呢?
如上图所示:原因就是作为构建工具的webpack
,webpack
作为一个强大的工具,他“天生”支持(经过webapck打包后)如下类型:
- ECMAScript 模块
- CommonJS 模块
- AMD 模块
- Assets
- WebAssembly 模块
运行时加载 vs 编译时加载
比如我们需要fs
模块里的一些功能,现在我们项目里就需要引入fs
模块,这个模块里有很多功能,比如stat,exists,readfile
等…
运行时加载
CommonJS 和 AMD都是运行时加载,怎么去理解呢?
let { stat, exists, readfile } = require('fs');
这种引入方法,实际上是预加载整个fs
模块,然后到了运行时才可以得到fs
对象,此时再从对象上读取需要的方法。
编译时加载(静态加载)
ES6就属于编译时加载,下面的代码,会从fs
模块加载3个方法,在编译时就可以确定。
import { stat, exists, readFile } from 'fs';
对比
编译时加载的特性会使被import的模块在加载时就被编译,这促使我们可以对工程代码进行静态分析,比如我们熟知的Element ui就支持基于ES modules的tree-shaking
,比如我们只用到了antd
中的少数几个组件,是可以不引入全部的组件的。我实现了一个demo去对比,按需引入Button
,发现体积差了整整2MB。
编译时加载(静态加载)的不足
上面介绍过,静态加载在编译时就会将内容都加载好。现在考虑一个场景,项目运行过程中按需加载某一个模块,项目正常运转不一定需要加载这个模块,但是一定条件下,又要加载这个模块。 实际场景:我们的系统有10个系统模块,我们本次登录系统只需要操作其中的3个模块,我们实际上是不需要将10个系统模块全部加载到内存中的。
比如下方的代码,当用户点击对应的超链接,才会加载超链接相关的资源。
<!DOCTYPE html>
<nav>
<a href="books.html" data-entry-module="books">Books</a>
<a href="movies.html" data-entry-module="movies">Movies</a>
<a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>Content will load here!</main>
<script>
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
e.preventDefault();
import(`./section-modules/${link.dataset.entryModule}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
});
}
</script>
当我们希望按照一定的条件或者按需加载模块的时候,动态import()
是非常有用的。而静态型的 import
是初始化加载依赖项的最优选择,使用静态 import
更容易从代码静态分析工具和 tree shaking 中受益。
说到最后
文章的最后,不得不再提一下webpack
,他对于动态导入也是十分支持的,篇幅问题,这里也不做展开讲,总的来说,webpack
提供了手动代码分割,动态导入的支持以及splitChunk
对公共代码进行分割,更多内容请参考webpack-import()。