前言

随着 Web 技术的蓬勃发展和依赖的基础设施日益完善,前端领域逐渐从浏览器扩展至服务端(Node.js),桌面端(PC、Android、iOS),乃至于物联网设备(IoT),其中 JavaScript 承载着这些应用程序的核心部分,随着其规模化和复杂度的成倍增长,其软件工程体系也随之建立起来(协同开发、单元测试、需求和缺陷管理等),模块化编程的需求日益迫切。JavaScript 对模块化编程的支持尚未形成规范,难以堪此重任;一时间,江湖侠士挺身而出,一路披荆斩棘,从刀耕火种过渡到面向未来的模块化方案;

概念

模块化编程就是通过组合一些__相对独立可复用的模块__来进行功能的实现,其最核心的两部分是__定义模块__和__引入模块__;

  • 定义模块时,每个模块内部的执行逻辑是不被外部感知的,只是导出(暴露)出部分方法和数据;
  • 引入模块时,同步 / 异步去加载待引入的代码,执行并获取到其暴露的方法和数据;

刀耕火种

尽管 JavaScript 语言层面并未提供模块化的解决方案,但利用其可__面向对象__的语言特性,外加__设计模式__加持,能够实现一些简单的模块化的架构;经典的一个案例是利用单例模式模式去实现模块化,可以对模块进行较好的封装,只暴露部分信息给需要使用模块的地方;

// Define a module
var moduleA = (function ($, doc) {var methodA = function() {};var dataA = {};return {methodA: methodA,dataA: dataA};
})(jQuery, document);// Use a module
var result = moduleA.mehodA();

直观来看,通过立即执行函数(IIFE)来声明依赖以及导出数据,这与当下的模块化方案并无巨大的差异,可本质上却有千差万别,无法满足的一些重要的特性;

  • 定义模块时,声明的依赖不是强制自动引入的,即在定义该模块之前,必须手动引入依赖的模块代码;
  • 定义模块时,其代码就已经完成执行过程,无法实现按需加载;
  • 跨文件使用模块时,需要将模块挂载到全局变量(window)上;

AMD & CMD 二分天下

题外话:由于年代久远,这两种模块化方案逐渐淡出历史舞台,具体特性不再细聊;

为了解决”刀耕火种”时代存留的需求,AMD 和 CMD 模块化规范问世,解决了在浏览器端的异步模块化编程的需求,__其最核心的原理是通过动态加载 script 和事件监听的方式来异步加载模块;__

AMD 和 CMD 最具代表的两个作品分别对应 require.js 和 sea.js;其主要区别在于依赖声明和依赖加载的时机,其中 require.js 默认在声明时执行, sea.js 推崇懒加载和按需使用;另外值得一提的是,CMD 规范的写法和 CommonJS 极为相近,只需稍作修改,就能在 CommonJS 中使用。参考下面的 Case 更有助于理解;

// AMD
define(['./a','./b'], function (moduleA, moduleB) {// 依赖前置moduleA.mehodA();console.log(moduleB.dataB);// 导出数据return {};
});// CMD
define(function (requie, exports, module) {// 依赖就近var moduleA = require('./a');moduleA.mehodA();     // 按需加载if (needModuleB) {var moduleB = requie('./b');moduleB.methodB();}// 导出数据exports = {};
});

CommonJS

2009 年 ry 发布 Node.js 的第一个版本,CommonJS 作为其中最核心的特性之一,适用于服务端下的场景;历年来的考察和时间的洗礼,以及前端工程化对其的充分支持,CommonJS 被广泛运用于 Node.js 和浏览器;

// Core Module
const cp = require('child_process');
// Npm Module
const axios = require('axios');
// Custom Module
const foo = require('./foo');module.exports = { axios };
exports.foo = foo;

规范

  • module (Object): 模块本身
  • exports (*): 模块的导出部分,即暴露出来的内容
  • require (Function): 加载模块的函数,获得目标模块的导出值(基础类型为复制,引用类型为浅拷贝),可以加载内置模块、npm 模块和自定义模块

实现

1、模块定义

默认任意 .node .js .json 文件都是符合规范的模块;

2、引入模块

首先从缓存(require.cache)优先读取模块,如果未命中缓存,则进行路径分析,然后按照不同类型的模块处理:

  • 内置模块,直接从内存加载;
  • 外部模块,首先进行文件寻址定位,然后进行编译和执行,最终得到对应的导出值;

其中在编译的过程中,Node对获取的JavaScript文件内容进行了头尾包装,结果如下:

(function (exports, require, module, __filename, __dirname) {var circle = require('./circle.js');console.log('The area of a circle of radius 4 is ' + circle.area(4));
});

