JavaScript中模块的发展历程

409 阅读4分钟
一、模块的重要性

Javascript不是一种模块化编程语言,它不支持”类”(class),更别说”模块”(module)了。
开发者们做了很多努力,在现有的运行环境中,实现”模块”的效果。

没有模块的日子里

function a() {...};
function b() {...};
function c() {...};

后果:

全局变量的灾难;
函数命名冲突;
依赖关系不好管理。

js模块的前身(为了解决以上问题)

1. 面对对象的写法:

var obj = {
    a: 1,	
    b: function() {
        ...
    }
};

优点:
1、解决了变量污染的问题;
2、保证模块名唯一即可,建立同一模块内的成员的关系。
缺点:
1、暴露所有模块成员,内部状态可以被外部任意改写。

obj.a = 100;

2. 匿名自执行函数:

(function() {
    var obj = {	
        a: 1,
        b: function() {
            ...
        }
    };
})();

优点:
1、解决暴露所有模块成员,内部状态可以被外部任意改写的问题。
缺点:
1、所需依赖还是得外部提前提供。

二、commonJS

2009年,对js是历史性的一年,nodeJS横空出世,让js跑在服务端,如果说js在浏览器上面可以没有模块,但是在服务端没有模块的思想是万万不能容忍的。
由Mozilla 的工程师 Kevin Dangoor 在2009年1月创建了commonJS规范。

// 创建模块one.js
var a = 'aaaa';
function b() {
    console.log(a);
};
module.exports = {
    a: a,
    b: b
};
// 加载模块two.js
var x = require('./one.js');
x.b();
优点:

1、所有代码都运行在模块作用域,不会污染全局作用域;
2、独立性是模块的重要特点就,模块内部最好不与程序的其他部分直接交互;
3、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存;
4、模块加载的顺序,按照其在代码中出现的顺序。
node推广了commonJS规范,但是在浏览器中又出现了很多问题
问题:
浏览器资源的加载方式与服务端完全不同。服务端require一个模块,直接就从硬盘或者内存中读取了,消耗的时间可以忽略。而浏览器则不同,需要从服务端来下载这个文件,然后运行里面的代码才能得到API,需要花费一个http请求,也就是说,require后面的一行代码,需要资源请求完成才能执行。
由于浏览器端是以插入script标签的形式来加载资源的(ajax方式不行,有跨域问题)没办法让代码同步执行,所以像commonjs那样的写法会直接报错。
这意味着要想适应浏览器,规范还要改进!!!
一个Modules/Wrappings规范出现了。

三、AMD

经过一番谈论、修改(过程忽略),AMD思想出现了…
AMD(Asynchronous Module Definition):

  • 异步模块定义规范制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。
  • 依赖前置,预执行(异步加载:依赖先执行,依赖必须一开始就写好,会先尽早地执行(依赖)模块 。换句话说,所有的require都被提前执行(require 可以是全局或局部 )。
  • 相关Api(简单实例):

    // 定义和暴露模块
    define("xxx", ["xxx", "xxx"], function(x, x) {  
        return ... ;
    });
    // 加载模块
    require(["xxx", "../xxx"], function(xxx, xxx) {	
        xxxx
    });

具体Api请参见:AMD (中文版)

RequireJS
说完AMD,就不得不提把AMD在浏览器实现的RequireJS。
RequireJS是一个JavaScript文件和模块加载器,采用AMD规范。
参考文件:RequireJS

三、CMD

CMD(Common Module Definition):

  • CMD更贴近 CommonJS Modules/1.1 和 Node Modules 规范,一个模块就是一个文件;
  • 它推崇依赖就近,想什么时候 require 就什么时候加载,实现了懒加载(延迟执行 );
  • 它也没有全局 require, 每个API都简单纯粹;
  • 不过RequireJS从2.0开始,也改成可以延迟执行。
  • 相关Api(简单实例):

    // 定义和暴露模块
    define('xxx', ['xxx'], function(xxx, xxx, xxx) {
        return ... ;
    });
    // 加载模块
    define(function(require, exports) {
        // 获取模块 a 的接口
        var a = require('./a');
        // 调用模块 a 的方法
        a.doSomething();
    });

seaJS

  • SeaJS遵循的CMD,将CMD在浏览器中实现;
  • SeaJS 是一个模块加载器;
  • 借鉴了 RequireJS 的不少东西

seajs官网

其他

UMD

既然CommonJs和AMD风格一样流行,似乎缺少一个统一的规范。所以人们产生了这样的需求,希望有支持两种风格的“通用”模式,于是通用模块规范(UMD)诞生了。

es6 modules

  • ES6自带了模块化, 也是JS第一次支持module;

后记

本文主要记述模块的思想在浏览器的实现过程,不是一篇详细的Api教程。