Skip to content

1. 模块化

模块化的理解

什么是模块化

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起
  • 块的内部数据与实现(函数)是私有的,只是向外暴露一些接口(方法)与外部其他模块通信

模块化的好处

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

模块化规范

CommonJS

NodeJs应用由模块组成,采用CommonJS。每个文件就是一个模块,有自己的作用域。在一个文件里定义的变量 函数、类都是私有的,对其他文件不可见。

在服务器端,模块的加载时运行时同步加载;在浏览器端,模块需要提前编译打包处理。

特点

  • 所有代码运行在模块作用域,不污染全局作用域
  • 模块可以多次加载,但只会在第一次加载时运行一次,并缓存起来。再加载,就直接读取缓存结果。要想让模块再次运行,必须清楚缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

基本语法

  • 暴露模块:module.exports = valueexports.xxx = value
  • 引入模块:require(name/path)
    • name: 第三方模块名
    • path:自定义模块文件路径

CommonJS规范规定,每个模块内部,module变量(是个对象)代表当前模块。module的exports属性(module.exports)是对外的接口

加载某个模块其实是加载某个模块的module.exports属性

js
  var x = 5
  var addR = function(value) {
    return value + x
  }
  module.exports.x = x
  module.exports.addX = addX;

上面代码通过module.exports输出变量x和函数addX

js
  var example = require('./example.js')
  console.log(example.x)
  console.log(example.addX())

CommonJS中一旦输出一个值,模块内部的变化就影响不到这个值

js
 // lib.js
 var counter = 3
 function incCounter() {
  counter += 1
 }
 module.exports = {
   counter,
   incCounter
 }
js
// main.js
var counter = require('./lib').counter
var incCounter = require('./lib').incCounter
console.log(counter) // 3
incCounter()
console.log(counter) // 3
// counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

AMD

CommonJS加载模块是同步的(只有加载完成才能完成后面的操作)。AMD是非同步加载,可以指定回调函数。

NodeJS主要用于服务端,模块文件一般存在本地,加载较快。如果是浏览器环境,要从服务器加载模块,就必须采用非同步模式,所以浏览器端一般采用AMD模式

基本语法

定义暴露模块:

js
  // 定义没有依赖的模块
  define(function(){
    return 模块
  })
  // 定义有依赖的模块
  define(['module1', 'module2'], function(m1, m2) {
    return 模块
  })

引入使用模块:

js
  define(['module1', 'module2'], function(m1, m2) {
    // 使用m1 m2
  })

CMD

CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

基本语法

定义暴露模块:

js
// 定义没有依赖的模块
  define(function(require, exports, module) {
    export.xxx = value
    module.exports = value
  })
// 定义有依赖的模块
define(function(require, exports, module) {
    //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

引入使用模块:

js
  define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

ES6模块化

ES6模块化语法

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

js
  // 定义模块 math.js
  var basicNum = 0
  var add = function(a, b) {
    return a + b
  }
  export {basicNum, add}
js
  // 引用模块
  import {basicNum, add} from './math'
  function test(ele) {
    ele.textContent = add(99 + basicNum)
  }

如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

js
  // exportDefault.js
  export default function() {
    console.log('foo')
  }
js
  // importDefault.js
  import test from './exportDefault'
  test() // foo

ES6模块化和CommonJS模块化的差别

  • CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用
  • CommonJS模块是运行时加载;ES6模块是编译时输出接口:因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

KESHAOYE-知识星球