特性总结

  • 同步执行模块声明和引入逻辑,分析一些复杂的依赖引用(如循环依赖)时需注意;
  • 缓存机制,性能更优,同时限制了内存占用;
  • Module 模块可供改造的灵活度高,可以实现一些定制需求(如热更新、任意文件类型模块支持);

ES Module(推荐使用)

ES Module 是语言层面的模块化方案,由 ES 2015 提出,其规范与 CommonJS 比之 ,导出的值都可以看成是一个具备多个属性或者方法的对象,可以实现互相兼容;但写法上 ES Module 更简洁,与 Python 接近;

import fs from 'fs';
import color from 'color';
import service, { getArticles } from '../service'; export default service;
export const getArticles = getArticles;

主要差异在于:

  • ES Module 会对静态代码分析,即在代码编译时进行模块的加载,在运行时之前就已经确定了依赖关系(可解决循环引用的问题);
  • ES Module 关键字:import export 以及独有的 default 关键字,确定默认的导出值;
  • ES Module 中导出的值是一个 只读的值的引用 ,无论基础类型和复杂类型,而在 CommonJS 中 require 的是值的拷贝,其中复杂类型是值的浅拷贝;
// a.js
export let a = 1;
export function caculate() {a++;
};// b.js
import { a, caculate } from 'a.js';console.log(a); // 1
caculate();
console.log(a); // 2a = 2; // Syntax Error: "a" is read-only

UMD

通过一层自执行函数来兼容各种模块化规范的写法,兼容 AMD / CMD / CommonJS 等模块化规范,贴上代码胜过千言万语,需要特别注意的是 ES Module 由于会对静态代码进行分析,故这种运行时的方案无法使用,此时通过 CommonJS 进行兼容;

(function (global, factory) {if (typeof exports === 'object') {   module.exports = factory();} else if (typeof define === 'function' && define.amd) {define(factory);} else {this.eventUtil = factory();}
})(this, function (exports) {​ // Define ModuleObject.defineProperty(exports, "__esModule", {value: true});exports.default = 42;
});

构建工具中的实现

为了在浏览器环境中运行模块化的代码,需要借助一些模块化打包的工具进行打包( 以 webpack 为例),定义了项目入口之后,会先快速地进行依赖的分析,然后将所有依赖的模块转换成浏览器兼容的对应模块化规范的实现;

模块化的基础

从上面的介绍中,我们已经对其规范和实现有了一定的了解;在浏览器中,要实现 CommonJS 规范,只需要实现 module / exports / require / global 这几个属性,由于浏览器中是无法访问文件系统的,因此 require 过程中的文件定位需要改造为加载对应的 JS 片段(webpack 采用的方式为通过函数传参实现依赖的引入)。

webpack 打包出来的代码快照如下,注意看注释中的时序;

