JavaScript 中的模块化

2023-11-16

在这里插入图片描述

封面图说明:© Michael J. Kochniss | mjk-photo.de | instagram.com/mjk_photo

在早期,JavaScript 程序主要用来实现一些页面上的动画或者简单的交互,所以程序不会太复杂,页面也不会有太多的 JavaScript 代码,前端在 JavaScript 程序中还没有模块化开发的需要。但是随着应用的复杂度增加,为增强代码的可维护性,提升应用的加载性能,前端开发对 JavaScript 模块化开发的需求愈发强烈。本文将按照 JavaScript 模块化开发的演进历程逐一介绍在社区被采用过的不同模块化方案。

什么是模块化

模块化是一种将系统分离成独立功能部分的方法,可将系统分割成独立的功能部分,严格定义模块接口、模块间具有透明性。
简单来说就是我们在开发应用的时候,通常会根据不同的功能分割成不同的模块来开发,开发完成后再按照特定的方式组合成一个整体,最终完成整个应用系统功能的开发。
JavaScript 的模块化开发,能帮助我们解决在开发过程中遇到的全局变量污染问题,函数命名冲突问题,以及依赖关系不好处理的问题,还有代码复用性问题。

无模块化开发

最原始的 JavaScript 程序开发就是没有模块化的概念的,在一个页面中,不管有多少功能,要写多少代码,我们都把代码写到一个或者几个JS 文件中,然后页面通过 script 标签引入相关 JS 文件。当页面功能比较复杂的时候,或者需要不同开发者协同进行同一功能开发的时候,就难免会遇到全局变量污染和命名冲突的问题,再者就是功能非常复杂的时候,写的代码量也会比较多,从外部引入到页面的 JS 文件数量也越来越多,如果没有管理和组织好代码以及代码的依赖关系,后期维护将变得非常被动。

立即执行函数(IIFE)

为防止 JavaScript 代码中的全局变量污染和命名冲突的问题,开发人员在 JavaScript 中引入了自执行函数,也就是大家经常都能在面试中都会被问到的闭包。什么是闭包呢?一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

通过闭包可以形成一个独立的作用域,其里面声明的变量仅在该作用域内,在闭包外部是不能访问和操作的,从而达到了私有变量的目的,也就解决了前文提到的全局变量污染和命名冲突的问题,但是闭包内部可以通过向外暴露一些方法,以让闭包外部能访问到闭包内的变量。另外,如果闭包内部想要访问全局变量或者其他模块里面的变量,这时可以通过立即执行函数的参数传递到闭包内部。

( function( global, factory ) {
        if ( typeof module === "object" && typeof module.exports === "object" ) {
                module.exports = global.document ?
                        factory( global, true ) :
                        function( w ) {
                                if ( !w.document ) {
                                        throw new Error( "jQuery requires a window with a document" );
                                }
                                return factory( w );
                        };
        } else {
                factory( global );
        }
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
} );

看上面一段代码,是我们熟悉的 jQuery 源码中的一部分,这个闭包函数有两个行参,第一个参数是当 typeof window !== “undefined” 的时候将 window 作为实参传递进去,否则将 this 作为实参传递进去,第二个参数则是一个匿名函数,可见通过闭包来隔离变量的使用是非常广泛的。

命名空间

除了使用立即执行函数可以解决变量污染的问题,还可以使用命名空间的方式来解决,简单来说,就是我们可以通过创建一个简单的对象字面量来保存所有的相关变量和函数,利用对象字面量模拟命名空间的作用。

var NAMESPACE = {
    person: function(name) {
        this.name = name;
        this.getName = function() {
            return this.name;
        }
    }
};

CommonJS

CommonJS规范加载模块是同步的,也就是说,加载完成才执行后面的操作。Node.js主要用于服务器编程,模块都是存在本地硬盘中,加载比较快,所以Node.js采用CommonJS规范。

CommonJS规范分为三部分:module(模块标识)、require(模块引用)、exports(模块定义)。 module变量在每个模块内部,就代表当前模块; exports属性是对外的接口,用于导出当前模块的方法或变量;require()用来加载外部模块,读取并执行js文件,返回该模块的exports对象。

CommonJS 有以下几个特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,再次加载时,会直接读取缓存结果。如果想让模块再次运行,则必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

定义模块:

// config.js
var api = 'https://github.com/trending?since=weekly&_pjax=%23js-pjax-container';
var config = {
  api: api,
};
module.exports = config;

引用模块:

// utils.js
var config = require('./config');
var utils = {
  request() {
    console.log(config.api);
  }
};
module.exports = utils;

AMD 模块化方案

