https://ponyfoo.com/articles/brief-history-of-modularity

这篇文章,我们会快速回顾和总结Javascript世界中的模块化的里程碑事件。这篇文章不会完整的列出所有的事件,而是回顾模块化发展历史中那些重要的大事件。

Script标签和闭包

在以前,Javascript写在HTML的<script>标签里面,或者好一点,写在单独的Javascript文件里面,它们都共享一个全局作用域。

在这些文件或者标签中声明的变量,都会关联到全局的window对象。这种情况下,会出现很多意外的错误,甚至会导致应用崩溃。比如,在一个script中,意外的覆盖了之前声明的变量名称。

最终,由于web应用日渐壮大和复杂化,全局域会很危险是尽人皆知的。然后就引入了著名的即时调用函数表达式(IIFE)。IIFE会把一个文件,或者文件的部分代码包裹进入一个函数,然后在定义函数之后立即执行它。Javascript中的每个函数都会创建一个单独的域,意味着var声明的变量将会绑定在IIFE内部,不会变成全局变量。

多谢IIFE,它帮助我们止住了Javascript隐式全局作用域带来的痛苦。

下面的例子中,是几个不同风格的IIFE。每个IIFE中的代码都是隔离的,如果要访问和赋值全局变量,需要显式地使用类似window.fromIIFE = true的表达式。

(function() {
console.log('IIFE using parenthesis')
})() ~function() {
console.log('IIFE using bitwise operator')
}() void function() {
console.log('IIFE using the void operator')
}()

使用IIFE后,一些JS库可以创建模块,只暴露一些公共API给window对象,因此最小化了全局命名的冲突。

在下面的例子中,我们创建了一个mathlib组件,它有一个sum方法,这是一个基于IIFE的库。如果你想要为mathlib增加更多的模块,我们可以将这些模块放到单独的IIFE,只要最后将它们加入到公共的mathlib接口就好了。

void function() {
window.mathlib = window.mathlib || {}
window.mathlib.sum = sum function sum(...values) {
return values.reduce((a, b) => a + b, 0)
}
}()

IIFE的缺点是,它没有明显的依赖树。这意味着,开发者必须手动将组件文件以正确的顺序导入。

RequireJS, AngularJS以及依赖注入

我们遇到的困难,一些模块系统以及考虑到了。比如RequireJS系统,或者类似AngularJS之类的依赖注入机制,它们都允许我们为每个模块的依赖显示地加入名称。

下面的例子,我们使用RequireJS的define函数,在mathlib/sum.js库中定义了一个方法,define函数是全局函数。

define函数返回的值,最后会加入到我们模块的公共接口。

define(function() {
sum: (...values) => {
return values.reduce((a, b) => a + b, 0)
}
})

然后,我们就有了一个mathlib.js模块,它聚集了我们所有想要的函数。在这个例子中,它还只有mathlib/sum方法,但是我们还可以用同样的方法加入更多依赖。我们使用一个array,将依赖的path放在array中,然后我们可以在callback中获得公共接口作为参数,顺序和array中一样。

define(['mathlib/sum'], function(sum) {
return { sum }
})

现在,我们定义了一个库,我们可以使用require来声明使用这个库。

require(['mathlib'], function(mathlib) {
mathlib.sum(1, 2, 3) // -> 6
})

无论我们的应用是否包含成百上千个模块,RequireJS都会解决内在的依赖树,不需要我们担心依赖列表的顺序。手动做这种时间非常枯燥,并且很容易犯错。

依赖使用显式地声明,是让它们可以显而易见,可以看到一个应用中的组件是如何与其它组件关联的。这种显式的特点,推动了模块化向前走出巨大一步,之前最难的就是很难搞清依赖链条。

RequireJS也不是没有问题。主要的问题就是模块的异步加载,很难对生产环境部署。使用异步加载机制,你的代码会在执行前完成上百个网络requests。生产环境需要使用另一个优化工具。用法很复杂。

AngularJS和类似的依赖注入系统,也有一些问题。这个机制和minifiers不兼容。

在AngularJS v1的晚些时候,引入了一个构建任务,会把下面这种代码:

module.factory('calculator', function(mathlib) {
...
})

转换为兼容manification兼容的形式:

module.factory('calculator', ['mathlib', function(mathlib) {
// …
}])

Node.js以及CommonJS的出现

随着Node.js的出现,还有很多创新也出现了,CommonJS就是其中之一。

因为Node.js程序可以访问文件系统,所以CommonJS标准更像传统的模块载入系统。

在CommonJS中,每个文件都是一个模块,有着自己的域和上下文。依赖通过同步的require函数来载入,它可以在模块中的任何时候,动态的加入:

const mathlib = require('./mathlib')

和RequireJS和AngularJS很像,CommonJS的依赖也以pathname来引用。唯一的不同是,那个繁琐的函数调用方式,以及依赖数组都不再需要了,另外一个模块的接口可以被赋值给一个变量,或者直接用在Javascript表达式中。

不像RequireJS和AngularJS的是,CommonJS是相当严格的。在RequireJS和AngularJS中,每个文件可能会出现很多动态定义的模块,而CommonJS的文件和模块是一对一映射的。另外,RequireJS有很多方式来声明模块,AngularJS有很多的factories,services,provides。。。而CommonJS,只有一种方式来声明模块。任何Javascript文件都是模块,调用require可以载入依赖,任何赋值给module.exports的都是它的接口。

