前端模块化演变

一、早期在没有工具和规范的情况下,对模块化的发展

阶段一:基于文件的划分模块的方式

实现:将每个功能及其相关状态数据各自单独放到不同的文件中,约定每个文件就是一个独立的模块,使用某个模块就是将这个模块引入到页面中,然后直接调用模块中的成员
缺点:所有模块都直接在全局工作,没有私有空间,所有成员都可以在模块外部被访问或修改,而且模块一段多了过后,容易产生命名冲突,另外无法管理模块与模块之间的依赖关系。

// module-a
var module_name = 'module-a'
function method1 () {
    console.log(module_name + '#method1')
}
// module-b
var module_name = 'module-b'
function method1 () {
    console.log(module_name + '#method1')
}
// index.html
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        // 命名冲突,这里module-a 和 module-b 的方法相冲突
        method1()
        // 模块 a、b 内的成员可以被修改
        module_name = 'foo'
    </script>

阶段二:每个模块只暴露一个全局对象,所有模块成员都挂载到这个对象中

实现:在第一阶段的基础上,通过将每个模块「包裹」为一个全局对象的形式实现,有点类似于为模块内的成员添加了「命名空间」的意思
缺点:通过「命名空间」减小了命名冲突的可能,但还是没有私有空间,所有模块成员仍然可以在模块外部被访问或修改,而且也无法管理模块之间的依赖关系

// module-a
var moduleA = {
    module_name : 'module-a',
    method1 : function () {
        console.log(this.module_name + '#method1')
    }
}
// module-b
var moduleB = {
    module_name : 'module-b',
    method1 : function () {
        console.log(this.module_name + '#method1')
    }
}
// index.html
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        moduleA.method1()
        moduleB.method1()
        // 模块成员可以被修改
        moduleA.module_name = 'foo'
    </script>

阶段三:使用立即执行函数表达式(IIFE:Immediately-Invoked Function Expression)为模块提供私有空间

实现:将每个模块成员都放在一个函数提供的私有作用域中,对于需要暴露给外部的成员,通过挂在到全局对象上的方式实现
优点:有了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问

// module-a
;(function () {
    var module_name = 'module-a'
    function method1 () {
        console.log(module_name + '#method1')
    }
    window.moduleA = {
        method1: method1
    }
})()
// module-b
;(function () {
    var module_name = 'module-b'
    function method1 () {
        console.log(module_name + '#method1')
    }
    window.moduleB = {
        method1: method1
    }
})()
// index.html
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        moduleA.method1()
        moduleB.method1()
        // 模块成员不可以被修改
        console.log(moduleA.module_name) // => undefined
    </script>

阶段四:利用 IIFE 参数作为依赖声明使用

实现:在第三阶段的基础上,利用立即执行函数的参数传递模块依赖项
优点:这使得每一个模块之间的关系变得更加明显

// module-a
;(function ($) {
    var module_name = 'module-a'
    function method1 () {
	    $('body').animate({margin: '200px'})
        console.log(module_name + '#method1')
    }
    window.moduleA = {
        method1: method1
    }
})(jQuery)
// module-b
;(function () {
    var module_name = 'module-b'
    function method1 () {
        console.log(module_name + '#method1')
    }
    window.moduleB = {
        method1: method1
    }
})()
// index.html
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        moduleA.method1()
        moduleB.method1()
    </script>

以上的模块化演变,都是以原始的模块为基础,通过约定的方式去实现模块化的代码组织。
为了统一不同的开发者和不相同项目之间的差异,因此需要一个标准去规范模块化实现的方式。
另外,在模块化当中,针对模块加载的问题,都是通过手动添加,一旦时间久了过后,维护起来非常麻烦。

二、模块化规范的出现

1、CommonJS规范(同步加载模块)

  • 允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports来导出需要暴露的接口。

CommonJS规范主要是用于服务端的模块化规范,可以说NodeJS是它的最佳实践,这是因为它是内置的模块系统。在服务端,模块的加载是运行时同步加载的。而在浏览器端的资源是异步加载的,模块需要提前编译打包处理。

2、AMD规范(异步加载模块)

  • AMD规范全称是Asynchronous Module Definition,即异步模块加载机制。它完整描述了模块的定义,依赖关系,引用关系以及加载机制。
    AMD对应的就是很有名的RequireJS

RequireJS 是一个 JavaScript模块加载器。它是执行的AMD规范,因此所有的依赖模块都是先执行;即RequireJS是预先把依赖的模块执行。
执行流程:

  1. require函数检查依赖的模块,根据配置文件,获取js文件的实际路径
  2. 根据 js 文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。
  3. 依赖script全部加载完成后,调用回调函数。
  • 在AMD规范当中,它约定每个模块都必须要通过define()这个函数去定义。
    这个函数默认可以接收两个参数,也可以接收三个参数。
    1. 参数一:这个模块的名字(后期加载这个模块时使用)
    2. 参数二:一个数组,用来声明这个模块的依赖项。
    3. 参数三:是一个函数,函数的参数与参数二的依赖项一一对应。每一项分别为
      依赖项导出的成员(作用:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。)
// 定义一个模块
define('module1', ['jquery','./module2'],function ($, module2) {
// 向外部导出成员,通过 return 方式实现 
	return {
		start: function () {
			$('body').animate({margin: '200px'})
			module2()
		}
	}
})
// 载入一个模块
require(['./module1'], function (module1) {
	module1.start()
})

缺点: AMD使用复杂,模块 JS 文件请求频繁。

3、CMD规范(异步加载模块)

  • CMD的全称是Common Module Definition,即通用模块定义,其提供了模块定义和按需加载执行模块。
  • 在CMD中,一个模块就是一个文件,通过 define()函数定义一个模块。其接收 factory 参数,factory可以是一个函数,也可以是一个对象或者字符串。
  • factory为函数时,有三个参数function(require、exports、module){}
    1. require是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口:require(id)
    2. exports是一个对象,用来向外提供模块接口
    3. module 是一个对象,用于存储与当前模块相关联的一些属性和方法。
  • 实例:
define(function(require、exports、module){
	// 通过 require 引入依赖
	var $ = require('jquery')
	// 通过 exports 或者 module.exports 对外暴露成员
	module.exports = function () {
		console.log('module 2~')
		$('body').append('<p>module2</p>')
	}
})
  • 淘宝推出的 Sea.js 就是使用了 CMD规范

4、UMD

  • UMD 通用模块定义规范(Universal Module Definition),是 CommonJS和AMD 的糅合,
  • UMD先判断是否支持 Node.js的模块(exports)是否存在,存在则使用Node.js模块模式;在判断是否支持AMD(define)是否存在,存在则使用AMD方式加载模块

5、 ES Modules

  • 它是 ECMAScript 2015 即ES6 中定义的一个最新模块系统。
  • ES Modules 模块设计思想:尽量的静态化、使得编译时就能确定模块的依赖关系,以及输入和输出的变量(CommonJS和AMD模块,都只能在运行时确定这些东西)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值