【js 基础系列】模块化

前端在发展初期仅仅被用作制作一些简单的页面,此时的页面逻辑比较简单,所以也没有太大的模块化需求。但是随着后期的发展,web 端的功能和逻辑越来越复杂,我们必须要有一套模块化的规范去管理我们的应用。

前端模块化的发展历程十分曲折,大致的历程如下
无模块化规范 => CommonJS 规范 => AMD/CMD 规范 => ES6 Module

无模块化规范

初期我们没有统一的模块化规范,但是我们本地的代码还是需要逻辑清晰便于维护的,普遍的一些做法是使用立即执行的匿名函数去保证变量不被污染。

var module = (function($, _) {
	// code
} (jQuery, lodash));

这样保证 module 内部自己的变量不会被外部污染,通过参数传递确定内部的依赖,是初期简单的模块化实现。详细历程在这里,有兴趣的同学可以去看下。

CommonJS

2009 年,nodejs 横空出世,将 js 运用于服务端。由于服务端逻辑的复杂性,迫切的需要一种统一的模块化规范。于是 nodejs 参照 CommonJS 的规范实现了模块系统。

CommonJS 规范的核心思想是允许模块通过 require 方法来同步加载所要依赖的其他模块,然后通过 exportsmodule.exports 来导出需要暴露的接口。

// 导入
const fs = require('fs')
fs.readFileSync(new URL('file://hostname/p/a/t/h/file'))

const util = require('../util.js')

// 导出
module.exports = { util }
exports.util = function() {}

CommonJS 简单方便的实现了 nodejs 的模块引用,一直被应用于服务端的开发。但是由于其同步加载模块的特性,导致客户端使用时会出现很多问题。
在服务端,我们的模块文件是从磁盘中读取,速度非常的快,同步加载不会出现问题。但是在客户端,网络环境千差万别,同步加载会阻塞其他脚本的执行,所以在客户端我们需要一种异步加载的模块化规范。

module.exportsexports 是有差别的,module.exports 本身就是一个对象,而 exports 是指向 module.exports 的一个引用,所以在使用上我们使用上面的方式去导出,而 exports = { util } 的方式会重写 exports

AMD、CMD

AMD 是 Asynchronous Module Definition 的缩写,意为“异步模块定义”,它是为浏览器环境设计的模块化规范。 RequireJS 是基于 AMD 规范实现的。

AMD 规范的语法如下

define(id?: String, dependencies?: String[], factory: Function|Object);

它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行,依赖前置。

  • id 是模块的名字,它是可选的参数。

  • dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入 factory 中。如果没有指定 dependencies,那么它的默认值是 ["require", "exports", "module"]

    define(function(require, exports, module) {})
    
  • factory 是最后一个参数,包裹着模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输出接口或值。

我们定义一个 math 模块,依赖于 jQuery 和一个我们本地的工具函数 util.js

// 定义
define('math', ['jQuery', './util.js'], function($, util) {
	// code
	const util = function() {}

	// util 就是模块对外输出的接口
	return util;
})

// 使用
require(['math'], function(math) {})

也可以在内部引用其他模块(注:这里不属于 AMD 的规范,是 RequireJS 2.0 加入的,对 SeaJS 写法的兼容,使 RequireJS 也支持按需加载)

// 定义
define('math', function(require) {
	const $ = require('jQuery')
	const util = require('./util.js')
	
	// code
	const util = function() {}
	
	// 也可以 exports 导出
	return util;
})

// 使用
require(['math'], function(math) {})

在使用时我们需要用 require 函数引用模块。

require(['jQuery', 'lodash'], function($, _) {
	// code
}, function(err) {
	// error
})

require 接收三个参数。

  • 第一个参数是一个数组,表示所依赖的模块,上例就是[‘jQuery’, ‘lodash’],即主模块依赖这两个模块。
  • 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
  • 第三个参数是处理错误的回调函数,接受一个error对象作为参数。

可以去查看规范的细节,AMD 规范

CMD 是 Common Module Definition 的缩写,SeaJS 是基于 CMD 规范实现的。
CMD 规范规定,一个文件就是一个模块,定义语法如下

define(factory)

factory 可以是一个函数、对象、数组、字符串。factory 为对象、数组、字符串时,表示模块的接口就是该对象、字符串。

define({ "foo": "bar" })
define(['foo', 'bar']);
define('I am a template. My name is {{name}}.')

factory 是一个函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。actory 方法在执行时,默认会传入三个参数:requireexportsmodule

define(function(require, exports, module) {
	const util = require('./util.js')
  	// code	
  	
  	// 导出模块,也可以直接 return
  	exports.math = function() {}
})

具体的细节可以去这里查看,CMD 规范,CMD 规范2

最后 AMD 和 CMD 的区别

  1. AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
  2. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。(RequireJS 从 2.0 开始,也改成可以延迟执行,根据写法不同,处理方式不同)。AMD 在开始时会加载所有的依赖模块,而 CMD 推崇使用时加载。
    // CMD
    define(function(require, exports, module) {
    	const a = require('./a')
    	a.doSomething()
    	// code ...
    	const b = require('./b') // 依赖可以使用时书写
    	b.doSomething()
    })
    
    // AMD
    define(['./a', './b'], function(a, b) {
    	// 依赖必须一开始就写好
    	a.doSomething()
    	// code ...
    	b.doSomething()
    })
    
    
  3. AMD 的加载速度会稍优于 CMD ,但是现代的浏览器可以忽略。
  4. CMD 规范中,没有全局的 require 概念,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。

最后附:RequireJS 和 SeaJS 的区别

ES6 Module

ES6 的模块化,大家可以直接去看阮一峰老师的教程

ES6 Module 和其他模块规范较大的区别是:

  • ES6 静态的 import 引入的模块,是强制开启严格模式的。
    详细可以查看 MDN 的描述

    静态的 import 语句用于导入由另一个模块导出的绑定。无论是否声明了 strict mode ,导入的模块都运行在严格模式下。

  • ES6 的引入,是对变量的引入,对原文件中值的改变会影响到 import 导入的变量。(这里只有 export 导出的模块是变量导出,export default 导出的是值,不会影响)

    // a.js
    var count = 1
    function add () {
    	return count++
    }
    export { count, add }
    
    // b.js
    import { count, add } from './a.js'
    console.log(count) // 1
    add()
    console.log(count) // 2
    

你可能感兴趣的:(JavaScript)