参考:
前端模块化的出现
网站的功能越来越复杂,需要的功能也越来越多,JS代码的体积量也越来越大,同时也会有很多个人开发,为了让前端开发人员更好的分工协作,进行管理和测试。于是就引入了模块化的概念
前端模块化的概念:
一组特定的功能方法的集合,它具有内部变量,通过它暴露出来的方法和变量等实现特定的功能,通过这些接口和变量进行通信
模块化的好处:
- 避免命名冲突
- 更好的分离、按需加载
- 更高复用性
- 更高可维护性
发展的历程
- namespace模式
- 减少了全局上的变量数
- 还是不太安全,我们还是可以直接修改内部的数据
var namespace1 = {
foo: function() {},
bar: function () {}
}
namespace1.foo()
- IIFE模式
- 拥有了封装性
- 但是在我们引入模块依赖的时候,必须非常小心的处理他们的引入顺序(在HTML中声明的顺序即为JS引擎加载并解析的顺序),否则会引发模块找不到的错误
- 造成的问题有:难以维护、依赖模糊、请求过多
var module = (function () {
var _private = "safe now"
var foo = function () {
console.log(_private)
}
return {
foo: foo
}
})()
// 增强,在第二个括号中传入依赖 模块模式,现代模块实现的基石
var module1 = (function ($) { // 引入外部依赖jQuery
var _$body = $('body')
var foo = function () {
console.log(_$body)
}
return {
foo: foo
}
})(jQuery)
- labjs、YUI3
CommonJS
- 每个文件就是一个模块,有自己的作用域。内部的变量,方法都是私有的。外部与该模块通过export出去的变量或方法进行定义
- 在服务端,加载是在运行时的,浏览器:提前编译打包处理
- 运行在模块作用域,不会污染全局作用域
- 可以多次加载,但是只会运行一次,以后的加载都是直接读取缓存的结果,想让模块再次重新加载(从源,而非缓存),必须清除缓存
- 加载顺序就是模块出现的顺序,同步加载,比较适用于服务端
- 基本语法
- 暴露模块:
module.exports = value
或者exports.xxx = value
- 引入模块:
require(xxx)
- 暴露模块:
- CommonJS 暴露的模块到底是什么?
CommonJS 规范规定,每个模块内部,module变量代表当前模块,这个变量是一个对象,他的exports属性是对外的接口,加载某个模块,其实是加载该模块的module.exports属性
- 模块加载机制
输入的是被输出的值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值
// lib.js
var counter = 3
function incCounter () {
counter++
}
module.exports = {
counter: counter,
incCounter: incCounter
}
// main.js
var counter = require('./lib').counter
var incCounter = require('./lib').incCounter
console.log(counter) // 3
incCounter()
console.log(counter) // 3
AMD
加载方式:允许异步加载,因此浏览器端一般采用AMD规范
- AMD是规范,其中的一个实现是requireJS
- requirejs的思想:通过define方法,将代码定义为模块,通过require方法,实现代码的模块加载
基本语法:
// 定义暴露模块
define(function(){ // 该模块不依赖于其他模块
return yourmodule
})
define(['module1', 'module2'], function(m1, m2){
return yourmodule
})
// 引入使用模块
require(['module1', 'module2'], function (m1, m2) {
// 在这里使用引入的模块m1/m2
})
CMD
专门用于浏览器端,模块加载异步,模块使用时才会加载执行,CMD规范整合了CommonJS和AMD规范的特点。在
Sea.js
中,所有JavaScript模块基本都遵循CMD模块定义规范
基本语法
// 定义没有依赖的模块
define(function(require, exports, module){
exports.xxx = value
module.exports = value
})
// 定义有依赖的模块
define(function (require, exports, module) {
// 引入依赖模块。同步
var module2 = require('./module2')
require.async('./module3', function (m3) {
})
exports.xxx = value
})
// 引入使用模块
define(function (require) {
var m1 = require('./module1')
var m2 = require('./module4')
m1.show()
m4.show()
})
ES6模块
ES6的思想:尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量
语法
// 定义模块
var basicNum = 0
var add = function (a, b) {
return a + b
}
export {
basicNum,
add
}
// 引用模块
import { basicNum, add } from './match'
function test(ele) {
ele.testContent = add(99+basicNum)
}
// 模块的默认输出
export default function () {
console.log('fun')
}
import 自己定义引入的模块名 from './js'
ES6模块与CommonJS模块的差异
- CommonJS模块输出的时一个值的拷贝,ES6模块输出的是指的引用。
- CommonJS 模块是运行时加载,ES6模块是编译时输出接口
第二个差异:CommonJS加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成,而ES6模块不是对象,它的对外接口只是一种静态定义,在代码解析阶段就会生成
总结:
- CommonJS规范主要用于服务端编程,加载模块是同步的。这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD、CMD解决方案
- AMD规范在浏览器环境中异步加载模块,并且可以并行加载多个模块,不过AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
- CMD规范和AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行,不过,依赖SPM打包,模块的加载逻辑偏重
- ES6在语言标准的层面上,实现了模块功能,而且实现的相当简单,完全可以取代CommonJS和AMD规范,成为浏览器和服务端通用的模块解决方案