前端模块化

前端模块化

前端模块化,使得开发体验大大增强,摆脱了很多需要人力去做切容易出错的点,使得代码管理更加清晰规范。文件按需加载,依赖自动管理,使得更多的精力去关注模块代码本身,开发时不需要在页面上写一大堆 <script>标签,不需要每增加一个文件,还要到HTML或者其他地方添加一个<script>标签或文件声明。减少命名冲突,消除全局变量;一个模块一个文件,组织更清晰;依赖自动加载,按需加载。

模块化标准及其比较

  1. 一切源于CommonJS,是服务器模块的规范,Node.js 采用了这个规范。文件即模块,每一个模块都是一个单独的作用域,在一个文件定义的变量(还包括函数)都是私有的,对其他文件是不可见的。使用require引用和加载模块,exports定义和导出模块,module标识模块,使用require时需要去读取并执行该文件,然后返回exports导出的内容。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,因此CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这种同步操作很耗时,阻塞后续代码的运行,并不适用。这时就必须采用非同步模式,从而在浏览器端由CommonJS衍生出两大分支:AMD(异步模块定义)和CMD(通用模块定义)。

  2. AMD & RequireJS:通过define(模块名,依赖模块数组,回调函数)来定义模块,使用提前异步加载依赖模块的方式,模块加载完毕后执行回调函数。等到所有前置依赖加载并执行完毕,再回调主要的代码逻辑。通过参数传入依赖模块,浏览器提前下载这些模块,破坏了就近声明原则。

  3. CMD & SeaJS:SeaJS是采用的就近依赖的方式来加载模块,一般define(function(require, exports, module){}),在factory里就近加载依赖模块;本质上也是异步的加载模块,只是和RequireJS相比加载和执行的时机不一样。

  4. UMD (通用模块规范):对AMD和CommonJS规范的整合,实现对JS模块化的跨平台。先判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式;再判断是否支持AMD(define是否存在), 存在则使用AMD方式加载模块;前两个都不存在,则将模块公开到全局(window或global)。

  5. ES6的规范化:ES6作为JavaScript新的标准,自带了模块化的buff,通过import和export导入导出模块;尽量并不是UMD, 但它还是尽量在兼容现有的AMD和CommonJS模块, 支持导入这两者的模块。 它要更优雅一些, 它有所超越, 原生的语法和关键字也更方便。 前端模块管理工具:npm + webpack

SeaJS对模块的态度是懒执行,SeaJS只会在真正需要使用(依赖)模块时才执行该模块;而RequireJS对模块的态度是预执行;会先尽早地执行(依赖)模块, 相当于所有的require都被提前了, 而且模块执行的顺序也不一定

模块加载器的实现原理( SeaJS )

模块化开发之sea.js实现原理简言之就是要解决三个问题,分别为:

  1. 模块加载(插入script标签来加载模块。你在页面看不到标签是因为模块被加载完后删除了对应的script标签);
  2. 模块依赖(按依赖顺序依赖);
  3. 命名冲突(封装一层define,所有的都成为了局部变量,并通过exports暴漏出去)。

简单的模块化,可以用闭包来做,但是闭包只能解决模块加载中第一个问题,变量名冲突的问题,但是一个成熟的模块加载系统是需要两个部分。

  1. 创建隔离的代码块,不会出现变量名冲突的问题
  2. 解决模块与模块之间的依赖问题

在seajs中模块化并不是通过闭包来实现的,而是通过js对象来实现的,一个模块就是一个对象,这个对象拥有一个id,一般用url的相对路径来作为它的id,但是你也可以创建命名的模块。模块下载下来之后,并不会立刻执行你写的整块代码,因为你写的整块代码,都会被一个define函数包裹,首先执行define函数,你所写的代码都会以参数的形式传入define函数,define函数会处理这些代码,第一步正则匹配,找出你代码里的依赖,如果有依赖,就会去处理这些依赖,等依赖加载完了之后,才会去执行全部的代码,最后暴露相应的接口,整个模块就已经准备好了。

入口 js 文件 <script src="sea.js"></script>写在页面的尾部,整个页面已经加载完成之后seajs再加载其他的模块,并不会阻塞页面从入口 js 往里检查依赖。正则匹配,分析依赖关系,建立脚本加载队列、递归加载。

  1. 动态创建脚本 createElement(‘script’)和appendChild(script) 动态创建脚本,添加到head元素中。
  2. 分析依赖建立 每个模块可能会依赖不同的模块,我们需要理清楚这些模块之间的依赖关系,然后分别将它们加载进来。为了分析依赖关系,我们使用toString的方法,将模块转化为一个string,然后去其中寻找依赖。 fn.toString().match(/.require((“|’)[^)]*(“|’))/g) 将模块转换为字符串,然后通过正则表达式,匹配每个模块中的的依赖文件。
  3. 建立脚本加载队列、递归加载。
    seajs

ES6 的模块加载实现

  1. 使用