- 前端js模块化的演变发展
- 模块化解决的问题
- 传统模块化、插件化
- CommonJS
- AMD/CMD
- ES6模块化
ES6以前 没有js引擎
- 一开始js写在html的script标签里
- js内容增多,抽取出index.js文件,外部引入
- js再增加,index.html对应index.js index2.html对应index2.js(模块化概念的诞生)
- 含有可复用的代码,提出公共的common.js
- 引入common.js的所有内容不合理 → 不能光以页面为基准来区分程序块、分js文件
案例一 模块化初试
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="text/javascript" src="js/module_a.js"></script><script type="text/javascript" src="js/module_b.js"></script><script type="text/javascript" src="js/module_c.js"></script><script type="text/javascript" src="js/index.js"></script>
</body>
</html>
// module_a.js
var a = [1, 2, 3, 4, 5].reverse()
// module_b.js
var b = a.concat([6, 7, 8, 9, 10])
// module_c.js
var c = b.join('-')
// index.js
console.log(a)
console.log(b)
console.log(c)
存在问题
- js引擎遇到script时阻塞,所以这4个js文件必须按内部的逻辑,顺序加载,顺序是不能变的
- 这4个文件共用了JS作用域-全局作用域
- 因此:污染全局 + if 变量重名 → 变量覆盖
模块化解决问题:
- 加载顺序
- 污染全局
案例二 IIFE注入
历史问题:ECMA规定语句应当以分号结尾,早前js都是运行在浏览器上的,但浏览器支持判断当前是否是语句,是就自动加上分号。当使用多个IIFE,且不写分号时,浏览器无法识别,报错。因此约定俗成的规定,IIFE前面必须写分号,更规范的是结尾也写分号,即
;(function(){
})();
- 使用IIFE,解决污染全局,为了易于拓展,模块应当返回对象
- 新的问题,若没有抛到全局,如何在模块之间获得相应的abc
- 用变量接收IIFE的返回值,在需要用的的模块传入(注入),解决了模块依赖
- 注意:模块名完全独立,不应该重复,因此在全局声明了,而内部abc属于数据类型的变量,不能在全局声明
- 注意:不注入moduleABC,直接用moduleA.a访问变量能得到正确结果,但注入意味着moduleABC被引入到局部作用域下,不再需要去全局上查找了
// module_a.js
var moduleA = (function () {var a = [1, 2, 3, 4, 5].reverse()return {a: a}
})();
// module_b.js
var moduleB = (function (moduleA) {var b = moduleA.a.concat([6, 7, 8, 9, 10])return {b: b}
})(moduleA);
// module_c.js
var moduleC = (function (moduleB) {var c = moduleB.b.join('-')return {c: c}
})(moduleB);
// index.js
; (function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})(moduleA, moduleB, moduleC);
存在问题
- 顺序问题依然未解决
插件
- 构造函数执行init
- 构造函数挂载到window(插件)
- script里实例化
案例三 CommonJS
NodeJS诞生带来了前所未有的模块化体验
require(...) 引入模块
module.exports导出模块
运行在node环境下
CommonJS是模块化规范,来源于NodeJS
在服务端开发,引入模块用require,是同步的方法
只要引用,就会创建模块的实例
有非常强的缓存机制
一定是在Node上运行,客户端运行不了(要借助webpack?)
require实质是IIFE,会传入一些参数
(function(exports,require,module,__filename.__dirname){})()
- CommonJS
- 只引入index.js
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="text/javascript" src="index.js"></script>
</body>
</html>
// module_a.js
var a = (function () {return [1, 2, 3, 4, 5].reverse()
})();
module.exports = {a
};
// module_b.js
var moduleA = require('./module_a')
var b = (function () {return moduleA.a.concat([6, 7, 8, 9, 10])
})();
module.exports = {b
}
// module_c.js
var moduleB = require('./module_b')
var c = (function () {return moduleB.b.join('-')
})();
module.exports = {c: c
}
// index.js
var moduleA = require('./js/module_a.js');
var moduleB = require('./js/module_b.js');
var moduleC = require('./js/module_c.js');
; (function () {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})()
案例四 AMD
- 不需要借助webpack就能运行在客户端
- 所有依赖加载完成后才会执行回调函数(前置依赖)
AMD Asynchronous Module Definition 异步模块定义
来源于CommonJS
define(moduleName, [module], factory) 定义模块
require([module], callback) 引入模块
RequireJS实现AMD
- 引入require.js
- 定义+使用依赖时注入
- 使用module得先require.config配置路径
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="js/require.js"></script><script src="js/index.js"></script>
</body>
</html>
// module_a.js
define('moduleA', function () {var a = [[1, 2, 3, 4, 5]]return {a: a.reverse()}
})
// module_b.js
define('moduleB', ['moduleA'], function (moduleA) {return {b: moduleA.a.concat([6, 7, 8, 9, 10])}
})
// module_c.js
define('moduleC', ['moduleB'], function (moduleB) {return {c: moduleB.b.join('-')}
})
// index.js
require.config({paths: {moduleA: 'js/module_a',moduleB: 'js/module_b',moduleC: 'js/module_c'}
})
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
});
案例五CMD
- 阿里对模块化的贡献
- require加载 define定义
- exports导出(return和它的效果一直) module操作
- 需要配置模块URL
- 依赖加载完毕后执行factory
- 依赖就近 按需加载(这是和CommonJS AMD本质上的不同)
Common Mudule Definition 通用模块定义
define(function(require,exports,module){}) 定义模块
seajs.use([module路径],function(moduleA,moduleB,moduleC){}) 使用模块
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="js/sea.js"></script><script src="js/index.js"></script>
</body>
</html>
// module_a.js
define(function (require, exports, module) {var a = [[1, 2, 3, 4, 5]]return {a: a.reverse()}
})
// module_b.js
define(function (require, exports, module) {var moduleA = require('module_a')return {b: moduleA.a.concat([6, 7, 8, 9, 10])}
})
// module_c.js
define(function (require, exports, module) {var moduleB = require('module_b')return {c: moduleB.b.join('-')}
})
// index.js
seajs.use(['module_a.js', 'module_b.js', 'module_c.js'], function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})
案例六 ES6模块化规范
import module from ‘模块路径’ 导入模块
export module 导出模块Uncaught SyntaxError: Cannot use import statement outside a module
调试过程中的报错解答
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="module" src="./js/index.js"></script>
</body>
</html>
// module_a.js
export default {a: [1, 2, 3, 4, 5].reverse()
}
// module_b.js
import moduleA from './module_a.js'
export default {b: moduleA.a.concat([6, 7, 8, 9, 10])
}
// module_c.js
import moduleB from './module_b.js'
export default {c: moduleB.b.join('-')
}
// index.js
import moduleA from './module_a.js'
import moduleB from './module_b.js'
import moduleC from './module_c.js'; (function (moduleA, moduleB, moduleC) {console.log(moduleA.a)console.log(moduleB.b)console.log(moduleC.c)
})(moduleA, moduleB, moduleC);
案例7 CommonJS与ES6的区别
- 配置webpack
// export.js
exports.a = 0;
setTimeout(() => {console.log('来自export', ++exports.a)
}, 300);
// commonjs.js
const { a } = require('./export')
setTimeout(() => {console.log('来自commonjs', a)
}, 300);
// es6.js
import { a } from './export'
setTimeout(() => {console.log('来自es6', a) // a是只读的
}, 300);
- commonjs输出的是一个值的拷贝
- es6模块输出的是值的引用
- commonjs模块是在运行时加载(commonjs运行在服务端,require时加载)
- es6模块是在编译时加载