模块化之CommonJs、AMD、CMD和ES6模块化

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

模块化有两个重要的概念:模块的导出和模块的导入

  • 模块的导出:暴露接口的过程即模块的导出

  • 模块的导入:当通过某种语法或API去使用一个模块时,这个过程叫做模块的导入

1、CommonJS

因为CommonJs是node服务提出的模块化规范,所以我们首先需要安装node环境

安装nodeJs

官网地址:https://nodejs.org/zh-cn/

安装完成后在dos窗口中输入node命令验证

使用nodeJs

在node环境中运行index.js文件

node index.js

nodejs直接运行某个js文件,该文件被称之为入口文件

为什么要有CommonJS?

在nodejs中,由于有且仅有一个入口文件(启动文件),而开发一个应用肯定会涉及到多个文件配合,因此,nodejs对模块化的需求比浏览器端要大的多。由于nodejs刚刚发布的时候,前端没有统一的、官方的模块化规范,因此,它选择使用社区提供的CommonJS作为模块化规范。

CommonJS使用

CommonJS使用exports导出模块,导出的是一个对象,require导入模块

具体规范如下:

  1. 如果一个JS文件中存在exportsrequire,该JS文件是一个模块
  2. 模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
  3. 如果一个模块需要暴露一些API提供给外部使用,需要通过exports导出,exports是一个空的对象,你可以为该对象添加任何需要导出的内容
//utils.js
function getNumber(){
    return 1
}
exports = {
    getNumber:getNumber  //导出getNumber方法
}
//等同于
exports.getNumber = function(){
    return 1; //导出一个方法叫getNumber
 }
//等同于
function getNumber(){
    return 1
}
module.exports = {getNUmber}
  1. 如果一个模块需要导入其他模块,通过require实现,require是一个函数,传入模块的路径即可返回该模块导出的整个内容
//index.js
const util = require("./utils.js")
//等同于
const {getNumber} = require("./utils.js")
//等同于
console.log(require("./utils.js").getNumber())

注意:必须加路径./../不可省略,否则会报错!

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

  2. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。(node笔记中解释)

     (function(){
         //模块中的代码
     })()
    
  3. 为了保证顺利的导出模块内容,nodejs做了以下处理

    1. 在模块开始执行前,初始化一个值module.exports = {}
    2. module.exports即模块的导出值
    3. 为了方便开发者便捷的导出,nodejs在初始化完module.exports后,又声明了一个变量exports = module.exports
     (function(module){
         module.exports = {};
         var exports = module.exports;
         //模块中的代码
         return module.exports;
     })()
    
  4. 为了避免反复加载同一个模块,nodejs默认开启了模块缓存,如果加载的模块已经被加载过了,则会自动使用之前的导出结果

CommonJS的工作原理

当使用require(模块路径)导入一个模块时,node会做以下两件事情(不考虑模块缓存):

  1. 通过模块路径找到本机文件,并读取文件内容
  2. 将文件中的代码放入到一个函数环境中执行,并将执行后module.exports的值作为require函数的返回结果

正是这两个步骤,使得CommonJS在node端可以良好的被支持

可以认为,CommonJS是同步的,必须要等到加载完文件并执行完代码后才能继续向后执行

2、AMD

当浏览器遇到CommonJS

当想要把CommonJS放到浏览器端时,就遇到了一些挑战

  1. 浏览器要加载JS文件,需要远程从服务器读取,而网络传输的效率远远低于node环境中读取本地文件的效率。由于CommonJS是同步的,这会极大的降低运行性能
  2. 如果需要读取JS文件内容并把它放入到一个环境中执行,需要浏览器厂商的支持,可是浏览器厂商不愿意提供支持,最大的原因是CommonJS属于社区标准,并非官方标准

因为浏览器无法支持模块化。出现了AMD和CMD规范,有效的解决了浏览器模块化的问题。

AMD(Asynchronous Module Definition):异步模块加载机制

require.js实现了AMD规范

在AMD中,导入和导出模块的代码,都必须放置在define函数中

define([依赖的模块列表], function(模块名称列表){
    //模块内部的代码
    return 导出的内容
})

在AMD中,入口文件需要特殊指明,用data-amin属性标记

<body>
    <script data-main="./index.js" src="./require"></script>
</body>
AMD使用
//utils.js
define(function(){
    let data = []
    //...模块内部代码 进行一系列
    return {
        name:"导出的内容",
        data:data
    }
})
//index.js
define(['utils'], function(utils) {
    //等utils模块加载完成后执行自己的模块,参数utils是utils模块导出的内容
    return {
        name:"index模块的内容"
    }
});

如果多个文件都用到了同一个模块,模块只会读取一次,执行一次,会加入缓存中

后来,require也实现了CMD规范

//utils2.js
define(function(require, exprots,module) {
    const utils = require("./utils.js")
    console.log("utils模块代码",utils)
    module.exprots = "导出utils2模块的内容"    
});

注意:可以省略./,但最好不要省略

3、CMD

CMD(Common Module Definition):公共模块定义规范

sea.js实现了CMD规范