随着前端业务的发展,代码也变得越来越复杂,通过简单的立即执行函数或者命名空间的方式来组织代码已经不太适用了。前端需要有一种更清晰、简单的方式处理最开始遇到的全局变量命名冲突的问题以及 JS 代码间的依赖关系。社区中不同的 JS 模块化规范陆续出现,其中使用比较流行的是 AMD 和 CMD,先来介绍一下 AMD 模块化方案。

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。它是一个在浏览器端模块化开发的规范,由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数(RequireJS库),实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。

AMD采用require()语句加载模块,它要求两个参数:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。

定义模块:

// a.js
define(function (){
  return {
   a:'hello world'
  }
});

引用模块:

// b.js
require(['./a.js'], function (moduleA){
 console.log(moduleA.a); // 打印出:hello world
});

CMD 模块化方案

CMD是 ”Common Module Definition”,称为 通用模块加载规范 。一般也是用在浏览器端。浏览器端异步加载库 Sea.js 实现的就是CMD规范。它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。

定义模块:

define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

引用模块:

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.foo()
  m4.bar()
})

ES6 Module

在ES6没有引入模块化概念之前,为了解决js模块化开发的问题,社区提出了CommonJS,AMD,CMD模块化开发方案;ES6模块化汲取CommonJS和AMD的优点,语法简洁,支持异步加载,旨在为浏览器和服务器提供通用的模块化解决方案,但从长远来看,未来无论是在web端还是基于node的服务端或者桌面端,模块化开发规范都将统一使用 ES6 Module。

ES6中模块的定义:ES6新增了两个关键字:export和import。export用于把模块里的内容暴露出来,import用于引入模块提供的功能。

// test.js
let name = '前端农民工';
function getName() {
  return name;
}

function setName(n) {
  name = n;
}


export {
  name,
  getName,
  setName,
}

ES6中模块的加载:使用 import 语法加载模块,如下:

import{ getName } from './test'
getName();

注意:可以使用export default命令,为模块指定默认输出,一个模块只能有一个默认输出,所以export default只能使用一次。

ES6模块运行机制:ES6模块是动态引用,如果使用import从一个模块加载变量(即import foo from ‘foo’),变量不会被缓存,而是成为一个指向被加载模块的引用。等脚本执行时,根据只读引用,到被加载的那个模块中去取值。

水平有限,文中难免有不足之处,欢迎大家关注我的微信公众号。(前端民工)