最终,为了嫁接Node.js服务器和浏览器的桥梁,Browserify出现了。使用browserify命令行接口,为它提供每个入口模块的路径,它会将这些模块都打包到一个bundle文件。CommonJS的这个杀手特性,以及npm package registry,共同架构起了繁荣的node.js模块生态。

ES6, import, Babel和Webpack

ES6在2015年6月成为标准,Babel在这之前就可以将ES6转译为ES5,新一代革命悄然临近。ES6规范,为Javascript带来了原生的模块系统,一般称为ECMAScript Modules(ESM)。

ESM受到CommonJS和其它先驱者很大的影响,提供静态声明API,以及基于promise的程序化API:

import mathlib from './mathlib'
import ('./mathlib').then(mathlib => {
// ...
})

在ESM中,每个文件都是模块,有者自己的域和上下文。

ESM相对于CommonJS最大的优势在于,它有且鼓励使用一种静态引入依赖的方式。静态引入提升了模块系统的内窥能力,让系统可以使用AST来为每个模块进行静态分析,词法提取。ESM中的静态引入限定在模块的顶部,可以更加简化解析和内窥。

在Node.js v8.5.0中,ESM模块系统被引入。很多浏览器现在也支持了ESM模块系统。

Webpack是Browserify的继承者。和Babel与ES6一样,Webpack长期支持ESM,包括importexport语句,以及动态的import()函数。另外,它还有令人震惊的代码分割功能,可以将一个应用的代码分割成多个bundle文件,提升应用的载入效率,加强用户体验。

因为语言有了原生的ESM,所以CommonJS将会在未来几年逐渐消失,感谢它做的贡献。

Javascript模块化简史的更多相关文章

  1. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

  2. Javascript模块化编程(二):AMD规范

    Javascript模块化编程(二):AMD规范   作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...

  3. Javascript模块化编程(一):模块的写法

    Javascript模块化编程(一):模块的写法 作者: 阮一峰 原文链接:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html ...

  4. Javascript模块化编程(二):AMD规范(转)

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要 ...

  5. Javascript模块化编程(一):模块的写法(转)

    随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个团队分工协作.进度管理.单元测试等等......开发者 ...

  6. Javascript模块化规范

    Javascript模块化规范 一.前端js模块化由来与演变 CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践.09年下 ...

  7. Javascript模块化开发,使用模块化脚本加载工具RequireJS,提高你代码的速度和质量。

    随着前端JavaScript代码越来越重,如何组织JavaScript代码变得非常重要,好的组织方式,可以让别人和自己很好的理解代码,也便于维护和测试.模块化是一种非常好的代码组织方式,本文试着对Ja ...

  8. Javascript 模块化开发上线解决方案

    最近又换部门了,好频繁地说...于是把这段时间搞的小工具们简单整理了一下,作了一个小的总结.这次用一个简单业务demo来向大家介绍一下Javascript模块化开发的方式和自动化合并压缩的一些自己的处 ...

  9. Javascript模块化编程(二):AMD规范 作者: 阮一峰

    声明:转载自阮一峰的网络日志 这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. (接上文) 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可 ...

随机推荐

  1. Bootstrap开发框架界面的调整处理

    我在之前介绍了很多关于Boostrap的框架方面的文章,主要是介绍各种插件的使用居多,不过有时候觉得基于Metronic的Boostrap框架的界面效果不够紧凑,希望对它进行一定的调整,那么我们应该如 ...

  2. 全国天气预报信息数据 API 功能简介与代码调用实战视频

    此文章对开放数据接口 API 之「全国天气预报信息数据 API」进行了功能介绍.使用场景介绍以及调用方法的说明,供用户在使用数据接口时参考之用,并对实战开发进行了视频演示. 1. 产品功能 接口开放了 ...

  3. VisualStudio2017下ASP.NET CORE的TagHelper智能提示不能使用的解决办法

    之前在VS2017RC中就发现该问题,安装了依赖,但是前段一直点不出来asp-for,后来查了发行说明, 才知道在VS2017rc中暂时无法解决,所以一直等到VS2017正式版的发布,急冲冲的装好, ...

  4. day4(分支结构,循环结构,for循环,九九乘法表)

    一:复习 ''' 1.变量名命名规范 -- 1.只能由数字.字母 及 _ 组成 -- 2.不能以数字开头 -- 3.不能与系统关键字重名 -- 4._开头有特殊含义 -- 5.__开头__结尾的变量, ...

  5. 关于H5页面中生成图片的两种方式!

    前言: 我们在做项目过程中,经常会遇到自定义生成一张图片并可以长按保存.长按保存图片在微信等浏览器中默认就有,那么对于生成图片的有哪些方式呢? 方法一: 利用canvas绘制图形,然后生成图片 代码如 ...

  6. PS绘制飘逸彩色丝带教程

    一.新建一个大小适当的图像,点击工具栏上的钢笔工具,使用形状图层来绘制出下图的形状. 二.把形状所在层的填充设为0%,填充设成0是不会影响到图层的,不像不透明度那样会影响图层样式的效果. 三.双击丝带 ...

  7. mybatis 使用接口绑定

    使用selectList,selectOne..的缺陷 刚开始学习mybatis的时候,使用selectList或者selectOne,传入要调用的mapper,如果又参数要传递的话,就需要将参数进行 ...

  8. python多线程和多进程

    1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发 ...

  9. php函数 array_chunk

    array_chunk ( array $array , int $size [, bool $preserve_keys = false ] ) : array 将一个数组分割成多个数组,其中每个数 ...

  10. Express使用art-template模板引擎

    第一步:安装 npm install --save art-template npm install --save express-art-template 第二步:指定.html使用的解析引擎(官方 ...