早期的JavaScript开发,由于缺乏模块化的概念,代码混杂在一起,难以维护、管理, 特别是大型项目,我们对于手动管理依赖顺序、甚至变量冲突等问题越来越苦恼。短短这些年,Web应用发展迅速、复杂性和规模不断提升,模块化的发展也成为了非常迫切的事情。
本文将介绍Javascript模块化在这些年的发展历程,并附带代码示例。
1. 无模块化阶段
早期的JavaScript开发中,没有模块化的概念。我们通常将所有代码写在一个或多个全局文件中,并按照文件的顺序执行。
script1.js
js
// script1.js
var name = "Script 1";
function sayHello() {
console.log("script1");
}
script2.js
js
// script2.js 假设在script1.js后面引入到html页面中
var name = "Script 2"; // 会覆盖 script1.js 中的 name 变量
function anotherFunction() {
console.log("testtest");
}
// 重写了 script1.js 中的 sayHello 函数 这也会覆盖 script1.js 中的 sayHello 函数
function sayHello() {
console.log("script2");
}
缺点:
-
全局作用域污染:所有js都运行在全局作用域下。可能导致命名冲突,如上例子
-
依赖关系混乱:我们需要手动管理代码的执行顺序,确保依赖项在需要之前被加载。不易管理。
以上造成了维护困难、可用性差的问题。
2. Common.js模块化规范
Node.js的出现后,其给JavaScript带来了模块化开发的能力。产生了CommonJS规范,这种规范在服务器端开发中被广泛使用。导出:module.exports
; 导入:require
moduleA.js
js
// 定义自己模块的变量和函数
var privateVariable = 'a';
function privateFunction() {
console.log('afun');
}
// 想要公用的函数
function publicFunction() {
console.log('This is a public function.');
}
module.exports = publicFunction; // 导出公用的函数
main.js
js
// 导入 moduleA 模块
var publicFunction = require('./moduleA');
// 可以调用公共函数
publicFunction();
// 不能调用模块A的私有变量因为它们没有被导出
题外话:
在浏览器中使用类似 CommonJS 的模块化,我们通常需要使用构建工具(如 Webpack、Browserify)或模块加载器(如 RequireJS)。例如,使用 Browserify,你可以将 CommonJS 模块打包成浏览器可以理解的格式。但直接在浏览器中使用 CommonJS 是不可能的,除非使用某种形式的转换或打包过程。
3. AMD规范
AMD是另一种 JavaScript 模块化规范,其产生主要是为了解决浏览器端js的模块化问题。CommonJS适合 Node.js 这类服务器端环境,且浏览器不支持 require、module、exports 等 Node.js 特有的全局变量,因此无法直接在浏览器中使用 CommonJS 模块。
AMD也提供了异步加载模块方案,即其不会阻塞代码的执行。AMD主要实现是 RequireJS,定义模块: define
;加载模块: require
。
moduleA.js
js
// 依赖moduleB 则传入['moduleB'],不依赖任何模块 则传入[]
define(['moduleB'], function(dependency) {
// 定义模块的功能
function doSomethingA() {
console.log('moduleA');
moduleB.someFunction(); // 调用依赖模块B的函数
}
// 导出模块的功能
return {
doSomething: doSomethingA
};
});
main.js
js
// 配置 RequireJS 的路径和依赖项
require.config({
baseUrl: 'path/scripts', // 基础路径
paths: {
'moduleA': 'moduleA' // 'moduleA' 模块的别名和路径
}
});
// 加载并使用模块
require(['moduleA'], function(moduleA) {
moduleA.doSomething(); // 调用模块A的功能
});
4. UMD
UMD主要是为了解决JavaScript模块在不同环境兼容性问题。由于浏览器端和服务器端(如Node.js)的js运行环境存在差异,导致不同的模块化规范(如CommonJS、AMD)的出现。而UMD的出现就是为了在这些不同的环境中提供一个统一方式。
js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD环境 则这样定义
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS环境
module.exports = factory();
} else {
// 全局变量环境
root.myModule = factory();
}
}(this, function () {
// 自己的代码
var myModule = {
doSomething: function () {
console.log('myself');
}
};
return myModule;
}));
5. Es6模块化
ES6引入了原生的模块系统,使得模块导入和导出更加简洁和直观。导入:import
;导出:export
。 moduleA.js
js
// 定义函数并导出
export function greet(name) {
console.log(`Hello, ${name}!`);
}
// 定义变量并导出
export const myArr = ['1', '2', '3'];
moduleB.js
js
// 导入moduleA中函数或者变量
import { greet, myArr } from './moduleA.js';
// 使用
greet('World'); // 输出: Hello, World!
console.log(myArr.key); // 输出: ['1', '2', '3']
ES6模块的不同导入导出方式:
- 默认导出(Default Exports)每个模块只能有一个默认导出。及对应导入。
js
// 导出单个函数作为默认导出
export default function() {
console.log('This is the default export.');
}
// 导入
import customName from './example';
- 命名导出(Named Exports)可以导出多个变量、函数、类等。及对应导入。
js
// 导出多个函数和变量
export const name = 'Example';
export function myFun() {
console.log(`Hello, ${name}!`);
}
// 导入指定的命名导出
import { name, myFun } from './example';
其他导入:
- 导入所有(不推荐,因为可能导致命名冲突)
js
// 导入模块的所有导出,并绑定到一个对象上
import * as example from './example';
console.log(example.name);
example.greet();
- 混合导入默认导出和命名导出
js
// 同时导入默认导出和命名导出
import myDefault, { name, myFun } from './example';
myDefault(); // 输出: This is the default export.
- 导入并立即重命名
js
// 在导入时重命名导出项
import { greet as sayHello } from './example';
sayHello();
注意: 默认导出和命名导出可以并存于同一个模块中。
以上,加油吧。