c2ab119c88fe4b78bbd02408c75294bd_tplv-k3u1fbpfcp-watermark.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript 中的模块化 的相关文章

  • 为什么我会收到此 Javascript 错误“连接未定义”?

    我不确定为什么会收到此错误 connection is not defined document getElementById flashTest sendValFromHtml connection value 这是我的代码 functi
  • 如何避免多系列折线图d3.js的工具提示重叠

    我已经在多系列折线图上创建了工具提示 如下所示在这里回答 https stackoverflow com questions 34886070 d3 js multiseries line chart with mouseover tool
  • 图表js不显示

    我正在尝试使用 Charts js 创建一个简单的折线图 当我运行下面的代码时 没有出现图表 我究竟做错了什么 我正在关注这个教程http www chartjs org docs latest getting started http w
  • 如何将毫秒转换为可读的日期?

    下列 new Date 1324339200000 toUTCString Outputs Tue 20 Dec 2011 00 00 00 GMT 我需要它返回Dec 20 除了我可以使用的更好的方法之外toUTCString 我正在寻找
  • 任何 JavaScript 代码都是有效的 TypeScript 代码吗?

    目前我已经开始学习TypeScript 从我研究过的文档来看TypeScript 我看到一些纯的样品JavaScript代码可以编译为TypeScript code 我的问题是 TypeScript 语言的设计方式是否使任何 JavaScr
  • 为 Promise 编写循环的正确方法。

    如何正确构造循环以确保满足以下条件承诺电话和被束缚的记录器 log res 通过迭代同步运行 蓝鸟 db getUser email then function res logger log res this is a promise 我尝
  • 如何为多个元素添加Class?

    我正在使用这段 javascript 向多个元素添加一个类 我试图引用多个 div 并向它们添加类 它只适用于第一个 JavaScript
  • 使用 ngx-translate 时更改 URL

    当有人使用 ngx translate 单击所选语言时 我尝试更改 URL 我想我应该通过订阅语言更改事件然后修改当前的 url 以反映所选的语言来做到这一点 因为我是新手 所以我不确定是否需要服务来做到这一点 或者可能是另一种解决方法 我
  • 如何在 Vue.js 2 中使用事件总线通过自定义事件传递数据

    我在用着Vue js 2 5 x 在我的玩具项目中 我实现了一个事件总线 类似于所示的here https alligator io vuejs global event bus 事件总线在 Vue 原型中全局注册为 eventBus 然后
  • 用于验证网络路径的正则表达式 PHP、jQuery、JavaScript、Ruby

    尝试找出用于验证网络路径的正则表达式 即 comp xyz or comp or comp x y z storage或者所有部分都更长的东西 但希望能够传达其要点 我目前拥有的是一个简单的输入字段 用户可以通过它传递信息 事情是我不希望他
  • c3js数据标签的位置

    有没有可能的方法来更改数据上方标签的位置c3条形图 在官方文档中 很好地解释了如何通过操作 y 和 x 整数来更改 x 和 y 测量轴上标签的位置 但我没有找到任何数据标签 我试图用简单的方式指出它d3其上c3是基于但是console lo
  • 为什么react中的组件需要大写? [复制]

    这个问题在这里已经有答案了 因此 当您声明一个组件以小写首字母进行反应时 它不会显示 也不会引发错误 当您将组件名称大写时 它确实有效 这样的实现是怎样的呢 为了避免与现有的 html 元素发生冲突 还是这是一个错误 var test Re
  • jQuery.ajax() 记录 HTTP 请求

    我有一个发送 HTTP POST 请求的函数 我想记录它以进行调试 这是函数 function serverRequest URL DATA callback ajax url URL type POST dataType text con
  • 如何防止 CSS 或 jQuery 中单词和标点符号之间的换行

    我在一个段落中有一些文字 我的问题是 当标点符号位于单词末尾时 有时可以换行到下一行 像这样 This is the text This is a new line 我可以用 CSS 或 jQuery 解决这个问题吗 如果您不在单词和标点符
  • IE 开发工具断点不起作用

    我正在尝试在 IE 11 中调试一些 javascript 但无法强制它在断点处停止 debugger 行工作正常 停止该行中的调试器 相同的文件没有debugger 行但在同一位置设置断点不会执行任何操作 功能正常 但调试器不会在断点处停
  • 检测浏览器是否支持 contentEditable?

    There s 这个问题 https stackoverflow com questions 3497942 browser detect contenteditable features 但发布的解决方案是浏览器嗅探 我试图避免这种情况
  • 如何找出javascript中加载了哪些javascript?

    继另一个问题的评论之后 我问自己是否有办法获取页面上加载的所有 js 代码的列表 就像 Firebug 或 chrome Inspector 所做的那样 有没有一种纯javascript的方法 一种方法是抓取脚本标签 但这样你可能会错过动态
  • 为什么转换 new.Date() .toISOString() 会改变时间?

    我正在以两种不同的格式在数据库中插入日期 这是作为日期时间插入 var mydate mydate new Date document getElementById clockinhour value mydate toISOString
  • 带有 'as' 属性的通用 React TypeScript 组件(能够渲染任何有效的 dom 节点)

    我在下面的示例中按预期工作 我的问题是 无论如何我可以重写它 这样我就不必传递通用的T和as支柱 理想情况下我想通过asprop 并让组件的 prop 接口使用它 这在 TypeScript 中可能吗 export type Props l
  • Page_ClientValidate 正在验证多次。

    我的问题是 验证摘要消息 警报 显示两次 我无法弄清楚原因 请帮忙 这是代码 function validate javascript function if typeof Page ClientValidate function var

