这篇文章,咱们会快速回顾和总结Javascript世界中的模块化的里程碑事件。这篇文章不会完整的列出全部的事件,而是回顾模块化发展历史中那些重要的大事件。html
在之前,Javascript写在HTML的<script>
标签里面,或者好一点,写在单独的Javascript文件里面,它们都共享一个全局做用域。node
在这些文件或者标签中声明的变量,都会关联到全局的window
对象。这种状况下,会出现不少意外的错误,甚至会致使应用崩溃。好比,在一个script中,意外的覆盖了以前声明的变量名称。web
最终,因为web应用日渐壮大和复杂化,全局域会很危险是尽人皆知的。而后就引入了著名的即时调用函数表达式(IIFE)。IIFE会把一个文件,或者文件的部分代码包裹进入一个函数,而后在定义函数以后当即执行它。Javascript中的每一个函数都会建立一个单独的域,意味着var
声明的变量将会绑定在IIFE内部,不会变成全局变量。npm
多谢IIFE,它帮助咱们止住了Javascript隐式全局做用域带来的痛苦。数组
下面的例子中,是几个不一样风格的IIFE。每一个IIFE中的代码都是隔离的,若是要访问和赋值全局变量,须要显式地使用相似window.fromIIFE = true
的表达式。promise
使用IIFE后,一些JS库能够建立模块,只暴露一些公共API给window
对象,所以最小化了全局命名的冲突。浏览器
在下面的例子中,咱们建立了一个mathlib
组件,它有一个sum
方法,这是一个基于IIFE的库。若是你想要为mathlib
增长更多的模块,咱们能够将这些模块放到单独的IIFE,只要最后将它们加入到公共的mathlib
接口就行了。服务器
IIFE的缺点是,它没有明显的依赖树。这意味着,开发者必须手动将组件文件以正确的顺序导入。网络
咱们遇到的困难,一些模块系统以及考虑到了。好比RequireJS系统,或者相似AngularJS之类的依赖注入机制,它们都容许咱们为每一个模块的依赖显示地加入名称。闭包
下面的例子,咱们使用RequireJS的define
函数,在mathlib/sum.js
库中定义了一个方法,define函数是全局函数。
define
函数返回的值,最后会加入到咱们模块的公共接口。
而后,咱们就有了一个mathlib.js
模块,它汇集了咱们全部想要的函数。在这个例子中,它还只有mathlib/sum
方法,可是咱们还能够用一样的方法加入更多依赖。咱们使用一个array,将依赖的path放在array中,而后咱们能够在callback中得到公共接口做为参数,顺序和array中同样。
如今,咱们定义了一个库,咱们可使用require
来声明使用这个库。
不管咱们的应用是否包含成百上千个模块,RequireJS都会解决内在的依赖树,不须要咱们担忧依赖列表的顺序。手动作这种时间很是枯燥,而且很容易犯错。
依赖使用显式地声明,是让它们能够显而易见,能够看到一个应用中的组件是如何与其它组件关联的。这种显式的特色,推进了模块化向前走出巨大一步,以前最难的就是很难搞清依赖链条。
RequireJS也不是没有问题。主要的问题就是模块的异步加载,很难对生产环境部署。使用异步加载机制,你的代码会在执行前完成上百个网络requests。生产环境须要使用另外一个优化工具。用法很复杂。
AngularJS和相似的依赖注入系统,也有一些问题。这个机制和minifiers不兼容。
在AngularJS v1的晚些时候,引入了一个构建任务,会把下面这种代码:
转换为兼容manification兼容的形式:
随着Node.js的出现,还有不少创新也出现了,CommonJS就是其中之一。
由于Node.js程序能够访问文件系统,因此CommonJS标准更像传统的模块载入系统。
在CommonJS中,每一个文件都是一个模块,有着本身的域和上下文。依赖经过同步的require
函数来载入,它能够在模块中的任什么时候候,动态的加入:
和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模块生态。
import
, Babel和WebpackES6在2015年6月成为标准,Babel在这以前就能够将ES6转译为ES5,新一代革命悄然临近。ES6规范,为Javascript带来了原生的模块系统,通常称为ECMAScript Modules(ESM)。
ESM受到CommonJS和其它先驱者很大的影响,提供静态声明API,以及基于promise的程序化API:
在ESM中,每一个文件都是模块,有者本身的域和上下文。
ESM相对于CommonJS最大的优点在于,它有且鼓励使用一种静态引入依赖的方式。静态引入提高了模块系统的内窥能力,让系统可使用AST来为每一个模块进行静态分析,词法提取。ESM中的静态引入限定在模块的顶部,能够更加简化解析和内窥。
在Node.js v8.5.0中,ESM模块系统被引入。不少浏览器如今也支持了ESM模块系统。
Webpack是Browserify的继承者。和Babel与ES6同样,Webpack长期支持ESM,包括import
,export
语句,以及动态的import()
函数。另外,它还有使人震惊的代码分割功能,能够将一个应用的代码分割成多个bundle文件,提高应用的载入效率,增强用户体验。
由于语言有了原生的ESM,因此CommonJS将会在将来几年逐渐消失,感谢它作的贡献。
本文转载自原文:https://www.cnblogs.com/thomaszdxsn/p/Javascript-mo-kuai-hua-jian-shi.html