JavaScript 模块化详解

目录

一. 什么是模块化

二. 模块化的发展过程

2.1 无模块化时代

2.2 模块化雏形时代

三. 模块化规范

3.1 CommonJS

3.2 AMD / RequireJS

3.3 CMD / SeaJS

3.4 AMD vs CMD

四. ES6 与 JavaScript模块化 关系

五. CommonJS vs AMD vs CMD vs ES6


一. 什么是模块化

  • 将一个复杂的程序,依据一定的规则(规范)封装成一个或多个块(文件)
  • 块的内部实现是私有的,只暴露一些接口(方法)供外部使用

二. 模块化的发展过程

2.1 无模块化时代

  • 在 Ajax 还未提出之前,JavaScript 只用来在网页上进行表单校验提交,渲染 DOM
============ 某 .js 文件 ============
============ 某 .js 文件 ============
============ 某 .js 文件 ============
var str, num;

function submit() {
  str = document.getElementById("xx").value;
  if (str) { ... } else { ... }
    ...
  num = 1;
  for (var i = 0; i < 10; i++) { num++; ... }
}

form.submit();


============ 某 .html 文件 ============
============ 某 .html 文件 ============
============ 某 .html 文件 ============

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="main.js"></script>
  • 缺点:全局变量污染、函数命名冲突、文件依赖顺序

2.2 模块化雏形时代

  • 2006 年,Ajax 的概念被提出,前端拥有了 主动向服务端发送请求,并操作返回数据 的能力,传统网页向 “富客户端” 发展
  • 出现了简单的 “功能对象” 封装

2.2.1 namespace 模式

  • 优点:减少了全局变量
  • 缺点:数据不安全(外部可以直接修改模块内部的数据),模块名称会暴露在全局,存在命名冲突,依赖顺序问题
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============

var myModule = {
  first_name: "www.",
  second_name: "baidu.com",
  getFullName: function () {
    return this.first_name + this.second_name;
  },
};


============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============

myModule.first_name = "img.";
console.log(myModule.getFullName());

2.2.2 自执行匿名函数(闭包)模式

  • 优点:变量、方法全局隐藏,模块私有化
  • 缺点:模块名称会暴露在全局,存在命名冲突,依赖顺序问题
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============

(function (window) {
  let _moduleName = "module";
  function setModuleName(name) {
    _moduleName = name;
  }
  function getModuleName() {
    return _moduleName;
  }
  window.moduleA = { setModuleName, getModuleName };
})(window);


============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============

moduleA.setModuleName("html-module");
console.log(moduleA.getModuleName());

console.log(moduleA._moduleName); //模块不暴露,无法访问模块内属性方法

2.2.3 模块化雏形时代问题总结

  • 如何安全的封装一个模块的代码?(不污染模块外的任何代码)
  • 如何唯一标识一个模块?
  • 如何优雅的把模块的 API 暴漏出去?(不能增加全局变量)
  • 如何方便的使用所依赖的模块?

三. 模块化规范

3.1 CommonJS

  • 2009 年 Node.js 发布,采用 CommonJS 模块规范

  • CommonJS 模块规范特点:
  1. 每个文件都是一个模块实例,代码运行在模块作用域,不会污染全局作用域
  2. 文件内通过 require 对象引入指定模块,通过 exports 对象来向外暴漏 API,文件内定义的变量、函数,都是私有的,对其他文件不可见
  3. 每个模块加载一次之后就会被 缓存
  4. 所有文件加载均是 同步完成,加载的顺序,按照其在代码中出现的顺序
  5. 模块输出的是一个值的拷贝,模块内部的变化不会影响该值
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============
============ 模块 .js 文件 ============

let _moduleName = "module";
function setModuleName(name) {
  _moduleName = name;
}
function getModuleName() {
  return _moduleName;
}
module.exports = { setModuleName, getModuleName };


============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============
============ .html文件 调用 模块.js ============

import { getModuleName, setModuleName } from "./es6.module";
setModuleName("es6 Module");
console.log(getModuleName());
  • 缺点:模块同步加载导致:资源消耗多,等待时间长,适用于服务器编程 

3.2 AMD / RequireJS

  • Commonjs 局限性:
  • 基于 Node.js 原生 API 在服务端可以实现模块 同步加载,但仅局限于服务端,客户端如果同步加载依赖的话,时间消耗非常大,于是 AMD 规范诞生了

  • 何谓 AMD?
  • AMD 是 ”Asynchronous Module Definition” 的缩写,意思就是 ”异步模块定义”
  • 它采用 异步方式 加载模块,模块的加载不影响它后面语句的运行
  • 所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(依赖前置),这个回调函数才会运行

  • 何谓 RequireJS?
  • RequireJS 是一个工具库,主要用于客户端的模块管理,它的模块管理遵守 AMD 规范
  • RequireJS 的基本思想:通过 define 方法将代码定义为模块,通过 require 方法实现代码的模块加载

  • 举个栗子:定义两个模块,一个没有依赖,一个有依赖