(function (modules) {// The module cachevar installedModules = {};// The require functionfunction __webpack_require__(moduleId) {}return __webpack_require__(0); // ---> 0
})
({0: function (module, exports, __webpack_require__) {// Define module Avar moduleB = __webpack_require__(1); // ---> 1},1: function (module, exports, __webpack_require__) {// Define module Bexports = {}; // ---> 2}
});

实际上,ES Module 的处理同 CommonJS 相差无几,只是在定义模块和引入模块时会去处理 __esModule 标识,从而兼容其在语法上的差异。

异步和扩展

1、浏览器环境下,网络资源受到较大的限制,因此打包出来的文件如果体积巨大,对页面性能的损耗极大,因此需要对构建的目标文件进行拆分,同时模块也需要支持动态加载;

webpack 提供了两个方法 require.ensure() 和 import() (推荐使用)进行模块的动态加载,至于其中的原理,跟上面提及的 AMD & CMD 所见略同,import() 执行后返回一个 Promise 对象,其中所做的工作无非也是动态新增 script 标签,然后通过 onload / onerror 事件进一步处理。

2、由于 require 函数是完全自定义的,我们可以在模块化中实现更多的特性,比如通过修改 require.resolve 或 Module._extensions 扩展支持的文件类型,使得 css / .jsx / .vue / 图片等文件也能为模块化所使用;

原文链接
本文为云栖社区原创内容,未经允许不得转载。

Javascript 模块化指北相关推荐

  1. 《高阶前端指北》之JavaScript爬虫速成-视频下载(第四弹)

    视频存储 当前各大视频厂商都在升级视频存储架构,对于多数开发者来说,爬视频不像以前那么容易Get了.视频常规的存储方式有两种: 云端加密 阿里云和腾讯云都提供了对应的加密模式,甚至还提供了管道流的处理 ...

  2. Blockly开发入门指北

    Blockly开发入门指北 [腾讯文档]Blockly开发入门指北 https://docs.qq.com/doc/DRWRDUU5kR2lhaGNN 写这篇文章的目的 最近公司的项目用到了Block ...

  3. javascript模块化之CommonJS、AMD、CMD、UMD、ES6

    javascript模块化之CommonJS.AMD.CMD.UMD.ES6 一.总结 一句话总结: CommonJS是同步加载模块,用在服务端:AMD是异步加载模块,用于浏览器端 1.为什么服务器端 ...

  4. 狼叔直播 Reaction《学习指北:Node.js 2022 全解析》

    大家好,我是若川.持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan02 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列& ...

  5. Javascript模块化编程系列二: 模块化的标准化(CommonJS AMD)

    前言 Javascript模块化编程系列一: 模块化的驱动 在前一篇介绍了为什么要进行Javascript模块化编程.至于如何实现模块化,不同的开发组织和个人具体的实现方式肯定是不一样.如何统一一个规 ...

  6. 无法找到模块“mint-ui”的声明文件_[搬运] JavaScript 模块化:CommonJS vs AMD vs ES6...

    本文主体部分 翻译+搬运 自外网著名技术博客网站 medium.com 的一篇点赞数 2.7k 的文章 (文章链接在结尾处) 什么是 JavaScript 模块 JavaScript 模块指的是一段可 ...

  7. 【博客排版】中文文案排版指北(转载)

    原文链接:中文文案排版指北--GitHub 文章目录 空格 中英文之间需要增加空格 中文与数字之间需要增加空格 数字与单位之间无需增加空格 全角标点与其他字符之间不加空格 `-ms-text-auto ...

  8. 「转」中文文案排版指北

    中文文案排版指北 統一中文文案.排版的相關用法,降低團隊成員之間的溝通成本,增強網站氣質. Other languages: English Chinese Traditional Chinese S ...

  9. 计算机系应届生求职指北

    最近帮了一个朋友的朋友做了下职业规划,结合之前在微博上的一些问答,觉得不少应届生同学对求职有蛮多误解的,所以这里分享下我的一点经验吧.虽然本文题为指北,但只是一个面向对行业.对业界技术不熟悉的同学的操 ...

最新文章

  1. 逆生长!小鼠「逆龄疗法」登Nature子刊,有望用于人类
  2. 辅助模块加速收敛,精度大幅提升 移动端实时的NanoDet-Plus来了
  3. HDU 2152 选课时间(题目已修改,注意读题) (母函数)
  4. mybatis-generator 插件扩展,生成支持多种数据库的分页功能
  5. android 自定义wifi设置在哪里,Android Wifi的设置、连接操作
  6. 错误./hello: error while loading shared libraries: libQtGui.so.4: cannot open shared object file:
  7. go trace 剖析 go1.14 异步抢占式调度
  8. Scala:未受重视却潜力巨大的Android编程语言
  9. Tensorflow:操作执行原理
  10. 吉他谱怎么看?看谱大攻略送上!
  11. DT大数据梦工厂 第72,73讲
  12. VC利用GDI+显示透明的PNG图片
  13. linux怎么用jconsole_linux中jconsole
  14. Java二维码登录流程实现(包含短地址生成,含部分代码)
  15. css3新单位vw、vh、vmin、vmax的使用详解
  16. apmserver导入MySQL_APMServ MySQL 错误
  17. css(六)--css高级技巧
  18. 基于springboot+mybatisplus+vue-科技项目评审及专家库管理系统
  19. VMware蓝屏问题解决方法
  20. 永远不要去依赖别人_不要太依赖一个人说说 永远都不要依赖任何人的经典句子...

热门文章

  1. 【LeetCode笔记】221. 最大正方形(Java、动态规划、思路题)
  2. html表单实验总结,HTML表单总结
  3. python音乐下载器交互界面_基于Python实现下载网易音乐代码实例
  4. jquery 验证小数点后几位_(亲测可用)input只能输入数字或小数点后几位
  5. c++ map iterator 获取key_Java遍历Map的4种方法
  6. verilog换行太长代码_Verilog 之 File I/O task and function
  7. 教授犀利致辞:躺平的韭菜不挨刀,但不挨刀的韭菜做不成佳肴
  8. 走心!北京语言大学教授毕业致辞:在人生的道路上,一定要把自己当回事儿...
  9. 新版:全世界最前沿的125科学问题
  10. 这些黑科技让百姓安心、安全过年