骚年,你对前端模块化了解多少

简介: 不管是前端老司机还是刚接触前端的"菜鸟"。模块化想必在每天工作中,或多或少都会接触到。尤其针对一些针对React、Vue开发的同学来说,那就是每天都会脱口而出的一个必备术语。并且在很多技术文档中,都常常看到AMD、UMD、COMMONJS还有ES6中的module。但是,模块化的本质是什么!前端是如何从"茹毛饮血"的<script>到现在es6的module的呢。今天我们就来唠唠这段鲜为人知的故事。


前言

不管是前端老司机还是刚接触前端的"菜鸟"。模块化想必在每天工作中,或多或少都会接触到。尤其针对一些针对ReactVue开发的同学来说,那就是每天都会脱口而出的一个必备术语。并且在很多技术文档中,都常常看到AMDUMDCOMMONJS还有ES6中的module

但是,模块化的本质是什么!前端是如何从"茹毛饮血"的<script>到现在es6的module的呢。

今天我们就来唠唠这段鲜为人知的故事。

模块化本质

何为模块化。其实就是功能的单一化或者说功能的切片化编程。更直白一点就是,每一个独立的功能都有自己独立的作用域

让我们看看针对模块的英文定义

Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized.

而JS在实现模块代码有如下方式:

  1. 对象字面量
  2. 设计模式中的模块模式
  3. AMD
  4. CommonJS
  5. ES6 module

让我们针对每一个方式来一一说明

对象字面量

由于JS语法本身没有块级作用域的概念(es6之前),所以是没法直接利用{}来将指定的代码进行封装。如果想将特定用于处理类似功能的代码合并到一起。对象字面量不失为一个很好的方式。(有人会说,用函数封装也可以啊,记住JS中一切皆对象)

Talk is cheap ,show you the code

var myModule = {
  myProperty: "北宸",
  // 对象字面量可以包含属性和方法
  // 我们还可以为该模块定义配置信息:
  myConfig: {
    useCaching: true,
    language: "en"
  },
  // 
  saySomething: function () {
    console.log( "你好啊,世界" );
  },
  // 基于配置信息输出一些信息
  reportMyConfig: function () {
    console.log( "缓存: " + ( this.myConfig.useCaching ? "可用" : "禁用") );
  },
  // 重新配置信息
  updateMyConfig: function( newConfig ) {
    if ( typeof newConfig === "object" ) {
      this.myConfig = newConfig;
      console.log( this.myConfig.language );
    }
  }
};
// 你好啊,世界
myModule.saySomething();
//  缓存可用
myModule.reportMyConfig();
//  fr
myModule.updateMyConfig({
  language: "fr",
  useCaching: false
});
//  缓存禁用
myModule.reportMyConfig();
复制代码

从上述代码中,可以看到,将一些操作和数据进行了封装。实现了,功能切片化处理。但是如果细想,感觉利用字面量来封装数据和方法,感觉有点鸡肋。因为这个对象是一个单例。如果只是在一个地方使用和操作,那完全没有问题,但是如果是多个地方都用到呢,同时多个地方都需要对指定的属性进行修改。这就是牵一发而动全身的操作。

同时,我们可以看到利用字面量进行数据和方法封装。这些属性都是对外友好的。都是能在外部访问到的。没有丝毫的隐私,用传统OOP编程术语来讲。这些属性都是public的。也就是说,无法实现属性私有化

模块模式

为了解决字面量无法进行属性私有化。模块模式应用而生。这也是JS语言在早起比较常用的模块化处理方式。

Talk is cheap ,show you the code

var MODULE = (function () {
  var my = {},
    privateVariable = 1;
  function privateMethod() {
    // ...
  }
  my.moduleProperty = 1;
  my.moduleMethod = function () {
    // ...
  };
  return my;
}());
复制代码

从代码上看到,一个IIFE赫然映入眼帘。偷偷的告诉大家,模块模式就是利用IIFE实现的。

为了不占用很大篇幅来讲解这个实现。特定为大家准备了饭后甜点。JS_Module模式深入了解一下

AMD

其实AMD(Asynchronous Module Definition)是一种为浏览器环境书写模块的模式。 而能够实现异步加载的关键就在于RequireJS

RequireJS是在ES6module没出现之前,常用的前端模块解决方案。RequireJS将加载的每一个独立模块作为<script>,并利用head.appendChild()追加到文档中。

RequireJS等待所有依赖模块加载,将该模块需要的额外模块进行排序,并在依赖模块加载完之后,调用本模块的定义函数。

Talk is cheap,show you the code

在项目中存在如下结构,我们用cart.jsinventory.js来构建一个shirt.js

  • my/cart.js
  • my/inventory.js
  • my/shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
        //返回一个对象用于定义"my/shirt"模块
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);
复制代码

当然,上述中的本地模块cart.js也可以换成JQuery等现成的模块。

如果想对AMD有一个更深的了解,或者想知道如何定义一个AMD模块。可以先移步RequireJS官网。

CommonJS

JS有一条定律:Atwood's Law

any application that can be written in JavaScript, will eventually be written in JavaScript.

JS是可以在服务端存在,所以出现了CommonJS(A Module Format Optimized For The Server),使得JS不仅仅在浏览器端应用,而且在服务端开始发光发热

