Skip to content

amd

amd Asynchronous Module Definition 异步模块化定义, requirejs实现了这套规范,思想是前置依赖,主要包括以下几个方法

  • require.config() 配置依赖
  • define([deps], callback)。定义模块
  • require([deps], callback)。引用模块
js
// dependA.js
define([
    'dependB'
], function(dependB) {
    return {
        start: function() {
            document.write(dependB.name)
        }
    }
});

// dependB.js
define(function() {
    return {
        name: 'dependB'
    }
});

// main.js
require.config({
    baseurl: '/static/umd',
    paths: {
        "dependB": 'dependB',
        "dependA": 'dependA',
    }
})

require(['dependA'], function(dependA) {
    console.log(dependA)
    dependA.start()
})

查看演示。requirejs 需要预先配置好所有依赖。不能动态获取所有

cmd

cmd 通用模块定义(Common Module Definition)。seajs实现了这套规范,依赖是就近依赖,延迟执行。

js
// main.js
seajs.config({
    alias: {
        dependB: './dependB.js'
        dependA: './dependA.js'
    }
})

seajs.use('dependA')

// dependA.js
define(function(require, exports, module) {
    let dependB = require('dependB');
    dependB.start()
})

// dependB.js
define(function(require, exports, module) {
    'use strict';
    exports.start = function() {
        document.write('dependB')
    }
});

查看演示。seajs通过正则解析,预先将当前文件的require的module加载,所以require可以同步加载

commonjs

commonjs 是nodejs实现的一套模块化规范,和seajs类似。通过require、exports、module实现动态导入导出,不过不需要写define语句。

js
// moduleA.js
const a = require('A');

exports.someA = function() {
    console.log('someA')
}

commonjs下,为什么我们可以在文件中使用__dirname,__filename, require,module,exports等。 nodejs会将js文件进行包裹,类似下面这种。

js
(function(exports, require, module, __dirname, __filename,...) {
    exports.say = function() {
        console.log('say')
    }
})

包装函数如下

js
function wraper(functionStr)  {
    return `function(exports, require, module, __dirname, __filename) {
        ${functionStr}
    }`
}

let moduleFunction = wraper`
    export.say = function() {
        console.log('say')
    }
`

runInThisContext(moduleFunction)(exports, require, module, __dirname, __filename)

runInThisContext 相当于eval或者new Function。实现动态申明函数的功能。读取文件内容 => 添加包裹 => eval生成函数 => 添加相应参数 => 执行,require整个流程。

在代码中可以打印require和module,查看具体详情。

require加载流程

require加载模块有3类

  • nodejs底层核心模块
  • 工程中代码
  • npm 中的第三方代码

require标志符指require时的参数,按照以下方式解析

  • 如果标识符是核心模块,加载核心模块。
  • 如果标志符以./ / ../开头,视为项目代码,会解析成绝对路径,作为module标志
  • 如果不满足上面,则认为是第三方模块。

解析第三方模块规则如下

  • 从当前__dirname 开始,寻找是否存在node_modules, 在node_module 中查找标识符目录,如果查询到,检查package.json 中是否存在main字段,如果有根据main字段指向的js作为module入口,如果没有使用index.js ,index.json ,index.node。
  • 如果未找到node_modules, 在父级目录查找,直到根目录。重复执行上述流程。

!()[]

require 解析是按照深度优先的原则,按照代码执行顺序解析require。

require 简单源码

commomjs 将所有文件视为一个module,module包括文件解析相关信息。

js
function require(id) {
    const cacheModule = Module._cache[id]
    if(cacheModule) {
         return cacheModule.exports
    }

    const module = {
        exports: {},
        loaded: false,
        ...
    }

    const fileContent = getFileContent(id);

    Module._cache[id] = module;

    runInThisContext(warper(fileContent))(module, module.exports, require, __filename, __dirname)
    module.loaded = true;
    return module.exports
}

如上所示,相同标识符下,require只会加载一次,避免了循环引用问题。 delete require.cache[moduleName]; 具体参考 commonjs 详解

umd

umd是兼容amd和commonjs的规范,对代码做相应包括可实现。

js
(function (context, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        console.log('是commonjs模块规范,nodejs环境')
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        console.log('是AMD模块规范,如require.js')
        define(factory)
    } else if (typeof define === 'function' && define.cmd) {
        console.log('是CMD模块规范,如sea.js')
        define(function(require, exports, module) {
            module.exports = factory()
        })
    } else {
        console.log('没有模块环境,直接挂载在全局对象上')
        root.umdModule = factory();
    }
})(this, function(exports, module) {
    // js 代码
})