自研模块加载器(一) 模块系统概述与自定义模块规范书写规定
模块系统概述
CommonJs/AMD/CMD/ES6 Modules
什么是模块化?
模块化就是把系统分离成独立的功能的方法,需要什么功能,就加载什么功能
当一个系统越来越复杂时候,我们会遇到这些问题
1. 命名冲突
2. 文件依赖
使用模块化开发可以避免以上问题,并提升开发效率
1. 可维护性
2. 可复用性
在生产角度,模块化是一种生产方式,这种生产方式效率高,维护成本低。
模块化开发演变
1. 全局函数
早期开发中,将重复的代码封装成函数,将多个函数放在一个文件中。 缺点: 污染全局变量,看不出相互的直接关系
2. 通过对象命名的方式
通过对象命名的形式,从某种程度上减少了变量命名冲突,但是不能从根本上解决。 缺点:内部变量可被外部改写,命名空间越来越长
3. 私有共有成员分离
利用此方式将函数包装成了独立的作用域,私有空间变量函数不会影响全局。 缺点:解决的变量冲突的问题,但是没有解决降低开发复杂度的问题。
CommonJS
CommonJS加载是同步的,也就是说加载完成才能执行后面的操作。
Node.js主要用于服务器编程,模块都是存储在本地硬盘中,加载速度快,所以Node.js 采用CommonJS规范。
CommonJS规范为三部分: Module(模块标识)、Require(模块引入)、Exports(模块定义)
Module变量在每个模块内部,就代表当前模块
Exports属性是对外接口,用于导出模块的变量和方法
Require()用来加载外部依赖,读取并执行JS, 返回该模块的Exports对象
AMD (RequireJS)
AMD也就是异步模块定义。它采用异步的方式加载模块,通过Define的方式定义模块,require方法加载模块。
定义模块:
define([tools], function(){})
如果该模块还依赖其他模块,则第一个参数以数组的形式传入依赖模块,第二个参数为自定义模块
AMD模块加载
require(['modules'],callback);
第一个参数 'modules' 为要加载的成员,因为是AMD异步的,所以第二个参数为模块加载完成后的回调
CMD (sea.js)
CMD即通用模块定义。CMD是国内发展出来的规范,Sea.js实现来这个规范。
在CMD规范中,一个模块就是一个文件,代码书写如下:
define(function(require, exports, module ) { })
CMD推崇依赖就近,延迟执行,文件是提前加载好,只有在require的时候才去执行。
define(function(require, exports, module ) { var math = require('./math'); math.add(); })
ES6模块化
语法简介,支持异步加载,未来可以成为浏览器和服务器通过的模块化解决方案。
ES6模块的定义:
新增 'import' 和 'export' 两个关键字
export用于暴露模块功能, import 用于引入模块提供的功能
import { foo, obj } from './lib'; // 引入模块功能 foo();// 使用方法 const foo1 = function() {} // 导出默认 export default foo1; export { foo1 }
ES6模块运行机制:
ES6 模块是动态引用,如果使用import 从一个模块加载变量(import foo from 'lib'),变量不会被缓存,而是成为一个指向被加载模块的一个引用。 等到脚本执行时,根据只读引用,到那个被加载的模块中取值。
自定义模块规范(书写约定)
自定义加载器
模块的定义:
1. 约定:所有的Javascript文件都应该用模块的形式来书写,并且一个文件只能包含一个模块;
2. 使用全局函数define来定义模块 define(factory[require,exports,module]));
3. factory函数在调用时,会始终传递三个参数: require,exports和module, 这三个参数在所有的模块代码里可以用
define(function(require, exports, module)) { // 模块代码 })
模块加载器会从factory.tostring()中解析出来当前模块依赖的模块, 它是一个由模块标识组成的数组。
exports用来向外提供模块的api || 使用return直接向外提供api
require 函数用来访问其他模块提供的api
module 参数存储模块的元信息(id是模块的唯一标识, deps依赖泪奔 exports对外提供的接口对象)
注意:
1. 在模块代码中, 第一个参数必须命名为 require
2. 不要重命名require函数,或在任何作用域中给require重新赋值
3. require的参数值必须是字符串直接量,不能是表达式。