CommonJS是专注于

  1. 服务端应用
  2. 公共工具方法
  3. 基于GUI的桌面程序
  4. 混合应用 (Titanium, Adobe AIR)

AMD不是一个服务层面。

Talk is cheap ,show you the code

a.js

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
复制代码

上面代码通过module.exports输出变量x和函数addX。

b.js

var example = require('./a.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
复制代码

commonJS模块特点

  1. 每个文件就是一个模块,它有自己的作用域(不会污染全局作用域)。在文件里面定义的变量、函数、类,都是这个模块的私有的,对外不可见。
  2. 模块加载顺序,按照代码执行顺序。也就是说,是同步加载的。
  3. 模块可以重复加载,但是会在加载第一次的时候,就将该模块缓存起来,后面再次加载将从缓存中获取该模块。(注意:如果要重新加载模块,需要清空缓存)

ES6 module

在ES6中,从语法层面就提供了模块化的功能。然而受限于浏览器的实现程度,如果想要在浏览器中运行,还是需要通过Babel等转译工具进行编译。

person-module.js

var firstName = '北宸';
var lastName = '范';
export { firstName, lastName };
复制代码

test-module.js

import {firstName,lastName} from './person-module.js';
console.log(`${lastName}${firstName}`)//范北宸
复制代码

具体细节请参考Module的用法

各个模块比较

模块化方案 加载 同步/异步 浏览器 服务端 模块定义 模块引入
Module Pattern 取决于代码 取决于代码 支持 支持 IIFE 命名空间
AMD 提前预加载 异步 支持 构建工具r.js define require
Common 值拷贝,运行时加载 同步 原生不支持 需要使用browserify提前打包编译 module.exports require
ES Modules(ES6) 实时绑定,动态绑定,编译时输出 同步 支持 需用babel转译 export import

本文参考链接:

  1. requirejs.org/docs/api.ht…
  2. addyosmani.com/resources/e…
  3. es6.ruanyifeng.com/#docs/modul…
  4. www.commonjs.org/
  5. www.cnblogs.com/scq000/p/10…
相关文章
|
5月前
|
资源调度 前端开发 JavaScript
构建高效前端项目:现代包管理器与模块化的深度解析
【2月更文挑战第21天】 在当今快速演变的前端开发领域,高效的项目管理和代码组织已成为成功交付复杂Web应用的关键。本文将深入探讨现代前端包管理器如npm, yarn和pnpm的工作原理,以及它们如何与模块化编程实践(例如CommonJS、ES6模块)协同工作以优化开发流程。我们将剖析这些工具的内部机制,了解它们如何解决依赖冲突,提高安装速度,并保证项目的健壮性。同时,本文还将介绍模块化编程的最佳实践,包括代码拆分、重用和版本控制,帮助开发者构建可维护且性能卓越的前端项目。
|
5月前
|
前端开发 JavaScript jenkins
构建高效前端项目:从模块化到自动化
【2月更文挑战第13天】 随着Web技术的不断进步,前端项目的复杂性日益增加。为了确保可维护性和性能,前端工程师必须采用模块化和自动化的策略来优化开发流程。本文将探讨如何使用现代前端工具和最佳实践来构建一个高效的前端项目架构,包括模块打包、代码分割和持续集成等方面。
|
2月前
|
前端开发 JavaScript
前端必会的JavaScript模块化
【8月更文挑战第3天】JavaScript模块化
23 1
|
4月前
|
JavaScript 前端开发
必知的技术知识:esm前端模块化
必知的技术知识:esm前端模块化
80 0
|
5月前
|
前端开发 JavaScript 安全
【Web 前端】怎么实现Module模块化?
【5月更文挑战第1天】【Web 前端】怎么实现Module模块化?
|
5月前
|
JavaScript 算法 前端开发
【专栏】前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式
【4月更文挑战第29天】本文探讨了前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式。slot算法允许在组件中定义插槽位置,实现内容的灵活插入和复用,提高代码可读性和维护性。shadow DOM则通过封装DOM子树,实现样式和事件的隔离,增强组件独立性和安全性。这两种技术常应用于组件开发、页面布局和主题定制,但也面临兼容性、学习曲线和性能优化等挑战。理解并掌握它们能提升开发效率和用户体验。
91 2
|
5月前
|
前端开发 JavaScript 安全
前端模块化发展
前端模块化发展
|
5月前
|
JavaScript 前端开发 开发者
【Web 前端】JS模块化有哪些?
【4月更文挑战第22天】【Web 前端】JS模块化有哪些?
|
5月前
|
前端开发 JavaScript 内存技术
Node-前端模块化
Node-前端模块化
42 0
Node-前端模块化
|
5月前
|
前端开发 JavaScript 测试技术
构建高效前端项目:模块化与组件化策略
【2月更文挑战第25天】 在现代网页开发中,随着用户对于网页性能和交互体验的期待不断提升,前端项目的复杂性也随之增加。为了应对这种复杂性并提高开发效率,本文将探讨模块化与组件化在前端项目中的应用策略。通过分析这两种方法的优势与适用场景,我们将揭示如何利用它们来优化项目结构,提升代码复用率,以及加快开发流程。
133 4