前端开发离不开模块化,与模块化有关的关键字有以下几个:
- require/module.exports
- export/import
- define/require/exprots
- define/seajs.use
这涉及到4种模块化规范:AMD、CMD、CommonJS、ES6。
AngularJS
AngularJS模块化使用的并不是标准的AMD规范,AngularJS的风格风格大致是这样的:
不仅模块的定义和引入麻烦,与Html标签耦合也较严重,阅读吃力,扩展与复用麻烦。AngularJS的模块化风格还停留在代码层面。
后来出现了AMD规范。
AMD
AMD规范使用统一的define、require做为伪关键字。模块化定义是这样的:
代码语言:javascript复制// a.js
define(["b", "require", "exports"], function(b, require, exports) {
console.log("a.js执行");
console.log(b);
// 暴露api可以使用exports、module.exports、return
exports.a = function() {
return require("b");
}
})
// b.js
define(function() {
console.log('b.js执行');
console.log(require);
console.log(exports);
console.log(module);
return 'b';
})
引用是这样的:
代码语言:javascript复制// index.js
require(['a', 'b'], function(a, b) {
console.log('index.js执行');
})
define与require虽然不是语法关键字,但已经在努力让自己看起来是关键字了,它们依赖于requireJS才能工作。AMD规范仍然是在代码层面折腾。
再后来出现了CMD规范。
CMD
CMD(Common Module Definition)表示通用模块定义,该规范是国内发展出来的,由阿里的玉伯提出。就像AMD有个requireJS,CMD有个浏览器的实现SeaJS。
定义模块:
代码语言:javascript复制// mymodule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
});
加载模块:
代码语言:javascript复制// 加载与使用模块
seajs.use(['mymodule.js'], function(my){
});
无论是AMD、还是CMD都是在文件内的代码层面折腾。后来出现了CommonJS,从CommonJS开始,默认一个文件是一个模块。
CommonJS
定义模块:
代码语言:javascript复制// foo.js
function foo(){
lib.log('hello world!');
}
// 导出给其它模块
module.exports.foo = foo;
使用模块:
代码语言:javascript复制const foo = require('foo');
CommonJS规范已经相当完善了。微信小程序、小游戏默认推荐的模块化规范即是CommonJS。
模块的动态加载与静态加载
使用CommonJS,可以在运行时动态加载模块:
代码语言:javascript复制if (condition) {
let my = require('./mymodual')
}
但是动态加载不便于引擎静态分析,不能在开发阶段就确认程序的依赖关系。此外,像这样的引入:
代码语言:javascript复制let { stat, exists, readFile } = require('fs');
这是整体引入了fs,然后分别赋值给了三个变量。等价于:
代码语言:javascript复制let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
为了实现按需引入及在开发阶段尽早确认类库的依赖关系,尽量减少运行时异常。ES6模块规范应运而生。
ES2015(ES6)
2015年6月,ES2015正式发布,即ES6。
声明模块:
代码语言:javascript复制export function area(radius) {
return Math.PI * radius * radius;
}
引用与使用模块:
代码语言:javascript复制import {area} from './circle'
console.log('圆面积:' area(10))
如果想在引入时不使用析造语法,声明模块时可以使用default关键字:
代码语言:javascript复制export default function area(radius) {
return Math.PI * radius * radius;
}
引用就这样了,不再需要花括号:
代码语言:javascript复制import area from './circle'
统一的导出格式
一个类库,有没有可能同时遵从多个模块化规范,支持多种方式的导入?
答案是可以的:
代码语言:javascript复制;(function (name, definition) {
// 检测上下文环境是否为AMD或CMD
var hasDefine = typeof define === 'function',
// 检查上下文环境是否为Node
hasExports = typeof module !== 'undefined' && module.exports;
if (hasDefine) {
// AMD环境或CMD环境
define(definition);
} else if (hasExports) {
// 定义为普通Node模块,使用CommonJS规范导出
module.exports = definition();
} else {
// 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
// 使用script标签在html中引入
this[name] = definition();
}
})('hello', function () {
// 真正的模块代码放在这里
'use strict';
/*eslint-disable */
var hello = function () {};
return hello;
});
这里导出方式,不但支持以上4种模块化方案,还支持传统的script标签引入。当以script标签在html页面中引入时,访问window.xxx可以调取类库。例如上面的示例,直接以script引入,可以访问window.hello。
use strict
是开启严格模式。开启后,对代码有如下约束:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
2019年5月10日 石桥码农