阅读 787

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

前言

不管是前端老司机还是刚接触前端的"菜鸟"。模块化想必在每天工作中,或多或少都会接触到。尤其针对一些针对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…