随机推荐

  • 微信终于支持 H5 跳转 App &小程序

    继小程序灰度测试分享朋友圈刷屏后 滴滴滴 这 闲着也闲着 顺藤摸瓜点了进去 好家伙 产品小姐姐写这个文案还是太含蓄了 我猜有可能是老干妈还没吃够 根据刀哥多年写代码要看文档的经验来看 证实了这次更新不仅支持了打开小程序 连app也顺带支持了
  • 第17节-PhotoShop基础课程-画笔修复工具

    文章目录 前言 1 画笔工具 1 基本操作 2 工具选项 1 不透明度 2 流量 3 平滑 2 画笔大小工具栏大小设置 4 笔刷 2 铅笔工具 3 颜色替换工具 批量替换颜色 4 混合器画笔工具 人像精修 前言 画笔工具的使用 1 画笔工具
  • BottomSheetDialogFragment圆角

    自己使用BottomSheetDialogFragment时 想实现上方圆角 布局设置了圆角的背景后 需要给dialog的北京设置为透明 才能有圆角的效果 网上其他的文章都是这么实现的 dialog getWindow findViewBy
  • 小程序发布上线全流程(包含小程序怎么通过审核)

    小程序在开发完成后 需要上传代码 设为体验版本 功能测试 提交审核 发布上线这几个基本步骤 接下来用自己的亲身经历一一详细介绍 小程序发布上线全流程 1 上传代码 在微信开发者工具的右上角上传处上传全部代码 如下图 如果小程序中涉及到一些r
  • 程序kill后仍占用GPU

    sc yolov5 zqchen gpurtx02 ultralytics gpustat gpurtx02 Thu Aug 24 09 18 31 2023 470 74 0 Quadro RTX 6000 41 C 0 0 24220
  • 2-需求分析

    一 需求收集 1 需求概念 以下常见三种情形 提问题 目的不明确 明确困境 提目的 目的明确 解决方案不明确 提方案 目的明确 方案明确 概念 本质是用户的预期和现状之间的差异产生的需求 在提出需求时 往往会基于目的描述问题 想法或建议 往
  • python代码~考研祝福

    完整代码如下所示 from turtle import speed 2 Turtle screen delay 0 def go to x y up goto x y down def ring a b c d for i in range
  • STM32F103小容量、中容量和大容量单片机介绍

    一 小容量 中容量和大容量表示的型号 STM32F103x4和STM32F103x6被归为小容量产品 闪存小于等于32K STM32F103x8和STM32F103xB被归为中等容量产品 闪存小于等于128K STM32F103xC STM
  • diskgenius创建efi分区_怎么创建efi系统分区?efi系统分区创建教程

    文章导读 近两年出来的的电脑不管是新台式机还是笔记本电脑 绝大多数是uefi主板 要采用对应的硬盘分区是gpt格式的 所以我们一定要记得采用efi引导对应的分区类型一定是gpt分区 EFI分区是GPT磁盘分区表里面的一个必要分区 是独立于系
  • win10上安装python3.9.0+robotframework

    win10上安装python3 9 robotframework python3 9 0下载安装 robotframework安装 wxpython安装 ride安装 python3 9 0下载安装 下载地址 python3 9 0下载地址
  • 【机器实战学习】朴素贝叶斯 python代码实现

    朴素贝叶斯 输入数据创造词汇表 代码实现 coding UTF 8 def loadDataSet 创建了一下实验样本 return 词条且分的文档集合 类别标签的集合 自动检测侮辱性的语言 postingList my dog has f
  • 23种设计模式:适配器模式(最强解析!!!)

    适配器模式是23种设计模式之一 适配器模式作用 适配器模式的作用 在于将一个类的接口变换为客户端所期待的另一种接口 使得原本因为接口不匹配而无法一起工作的两个类能在一起工作 也就是说 适配器模式解决的是接口兼容性问题 适配器模式实现方式 适
  • 深度学习(十四):详解Matconvnet使用imagenet模型训练自己的数据集

    上节讨论过如何使一个简单的cnn网络训练mnist数据集 该节介绍复杂并且使用广泛的使用imagenet网络的预训练模型训练自己的数据集 Ok首先是自己的数据集了 Matconvnet中训练imagenet的数据集的准备不像caffe这些工
  • 【原创】【硬件电路】N沟道、P沟道MOS管基本原理与应用案例

    文章首发于同名微信公众号 DigCore 欢迎关注同名微信公众号 DigCore 及时获取最新技术博文 一 N MOS管和P MOS管的对比 二 N MOS的开关条件 N MOS管的导通调节是G极与S极中间的电压差超过阈值时 D极和S极导通
  • C语言中printf("lld")和printf("I64d")

    C语言中printf lld 和printf I64d 是不同的 这个是因为编译器的不同导致的 所以广大的ACMer需要注意了 HDU OJ 4504就是这样的一个问题 下面是Dp和math方法的AC代码 但是换成lld全都Wrong An
  • 二级空间配置器

    一级空间配置器 https blog csdn net qq 37964547 article details 80474316 一 二级空间配置器 在前面文章中我们介绍了一级空间配置器的概念和实现 一级空间配置器的实现相对比较简单 直接封
  • 操作系统教程第六版——3.3页式存储管理笔记

    一 引入页式存储管理的目的 1 减少碎片 纯分页系统 2 只在内存存放那些反复执行或即将执行的程序段与数据部分 而把那些不经常执行的程序段和数据存放于外存待执行时调入 以提高内存利用率而提出来的 页式虚拟存储 请求分页系统 二 基本思想 1
  • 【报错】RuntimeError: CUDA error: CUBLAS_STATUS_EXECUTION_FAILED when calling `cublasLtMatmul( ltHandle,

    在GPU上运行hugging face transformer的时候出现如下报错 RuntimeError CUDA error CUBLAS STATUS EXECUTION FAILED when calling cublasLtMat
  • 【Android】JUnit和Espresso单元测试新手快速入门

    引入依赖 android defaultConfig testInstrumentationRunner androidx test runner AndroidJUnitRunner dependencies testImplementa
  • JavaScript 中的模块化

    封面图说明 Michael J Kochniss mjk photo de instagram com mjk photo 在早期 JavaScript 程序主要用来实现一些页面上的动画或者简单的交互 所以程序不会太复杂 页面也不会有太多的