webpack模块加载(一):前端模块化简介

83 阅读5分钟

1.概述

什么是模块化?

  模块化就是把系统分离成独立功能个方法,需要什么功能,就加载什么功能;

优点:

  • 解决传统编码过程中的命名冲突文件依赖的问题;
  • 提高代码的可维护性可复用性以及提高生产效率

目前实现模块化的规范主要有:

  • CommentJs
  • AMD (RequireJS)
  • CMD (SeaJS)
  • ESModule

CommonJs

  • CommentJs主要用于服务器端
  • CommentJs加载模块是同步的,也就是说,加载完成之后执行后面的操作;
  • commonJs使用基于文件的模块,所以一每个文件只能定义一个模块;

Node.js主要用于服务端编程,模块都是存储在本地的硬盘中加载比较快,所以Node.js采用的是CommentJs的规范;

CommentJs规范分为三个部分:

  • module(模块标识)module变量在每个模块的内部,就代表当前模块
  • require(模块引用)require()用来加载外部模块,读取并执行js文件,返回该模块的exports对象;
  • exports(模块输出)exports是对外的接口,用于导出当前模块的变量和方法;

定义一个CommonJs的模块

// aModule.js
const $ = require("jQuery");     // 用于其他的模块
let count = 1;
const numClick = function(){
    alert(++count)
}
// 使用module.exports 定义模块公共接口
module.exports = {
    countClick:function(){
        $(document).on("click",numClick )
    }
}


CommonJs 加载模块

// 使用我们写的module
const aModule = require("aModule");  //引入模块
// 使用定义的公共接口
aModule.countClick();

我们需要注意的是:虽然我们的在aModule.js中定义的 "全局变量" $ , count, numClick;在我们的标准js文件中会生成全局变量,但是在Commonjs中它的作用域只存在于当前的模块;

AMD(Asynchronous Module Definition)

AMD就是异步模块定义。它采用异步的方法加载模块,通过define方法去定义模块,require方法去加载模块 ;

AMD的优缺点

AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖 AMD 的这个特性有好有坏:

  • 首先,尽早执行依赖可以尽早发现错误。  

  • 另外,尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。

  • 引用AMD的Javscript库:目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js

  • 在浏览器环境中异步加载模块;并行加载多个模块;

  • 开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;不符合通用的模块化思维方式,是一种妥协的实现。

AMD模块定义

如果这个模块还依赖于其他模块,那么define函数的第一个参数,必须是一个数组,指明该模块的依赖;

define(["tool"],function(){
    // ... 模块逻辑
})

  

AMD模块的加载

require(['modules'], callback);

第一个参数 ['modules'] ,是一个数组,里面的成员就是要加载的模块; 第二个参数 callback ,则是加载成功之后的回调函数。

require异步加载模块,浏览器在加载过程中不会失去响应;指定的回调函数,只有在前面的模块加载成功后,才会去执行,解决了依赖性的问题。

// a.js 定义a 模块 依赖于b模块
define(["b"],function(){
    console.log(b)
    var hello = function(){
        console.log("hello")
    }
    return {
        hello:hello
    }
})

// b.js  定义b模块
define(function(){
    var name = "max";
    return {
        name : name
    }
})
  
// demo.html
<script>
    require.config({   // 配置路径别名
        path : {
            'a':'./a',
            'b':'./b'
        }
    })
    require(['a','b'],function(a,b){    // 引入a,b模块
        console.log(a); // {hello:function(){}}
        console.log(b); // {name:'max'}
    })
</script>
 

CMD(Common Module Definition)

CMD 即通用模块定义,CMD规范是国内发展过来的;

Sea.jsrequire.js解决的问题是一样的,只不过在模块的定义方法和加载方法不同

CMD推崇就近依赖,延迟执行。文件是提前加载好的,只有在require的时候才去执行文件;

在CMD规范中,一个模块就是一个文件。

  • require 是可以把其他模块导进来的一个参数;
  • exports 可以把模块内的一些属性和方法导出;
  • module 是一个对象,上面储存了与当前模块相关联的一下属性和方法;
define(function(require,exports,module){
    // 模块代码
})

// a.js
define(function(require,exports,module){
    export.name = "max"
})

// b.js
define(function(require,exports,module){
    var a = require('./a');
    export.hello = function(){
        console.log("hello")
    }
})
<!-- demo.html -->
<script>
    seajs.config({
        alias:{
            a:'./a',
            b:'./b'
        }
    })
    seajs.use(['a','b'],function(a,b){
        console.log(a);
        console.log(b);
    })
</script>

ES6模块化

ES6汲取了 CommonjsAMD 的优点,语法简洁,并且基于文件。支持异步加载,可以成为浏览器和服务端通用的模块化解决方案;

ES6的主要思想是必须显式的使用标识符导出模块,才能从外部访问模块。其他的标识符,甚至在顶级的顶级作用域中定义,也只能在模块内使用;(与CommonJs一样)

ES6提供了两个标识符

  • import:导入模块标识符
  • export:从模块外部指定标识符(用于把模块中的内容暴露出来)

ES6的导出模式分以下几种

  • 1.使用关键字export分被导出定义的变量和函数
export const name = 'name';
export function compare(){};
export class Car()
  • 2.使用export将所有的模块标识符全部导出
const name = 'name';
function compare(){};
class Car();
export { name, compare }
  • 3.使用 expport default 关键字定义模块的默认导出
export default class Car()
  • 4.使用默认导出的同时还可以导出指定的名称
export const name = "name"
export default class Car();
  • 5.通过关键字 as 设置别名

const name = "name"
export { name as myname }

ES6的引入模式

  • 1.导入默认的导出
import car from 'aModule.js'
  • 2.导入命名导出
import { name, compare } from 'aModule.js'
  • 3.导入模块中声明的全部导出内容
import * as aModule from 'aModule.js'
  • 4.通过别名导入模块中声明的全部导出内容
import {name as myname} from 'aModule.js'
  • 5.导入默认导出和命名导出
import car,{name,compare} from 'aModule.js'

export 与 export default 的区别

  • 导入模块默认导出的内容,不需要使用花括号{},可以任意指定名称
  • 导入已命名的导出内容必须使用花括号,且名称需要对应

ES6 模块与 CommonJS 模块的差异

它们有两个重大差异:

  1. CommonJS 模块输出的是一个值的拷贝ES6 模块输出的是值的引用
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口.

参考文章

LABjs、RequireJS、SeaJS 哪个最好用?为什么?

前端模块化的全面总结