参考:

前端模块化的出现

网站的功能越来越复杂,需要的功能也越来越多,JS代码的体积量也越来越大,同时也会有很多个人开发,为了让前端开发人员更好的分工协作,进行管理和测试。于是就引入了模块化的概念

前端模块化的概念:

一组特定的功能方法的集合,它具有内部变量,通过它暴露出来的方法和变量等实现特定的功能,通过这些接口和变量进行通信

模块化的好处:

  • 避免命名冲突
  • 更好的分离、按需加载
  • 更高复用性
  • 更高可维护性

发展的历程

  1. namespace模式
  • 减少了全局上的变量数
  • 还是不太安全,我们还是可以直接修改内部的数据
var namespace1 = {
    foo: function() {},
    bar: function () {}
}
namespace1.foo()
  1. 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)
  1. 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规范,成为浏览器和服务端通用的模块解决方案