简单描述一下你所了解的JS模块化
这道题目主要考察对JS模块化发展历程的了解,以及发展中出现的一些规范和技术的掌握。
模块化是每一种语言膨胀的毕竟之路,JS 也不例外,从原来的不支持模块化,到现在支持模块化,经历了一个曲折的过程。
JS 模块化产生的原因
以前JS本身没有模块化的概念和相关API,开发者一般都是在html中引入多个script标签,业务逻辑复杂时,就会带来很多问题,比如:
script
的书写顺序进行加载模块化的作用
它可以帮助开发者拆分和组织代码,方便复用功能,降低项目开发的复杂度,提高可维护性。
模块化方案及规范
一、闭包
在模块化规范形成之前,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模块与模块化规范相比,有两大特点:
模块化规范输出的是一个对象,该对象只有在脚本运行完才会生成。而 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 模块化问题正是以后的方向。一句话,模块化仍在路上。