什么是模块
早期js代码的书写方式
function bar () {
//...
}
function foo () {
//...
}
将不同的功能封装成不同的函数,挂载在全局,命名污染
简单封装:namespace方法(减少了变量数,仍未解决gobal污染的问题)
var myModule = {
foo:function() {},
bar:function() {}
}
myModule.foo()
IIFE模式(匿名闭包)
var myModule = (function(){
var _private = "private data";
var foo = function(){
console.log(_private)
}
return { foo: foo }
})()
//依赖注入
myModule.foo();
myModule._private; // undefined
引申,javascript的作用域(Scope)
- ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了‘块级作用域’,可通过新增命令let和const来体现。 特性:作用域是分层的,内层作用域可以访问外层作用域的变量
js是词法作用域
var value = 10;
function foo() {
console.log(value);
}
function bar() {
var value = 20;
foo();
}
bar();
结论1:函数是 JavaScript 唯一的 Local Scope
SCRIPT LOADER 只有封装性不够,还需按需加载
//body
script(src="jquery.js")
script(src="app.js")
...
...
按照代码的顺序执行,如需要体积大的资源,阻塞渲染
引申 script的defer和async
async:异步加载,下载完后执行 defer:异步加载,渲染完后执行
CommonJs
- 通过
require函数进行导入 - 通过
export或者module.exports导出 - 同步加载
- 四个重要的
module、exports、require、global
实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。
exports.add = function(a, b){ return a + b; }
module.exports = {
//在这里写上需要向外暴露的函数、变量
add: ...
}
// main.js
var math = require('math') // ./math in node
console.log(math.add(1, 2));
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
AMD(Async Module Definition)(RequireJS 对模块定义的规范化产出)
RequireJS 是一个JavaScript模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境,就像 Rhino and Node。使用RequireJS加载模块化脚本将提高代码的加载速度和质量。
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块 a.doSomething();
// 已加载所有
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.doSomething()
}
});
优点:
适合在浏览器环境中异步加载模块。可以并行加载多个模块。
缺点:
提高了开发成本,并且不能按需加载,而是必须提前加载所有的依赖。
CMD(Common Module Definition)(SeaJS 对模块定义的规范化产出)
/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明,代码运行到这才加载
a.doSomething();
if (false) {
var b = require('./b'); b.doSomething();
}
});
优点:
同样实现了浏览器端的模块化加载,可以按需加载,依赖就近。
缺点:
依赖SPM打包,模块的加载逻辑偏重。
UMD模块(兼容node端,和浏览器端)
factory负责返回你需要导出的内容(对象,函数,变量等)。
(function(root, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
console.log('是commonjs模块规范,nodejs环境')
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
console.log('是AMD模块规范,如require.js')
define(factory)
} else if (typeof define === 'function' && define.cmd) {
console.log('是CMD模块规范,如sea.js')
define(function(require, exports, module) {
module.exports = factory()
})
} else {
console.log('没有模块环境,直接挂载在全局对象上')
root.umdModule = factory();
}
}(this, function() {
return {
name: '我是一个umd模块'
}
}))
ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口
// math.js
export default math = {
PI: 3.14,
foo: function(){}
}
// app.js
import math from "./math";
math.PI
ESModule 需要经历三个步骤:
1: Construction(构造)- 找到,下载所有的文件并且解析为module records。
2: Instantiation(实例化)- 在内存里找到所有的“盒子”,把所有导出的变量放进去(但是暂时还不求值)。然后,让导出和导入都指向内存里面的这些盒子。这叫做“linking(链接)”。
3: Evaluation(求值)- 执行代码,得到变量的值然后放到这些内存的“盒子”里。