在CMD中,导入和导出模块的代码,都必须放置在define函数中,内部实现类似CommonJs

define(function(require, exports, module){
    //模块内部的代码
})
CMD简单使用
define(function(require, exprots,module) {
    const utils1 = require("./utils1.js")
    const utils2 = require("./utils2.js")
    module.exprots = "导出该模块的内容"    
});

通过异步方式导入模块

define(function(require, exprots,module) {
    require.async("./utils1.js",function(utils1){
        console.log(utils1)
    })
    require.async("./utils2.js",function(utils2){
        console.log(utils2)
    })
    module.exprots = "导出a模块的内容"    
});

4、ES6模块化

ECMA组织参考了众多社区模块化标准,终于在2015年,随着ES6发布了官方的模块化标准,后成为ES6模块化

模块化声明方式有两种

  1. 依赖延迟声明 ,如:commonjs
    1. 优点:某些时候可以提高效率
    2. 缺点:无法在一开始确定模块依赖关系(比较模糊)
  2. 依赖预声明 ,如: cmd,amd,es6模块
    1. 优点:在一开始可以确定模块依赖关系
    2. 缺点:某些时候效率较低

ES6模块化具有以下的特点

  1. 使用依赖预声明的方式导入模块
  2. 灵活的多种导入导出方式
  3. 规范的路径表示法:所有路径必须以./或…/开头

模块的引入

注意:这一部分非模块化标准

目前,浏览器使用以下方式引入一个ES6模块文件

<script src="入口文件" type="module">

ES6导入和导出

1、基本导入和导出

  • 基本导出:

基本导出可以有多个,每个必须有名称。export 声明表达式

// 基本导出,声明语句
export const name = "lkx" //导出name = "llx"
export function test(){
    console.log("导出函数test")
}

其他写法export {具名符号}

const age = 10
const sex = "男"
export {
    age,
    sex
}
//将age变量的名称作为导出的名称,将age的值,作为导出的值
// 等同于
export const age = 10
export const sex = "男"

也可以给导出变量起别名

const name = 10
export {
    name as name1
}

由于基本导出必须具有名称,所以要求导出内容必须跟上声明表达式具名符号

  • 基本导入:

由于使用的是依赖预加载,因此,导入任何其他模块,导入代码必须放置到所有代码之前

(不在开头也可以,浏览器预编译时进行import导入提升)不是解构赋值

对于基本导出,如果要进行导入,使用下面的代码

import {导入的符号列表} from "模块路径" 
import {name,test} from "./until.js"
console.log(name)
console.log(test())

注意以下细节:

  1. 导入时使用的符号是常量,不可修改

  2. 导入时,可以通过关键字as对导入的符号进行重命名

import {name,test as t} from "./until.js"
console.log(t())
  1. 可以使用*号导入所有的基本导出,形成一个对象
import * as obj from "./b.js"
//会把所有导出的内容形成一个对象,必须使用as重命名
  1. 多个文件导入同一个模块,会加入缓存,不会重复运行
import "./init.js"
//这个导入语句,仅运行文件,不会导入模块,如果有缓存,不运行

2、默认导入和导出

  • 默认导出

每个模块,除了允许有多个基本导出之外,还允许有一个默认导出

默认导出类似于CommonJS中的module.exports,由于只有一个,因此无需具名

具体的语法是

export default 默认导出的数据
//或
export {默认导出的数据 as default}

基本使用

export default {
    name:name,
    age:18
}
  • 默认导入

需要想要导入一个模块的默认导出,需要使用下面的语法

import 接收变量名 from "模块路径"

由于默认导入时变量名是自行定义的,因此没有别名一说

import data from "./utils.js"
console.log(data)

基本导入和默认导入混合使用:

如果希望同时导入某个模块的默认导出和基本导出,可以使用下面的语法:

import 接收默认导出的变量, {接收基本导出的变量} from "模块路径"


export const sex = "fale"
export default {
    name:name,
    age:18
}

import data,{sex} from "./utils.js"
console.log(data,sex)

注:如果使用*号,会将所有基本导出和默认导出聚合到一个对象中,默认导出会作为属性default存在

//b.js
export const sex = "fale"
export default {
    name:name,
    age:18
}

import * as data from "./b.js"
console.log(data)

注意:

  1. 尽量导出不可变值

当导出一个内容时,尽量保证该内容是不可变的(大部分情况都是如此)。因为,虽然导入后,无法更改导入内容,但是在导入的模块内部却有可能发生更改,这将导致一些无法预料的事情发生

导入的模块是常量

import { name } from "./b.js"
name = 10 //报错
  1. 可以使用无绑定的导入用于执行一些初始化代码

如果我们只是想执行模块中的一些代码,而不需要导入它的任何内容,可以使用无绑定的导入:

import "模块路径"
  1. 可以使用绑定再导出,来重新导出来自另一个模块的内容

有的时候,我们可能需要用一个模块封装多个模块,然后有选择的将多个模块的内容分别导出,可以使用下面的语法轻松完成

export {绑定的标识符} from "模块路径"
发布了21 篇原创文章 · 获赞 82 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Newbie___/article/details/104523097