首先,在ES6的class出来之前是没有模块的,JS就不是一个模块化的语言,所谓的“模块”要么是个匿名函数,要么就是个对象的伪模块

var module = (function(){
    let _count = 2 // 私有变量
    let m1 = function() {
        console.log(_count)
    }
    let m2 = function() {
        console.log(_count + 1)
    }
    return {
        m1,
        m2
    }
})()

转折点: 2009年美国大神程序员发明了nodeJs用于服务端编程,服务端必须要有模块,因为要跟操作系统和其他应用互动,否则无法编程,所以引出了commonJs规范

commonJs规范

导出

module.exports = {
    math
}
exports.math = function(){} // 跟module.exports一个意思,但是容易出错,不要使用

引入

let math = require('math') // 同步加载,会引起阻塞
math.add(2,3)

缺点:
因为nodeJs是服务器语言,它的模块都在本地放着,加载速度比较快,但是前端加载JS是从服务器加载的,比较慢,可能会引起浏览器假死状态

AMD规范

define(id?, dependencies?, factory)
*   id:字符串,模块名称(可选),默认文件名称
*   dependencies: 是我们要载入的依赖模块(可选),使用相对路径。,注意是数组格式
*   factory: 工厂方法,返回一个模块函数

导出

// math.js
define(['lib'],function(){ // 依赖lib.js
    let add = function(x, y){
        return x + y
    }
    return {
        add
    }
})

引入

require(['math'],function(){
    math.add(2,3)
})

优点: 异步加载模块,执行内容放在回调里
缺点: 也算不得缺点,所有的依赖必须声明在文件头部
使用者: require.js

CMD规范

跟AMD的规范就一丢丢小区别

define(['lib'],function(require, exports, module){ // 依赖lib.js
    let a = require('./a') // 依赖a.js
    a.test()
    let add = function(x, y){
        return x + y
    }
    module.exports = {
        add
    }
})

与AMD的区别:

  1. 工厂函数默认有三个参数,require用来引入模块,exportsmodule用于导出,当然导出也可以直接用return
  2. 在工厂函数内部可以用require引入模块,不需要把所有依赖都声明在头部,实现就近依赖,对于开发者比较友好

缺点:
就近依赖就必须先解析一下模板才知道加载哪些模块,不过解析模块的时间可以短到忽略不计
使用者: seaJs

ES6 Module

导出

// 注意注意,这里是export,不是exports
function addition(){}
export {addition}  // 导出函数
export const foo = 'Hello' // 导出一个变量
export default function(){} // 默认导出一个函数,引入时比较方便,但是只能存在一个
export default class() // 默认导出一个类

引入

import defaultModule from 'module-name' // export default导出
import * as name from 'module-name' // 引入所有导出,可以使用 name.add()使用
import { add, del } from 'module-name' // 解构,按需引入
import from 'module-name' // 仅引入

混合模式

export default const test = 'hello'
export const run = 'world'

// 引入
import ss,{ run } from './lib'
console.log(ss + run) // hello world

优点:

  1. 首先ES6模块输出的是值的引用,而不是值本身,当模块内部变化时,输出值也会随之变化;因此可以实现跨模块的常量,类似VueX

    var count = 1
    function change() {
        count ++ 
    }
    export {
        count,
        change
    }
    
    // 引入
    import {count,change} from './lib'
    console.log(count) // 1
    change()
    console.log(count) // 2
  2. 当出现循环加载问题的时候,commonJs的重要特性是加载时执行,当在require第一次加载后,就会执行整个脚本,然后缓存下来在内存中生成一个对象。之后每次加载都会返回这个对象的最后一次运行结果。

    // a.js
    exports.msg = 'aaa111'  // 1. a导出msg
    var b = require('./b.js') // 2. 加载b.js
    console.log(b.msg) // 7. b已经加载完毕并缓存下来,返回最后一次运行结果,输出 bbb222
    exports.msg = 'aaa222' // 8. a导出msg    
    // b.js
    exporst.msg = 'bbb111' // 3. b导出msg
    var a = require('./a.js') // 4. 加载a.js
    console.log(a.msg) // 5. 输出 aaa111
    exports.msg = 'bbb222' // 6. b导出msg
    // c.js
    var a = require('./a.js')
    var b = require('./b.js')
    console.log(a.msg) // aaa222 
    console.log(b.msg) // bbb222

    因此这样的方式有可能导致取值不准确
    而ES6import一个模块加载的是变量的引用,而且它不会被缓存

  3. 可以使用import()实现动态加载,类似于CMDrequire.async()

exports 与 module.exports

exports等同于module.exports,指向同一块内存区域,它只不过是后者的一个辅助属性

exports = module.exports = {}

require它只识别module.exports对象,所以一旦exports不指向module.exports就会报错

exports = { // 报错,改变了引用
    math
}

require 和 import

  1. require是引入整个模块,而import是结构赋值
  2. require像一个全局方法,可以随时随地使用,而且还可以动态拼装路径,而import必须在文件的顶部
  3. require需要引入整个模块并赋值给变量,所以性能稍低

yingmhd
67 声望4 粉丝

路漫漫其修远兮,吾将上下而求索