JS模块化
JS模块化
在ES6模块化规范出来之前,有AMD,CMD,CommonJS等模块化规范。AMD和CommonJS都是运行时加载,ES6是编译时加载也叫静态加载。
CommonJS
nodejs环境下(服务器端)的JS模块化规范
- 暴露模块:
module.exports = value
exports.xxx = value
暴露的本质是暴露的exports对象。
exports是module.exports的一个引用,exports指向的是module.exports
- module.exports和exports的区别:
exports.xxx就是对exports对象添加属性,可以添加多个属性。
module.exports在一个js文件里,只能写一次,否则后面的会覆盖前面的。
module.exports可以赋值一个对象,而exports却不可以。
- 引入模块:require
一require就会执行模块里的代码。
每个模块都是单例的,无论加载(require)多少次,都只会执行一遍,以后加载的都是它的缓存。
CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
browserify
https://browserify.org/
在浏览器端使用commonJS规范。
浏览器没有定义require方法,browserify可以让浏览器像node一样使用require方法。
npm install browserify -g
npm install browserify --save-dev
browserify main.js -o bundle.js //打包
browserify会解析require()方法的AST,遍历整个项目的依赖图。
AMD
浏览器端的JS模块化规范。
AMD:Asynchronous module definition,异步的模块定义
需要引入require.js。
https://requirejs.org/
define( id, dependencies, factory);
- 第一个参数,id 为字符串类型,为可选参数,为模块的标识。如果此标识不存在,模块的标识则为此脚本的文件名称,必要时还包含此文件的路径。
- 第二个参数,dependencies为字符串数组,为当前模块依赖的其他模块标识。
- 第三个参数,factory为function,其参数对应依赖模块,是一个需要进行实例化的函数或者一个对象。
require([module], callback);
异步,require指定加载的模块,等到模块加载完成,回调函数就会执行模块里的代码。回调函数的参数与[module]是按顺序一一对应的。
无论加载(require)多少次,都只会执行一遍,以后加载的都是它的缓存。
requireJs
- requireJS总是动态地请求依赖的JS文件,所以必然涉及到一个JS文件的路径解析问题,requireJS默认采用一种baseUrl + moduleID的解析方式,requireJS对它的处理遵循如下规则:
1、在没有使用data-main和config的情况下,baseUrl默认为当前页面的目录
2、在有data-main的情况下,main.js前面的部分就是baseUrl,比如上面的js/
3、在有config的情况下,baseUrl以config配置的为准
上述三种方式,优先级由低到高排列。
require.config({
baseUrl: 'js/modules',
path:{
moduleA: 'moduleA',
moduleB: 'moduleB'
}
})
requireJS以一个相对于baseUrl的地址来加载所有的代码。页面顶层script标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl一般设置到与该属性相一致的目录。
<script data-main="js/main.js" src="scripts/require.js"></script>
CMD seajs
seajs官网已经不存在了
https://seajs.github.io/seajs/docs/#intro
CMD暴露模块用的是Commonjs的规范,导入模块用的是AMD的规范。
//定义没有依赖的模块 module1.js
define(function(require, exports, module){
module.exports = value;
exports.xxx = value;
})
//定义有依赖的模块
define(function(require, exports, module){
//引入依赖模块(同步)
var m2 = require('./module2');
//引入依赖模块(异步)
require.async('./module3', function(m3){
})
//暴露模块
exports.xxx = value;
})
//引入模块 index.js
define(function(require){
var m1 = require('./module1');
})
//在html中引入
<script src='js/libs/sea.js'></script>
<script>
seajs.config({
base: "./js/"
});
seajs.use("index");
</script>
ESModule
https://es6.ruanyifeng.com/
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。import命令是编译阶段执行的,在代码运行之前。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
es6module需要编译转换才能在浏览器端使用es6。
- 1.用babel将es6(import)转换为commonjs(require)
https://babeljs.io/docs/en/usage
npx babel js/src --out-dir lib
- 2.用browserify将commonjs转换为在浏览器端可以解析的
browserify .\lib\main.js -o dist/bundle.js
采用ES6Module规范,每次都需要执行打包编译的命令才能在浏览器端使用,因此需要一个打包工具自动完成打包。webpack
浏览器加载
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到script标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
script标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer与async的区别
defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
一句话,defer是“渲染完再执行”,async是“下载完就执行”。
另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。