背景
随着前端代码越来越复杂,怎样写出优雅、清晰、可维护性的代码变的越来越重要。但因为js本身的原因,开始并没有模块化的设计,所以代码开始变的越来越难以组织和维护。为了使它变的可维护,主要经历了以下的几个阶段。
js模块化进程的历史
- 写成函数
function fn() {}
相关的代码块封装在一个函数中是最容易想到的方法,但是他也有不少的缺点。
命名污染全局变量、一个大功能模块中的成员互相不关联,无法保证不与别人冲突。
- 使用对象封装模块 在对象上定义属性和方法
var module = {
fn() {}
}
每个模块内部的属性和方法不占有全局变量名,大大的避免了冲突的问题。
缺点 外部可以随意修改内部的方法和属性
- 立即执行函数建立私有变量和作用域
(function(){
// 实现一个特权方法来访问模块
fn = function() {}
}())
使用这种方法较好的解决了上述的几个问题,模块对外只暴露定义的接口。且因为模块内部是私有变量,模块内不会被随意的访问和改动。
简单了解一下背景后来学习一下目前流行的两个基于不同环境的JS模块规范。
-
CommonJs规范
来源于nodeJS诞生的规范,运行在服务器环境。
一个文件就是一个模块,拥有单独的作用域。
通过require来加载模块。require(同步加载) 读取执行一个JS文件 返回该模块的exports对象。
例如:
var math = require('math')
CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}
require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。
-
AMD规范
AMD异步定义的模块
由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。
RequireJS主要解决两个问题
- 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
- js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
RequireJs也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。math.add()与math模块加载不是同步的,浏览器不会发生假死。
RequireJs包含有一个define全局变量 用来定义模块
define(id, dependencies, factory)
id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
使用方法
<script src="js/require.js" data-main="js/main"></script>
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
大致了解两种模块实现规范后我们就可以写一种兼容性的模块定义方法来让我们的模块在所有环境中都能运行。
下面给出代码:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD规范
define([], factory);
} else if (typeof exports === 'object') {
// 类Commonjs规范 node环境
module.exports = factory();
} else {
// 浏览器全局环境(root is window)
// 直接将函数定义在全局变量上
root
}
}(this, function () {
// do something ....
}))
实现了在AMD规范和node环境或者直接引入的浏览器全局环境中都能很好的兼容。基于webpack打包的机制可以直接npm安装后使用import引入这个模块。