基础详细版:Javascript模块化发展历程

早期的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;导出:exportmoduleA.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模块的不同导入导出方式:

  1. 默认导出(Default Exports)每个模块只能有一个默认导出。及对应导入。
js 复制代码
// 导出单个函数作为默认导出  
export default function() {  
    console.log('This is the default export.');  
}
// 导入  
import customName from './example';  
  1. 命名导出(Named Exports)可以导出多个变量、函数、类等。及对应导入。
js 复制代码
// 导出多个函数和变量  
export const name = 'Example';  
export function myFun() {  
    console.log(`Hello, ${name}!`);  
}  
// 导入指定的命名导出  
import { name, myFun } from './example';  

其他导入:

  1. 导入所有(不推荐,因为可能导致命名冲突)
js 复制代码
// 导入模块的所有导出,并绑定到一个对象上  
import * as example from './example';  
console.log(example.name); 
example.greet(); 
  1. 混合导入默认导出和命名导出
js 复制代码
// 同时导入默认导出和命名导出  
import myDefault, { name, myFun } from './example';  
  
myDefault(); // 输出: This is the default export.  
  1. 导入并立即重命名
js 复制代码
// 在导入时重命名导出项  
import { greet as sayHello } from './example';  
sayHello();

注意: 默认导出和命名导出可以并存于同一个模块中。

以上,加油吧。

相关推荐
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普3 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端