搞懂前端模块化的演进

前端的模块化根据环境的不同有不同的规范,所以写一下各个方面的前端的模块化,以加深自己的理解。主要分四块:es5环境下的模块化、commonJs下的模块化、es6环境下的模块化、AMD和CMD。

一、es5 环境下的模块化

早期的 Javascript 并没有模块化的思想,他被创造出来作为脚本的存在 ,用来解决HTML中的一些基本的动画和展示。后来随着前后端分离,前端业务量的增加,以及 命名冲突、代码无法复用等问题的出现,对于Javascript有了新的要求。

其实es5也可以实现简单的模块化。主要方式是:闭包+变量

// moduleA.js
;
var moduleA = (function(){
    var obj = {
        name:'one',
        add: function(num1, num2) {
            return num1 + num2
        }
    }
    
    return obj
})()

// main.js
var name = moduleA.name
var num = moduleA.add(1, 2)

// 在HTML 中需要注意两个js引入顺序, 先module.js 后 main.js

二、commonJs下的模块化

commonJs规范 最常见的应用就是nodejs 环境中的应用。它定义了导入和导出的方式:

  • require()
  • module.exports / exports

module.exports 的导出,导出为一个对象,可以在里面定义导出的属性。

// moduleA.js
var name = 'one'
function add(num1, num2) {
    return num1 + num2
}

// 这里的导出必定是一个对象
module.exports = {
    name, // => num: num
    add // => add: add
}

exports 简写导出,注意不能对exports 进行赋值操作。

// moduleB.js
// 使用exports的时候可以通过点的形式添加属性,最终他们会被添加到一个对象中对外导出。
exports.name = 'moduleB'
exports.mul = function(num1, num2) {
    return num1 * num2
}

// 错误用法 如下
exports = {
    name : 'moduleB'
}
/*
// 规范规定
var exports = module.exports

exports.name = 'ddd' // 相当于在module.exports.name = 'ddd'
exports = {
    name : 'moduleB'
} // 这里进行了重新赋值 此时就和module.export无关了

// 规范规定最后的返回是
return module.exports
*/

require()` 导入使用,动态加载, 需要注意导入的位置,同步方法,读入并执行一个js文件(如果已经读入了就从缓存加载),然后返回该文件导出的对象,如果没有找到,则会报错。

// main.js
//  导入 可以放在文件的最开头
var moduleA = require('./moduleA.js')

// 导入 可以放在方法中 ,这个时候什么时候方法被调用,什么时候导入的文件才会被加载进入内存
function foo() {
    var { name, mul } = require('./moduleB.js') // 当然 此时可以解构导入的对象
}

require的加载规则:

  • require(/usr/test/a)/开头的路径,会作为全局路径加载
  • require(./b).开头的路径,会作为相对路径加载
  • require('path') 不以/ 或者.开头,会从node_module中加载。
  • require('test_module/path/a') 不以/ 或者.开头,是一个路径,那么会先找到test_module的位置然后往下找。
  • 如果指定 的模块没找到,Node会尝试为文件名添加.js.json.node后,再去搜索。
  • 可以通过require.resolve() 得到文件确切加载名。

require的加载原理:

  • require 不是一个全局命令,它指向了模块的module.require命令
  • module.require 调用了Module._load函数
  • Module._load函数内部会进行一下判断
    • 检查Module._cache,缓存中是否有指定模块
    • 如果没有就创建一个新的Module 实例,并保存到缓存
    • 调用Module.load()加载指定的模块文件。读取内容后调用Module.compile()进行解析
    • 解析/加载过程中如果报错,就从缓存中删除
    • 返回模块对应的module.exports

三、es6环境下的模块化

es6的模块化同样也是分导入和导出两块,同时为了使得浏览器识别用了模块化,需要在脚本引用的时候标明。


    
        ...
        /* 只有在这里标明type, 在js文件中使用es6模块化,才能被浏览器处理 */
        
        
    

具体使用方式:

  • export
  • import from

export 导出模块,主要用法:

// moduleA.js

const name = 'moduleA'
function add(a, b) {
    return a + b
}

// 1. 导出一个对象(推荐)
export {
  name, add
}

// 2. 单个导出
export const age = 21
export function mul(a, b) {
    return a * b
}

// 3. 在1基础上改进  重定义对外暴露的名称
export {
  name as outName, 
  add as OutAdd
}

// 4. 默认导出 同一个文件中只允许export default 1个
// 好处是:这种方式导出, 在导入的时候可以自由命名。
// export default name
// export default add
// export default { }

import from 静态导入模块,主要用法:

// main.js
// 导入一般写在文件开头 静态导入
// 1. 导入模块
import  {name, add} from './moduleA.js'

// 2. 重命名导入
import  {name as outName, add as outAdd} from './moduleA.js'

// 3. 当导出使用的是export.default的时候,可以如下导入
import data from './moduleA.js'

// 4. 统一全部导入
import * as info from './moduleA.js'

// 5. 还可以通过import() 函数导入
import('./moduleA.js').then(res =>  {
    
}).catch(err => {})

四、AMD、CMD

AMD作为一种模块规范,采用的是异步的方式去加载依赖的模块,比如:require.js实现AMD

// 使用define方法定义一个模块
define('a', [/*  需要引用的模块 */], function () {
    return 'a';
});
require([/*  需要引用的模块 */], function () {
 
});

CMD是另外一种模块规范,和AMD 类似,比如:sea.js 两者的区别是:

  • AMD 事先声明,前置加载
  • CMD 动态生命,用到时加载
define(function(require, exports, module) {
    // 懒加载
    var $ = require('jquery.js')
    exports.add = function(a, b) {
        return a+b
    }
})

最后,模块化的目的是为了代码的更好的复用,所以咯,使用方式可以各式各样,但是目的一致。

以上是我对前端模块化的理解,如果能够帮到您,希望不吝点个赞哦;如果哪里写的不对,还望告知~~

你可能感兴趣的:(搞懂前端模块化的演进)