// module1.js 定义没有依赖的模块
define(function () {
  let _moduleName = "module";
  function getName() {
    return _moduleName;
  }
  return { getName }; // 暴露模块
});


// module2.js 定义有依赖的模块
define(["module1"], function (module1) {
  let _firstName = "AMD";
  function getFullName() {
    return _firstName + " " + module1.getName();
  }
  function setFirstName(name) {
    _firstName = name;
  }
  // 暴露模块
  return { _firstName, getFullName, setFirstName };
});
  • 在 main.js 中使用这两个模块
  • main.js 相当于一个工具库,对各种模块路径、基本使用方法进行封装 
// mian.js
require.config({
  paths: {
    module1: "./modules/module1",
    module2: "./modules/module2",
    // 第三方库模块
    jquery: "./libs/jquery.min",
  },
});

require(["module2", "jquery"], function (module2, jquery) {
  console.log(module2.getFullName());
  module2.setFirstName("AMD-AMD");
  console.log(module2.getFullName());
  console.log(module2._firstName);
  jquery("#moduleId").html("<i>My name is jquery-module</i>");
});
  •  .html 中引入工具库,并定义 js 主文件
// .html 中引入工具库,并定义js主文件
<script data-main="./main" src="./libs/require.js"></script>

  • AMD / RequireJS 特点:浏览器直接运行无需编译,异步加载,依赖关系清晰

3.3 CMD / SeaJS

  • 借鉴了 Commonjs 的规范与 AMD 规范,国内(阿里)诞生了一个 CMD(Common Module Definition)规范,CMD 规范专门用于浏览器端
  • 跟 RequireJs 类似,SeaJs 是 CMD 规范的实现
  • CMD 是 SeaJs 推广过程中诞生的规范,CMD 借鉴了很多 AMD 和 Commonjs 优点

  • 与 AMD 非常类似,CMD 规范(2011)具有以下特点:
  1. define 定义模块,require 加载模块,exports 暴露变量
  2. 不同于 AMD 的依赖前置,CMD 推崇依赖就近(需要的时候再加载)
  3. 推崇 api 功能单一,一个模块干一件事
// module.1
define(function (require, exports, module) {
  module.exports = {
    msg: "I am module1",
  };
})

// module.2
define(function (require, exports, module) {
  var module2 = require("./module1");
  function show() {
    console.log("同步引入依赖模块1 " + module2.msg);
  }
  exports.showModule = show;
});


// main.js
define(function (require) {
  var m2 = require("./modules/module2");
  m2.showModule();
});


// .html中引入工具库,并定义 js 主文件
<script type="text/javascript" src="./libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./main')
</script>

3.4 AMD vs CMD

  • AMD 推崇依赖前置
  • CMD 推崇依赖就近
// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
 a.doSomething()
 ...
 b.doSomething()
 ...
})


// CMD
define(function(require, exports, module) {
 var a = require('./a')
 a.doSomething()
 ...
 var b = require('./b') // 依赖可以就近书写
 b.doSomething()
 ...
}

四. ES6 与 JavaScript模块化 关系

4.1 ES6 中的模块化示例

  • 2015 年,ES6 规范中,将 JavaScript模块化 纳入 JavaScript 标准
  • ES6 中的模块化,在 CommonJS 的基础上进行改造,关键字有 import,export,default,as,from
// 模块js
let _moduleName = "module";
function setModuleName(name) {
  _moduleName = name;
}
function getModuleName() {
  return _moduleName;
}
export { setModuleName, getModuleName };


// 调用js
import { getModuleName, setModuleName } from "./es6.module";
setModuleName("es6 Module");
console.log(getModuleName());

4.2 CommonJS vs ES6

  • 模块内部值的改变 是否影响 外部引用的该模块?
  1. CommonJS 模块输出的是一个值的拷贝,原来模块中的值改变,不会影响已经加载模块中的值
  2. ES6 模块输出的是值的只读引用,模块内值改变,引用也改变

  • 模块导出内容范围?
  • CommonJS 模块是运行时加载,加载的是整个模块,所有的接口会全部加载
  • ES6 模块是编译时输出接口,可以单独加载其中的某个接口

五. CommonJS vs AMD vs CMD vs ES6

  • CommonJS 规范主要用于服务端编程,加载模块是同步的;不适合在浏览器环境,因为浏览器资源是异步加载的,存在阻塞加载,也因此有了 AMD、CMD
  • AMD 规范在浏览器环境中,异步加载模块,而且可以并行加载多个模块
  • CMD 规范与 AMD 规范很相似,都用于浏览器编程,依赖就近,代码更简单
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为 浏览器 和 服务器 通用的模块解决方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值