【第22题】理解 JS 模块化

424 阅读6分钟

题目描述

简单描述一下你所了解的JS模块化

答案解析

这道题目主要考察对JS模块化发展历程的了解,以及发展中出现的一些规范和技术的掌握。

模块化是每一种语言膨胀的毕竟之路,JS 也不例外,从原来的不支持模块化,到现在支持模块化,经历了一个曲折的过程。

JS 模块化产生的原因

以前JS本身没有模块化的概念和相关API,开发者一般都是在html中引入多个script标签,业务逻辑复杂时,就会带来很多问题,比如:

  1. 全局作用域下容易造成变量冲突
  2. 文件只能按照 script的书写顺序进行加载
  3. 开发人员必须主观解决模块和代码库的依赖关系
  4. 在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪

模块化的作用

它可以帮助开发者拆分和组织代码,方便复用功能,降低项目开发的复杂度,提高可维护性。

模块化方案及规范

一、闭包

在模块化规范形成之前,JS 开发者通常利用闭包来解决全局作用域的污染问题,一般是通过自定义暴露行为来区分私有成员和公有成员。

let myModule = (function (window) {
    let name = '前端名狮'  // private
    // public
    function setName(str) {
      name = str
    }
    // public
    function getName() {
      return name
    }
    return { setName, getName }  // 暴露行为
})(window)

二、CommonJS

JS 一开始只能运行于浏览器中,所以一直被诟病。为了突破JS的运行环境,CommonJS 规范出现了,它定义了一些规范和API(主要指非浏览器端的),使 JS 语言拥有了类似Python、Java等后端编程语言的能力。这样的话,开发者可以使用CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志"Javascript模块化编程"正式诞生。NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写的。

node 模块化示例:

// math.js 文件
let math = {
    add: function(a, b) {
        console.log(a + b);
    }
}

module.exports = math;

// app.js 文件
let math = require('./math.js');
math.add(1, 1);

三、AMD && CMD

基于commonJS规范的nodeJS出来以后,服务端的模块概念已经形成,如果将其应用到浏览器端,会出现什么问题呢?

let math = require('./math.js');
math.add(1, 1);

上面代码执行require的时候,需要等math.js文件加载完成,才能继续执行,如果加载时间过长,页面就会出现“假死”状态。

这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,文件的加载需要通过HTTP请求,等待时间取决于网速的快慢,可能要等很长时间。

CommonJS是主要为了JS在后端的表现制定的,是不适合前端的 ,所以出现了AMD、CMD、UMD等等一系列可以在浏览器等终端实现的异步加载的模块化方案**,我们最熟悉的require.js就是AMD的产物,seajs是CMD的产物。**

// AMD
require(['a', 'b'], function(math) {
 a.doSomething();  
 b.doSomething();
});

// CMD

define(function(require, exports, module) {  
    var a = require('./a')  
    a.doSomething();  
    ...
    var b = require('./b')   // 依赖可以就近书写  
    b.doSomething();
})

AMD 推崇依赖前置,CMD 推崇依赖就近。

上面这句话的意思就是:CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

四、ES6 Module

ES6的模块化已经不是规范了,而是JS语言的特性。随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:

  1. 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. 模块化规范是运行时加载,ES6 模块是编译时输出接口。

模块化规范输出的是一个对象,该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,ES6 module 是一个多对象输出,多对象加载的模型。从原理上来说,模块化规范是匿名函数自调用的封装,而ES6 module则是用匿名函数自调用去调用输出的成员。

注意:目前的浏览器几乎都不支持 ES6 的模块机制,所以我们要用 Babel 把 ES6 的模块机制转换成 CommonJS 的形式,然后使用 Browserify 或者 Webpack 这样的打包工具把他们打包起来。达到模块化加载效果类似于 seajs代码

展望

1. Http 2.0 对 js 模块化的推动

js 模块化定义的再美好,浏览器端的支持粒度永远是瓶颈,http 2.0 正是考虑到了这个因素,大力支持了 ES 2015 模块化规范。

随着 HTTP/2 流行起来,请求和响应可以并行,一次连接允许多个请求,对于前端来说,不再需要在开发和上线时再做编译这个动作。

ES2015 Modules 也只是解决了开发的问题,由于浏览器的特殊性,还是要经过繁琐打包的过程,等 Import,Export 和 HTTP 2.0 被主流浏览器支持,那时候才是彻底的模块化。

前端复杂度不断提高,促使着模块化的改进,代理(浏览器、node) 的支持程度,与前端特殊性(流量、缓存)可能前端永远也离不开构建工具,新的标准会让这些工作做的更好,同时取代、增强部分特征,前端的未来是更加美好的,复杂度也更高。

2. html、css模块化

文章中的 JS 的模块化还不等于前端工程的模块化,Web 界面是由 HTML、CSS 和 JS 三种语言实现,不论是 CommonJS 还是 AMD 包括之后的方案都无法解决 CSS 与 HTML 模块化的问题。

html 与 css 模块化问题正是以后的方向。一句话,模块化仍在路上。

关注我

扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!