JS模块化---CommonJS与ES Module的使用与分析

目录

一、模块化的概念

1.定义

2.没有模块化带来的问题

二、CommonJS规范

1.定义

2.CommonJS规范与Node的关系

4.node中CommonJS的原理

5.module.exports导出

6.exports导出

7.require导入

8.模块的加载过程

二、ES Module规范

1.介绍

2.基本使用

3.export关键字

4.import关键字

5.import与export结合使用

6.default默认导出

7.import函数

8.ES Module的解析流程


一、模块化的概念

1.定义

模块化开发最终的目的是将程序划分成一个个小的结构,这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构,这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用,也可以通过某种方式,导入另外结构中的变量、函数、对象等。

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程

2.没有模块化带来的问题

没有模块容易造成命名冲突,在一个地方改变其他地方的变量,导致一些代码无法运行

解决没有模块化的方案:

使用立即执行函数包裹一些代码,并将需要暴露的就通过返回值返回一个对象,需要暴露的东西就作为这个对象的属性,我们可以获取这个对象从而获取他们的属性并使用

缺点:

  • 必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用
  • 代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写
  • 在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况

二、CommonJS规范

1.定义

CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了 体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS

2.CommonJS规范与Node的关系

Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发

  • 在Node中每一个js文件都是一个单独的模块
  • 这个模块中包括CommonJS规范的核心变量:exportsmodule.exportsrequire
  • 我们可以使用这些变量来方便的进行模块化开发

3.基本使用

模块化的核心是导出和导入,Node中对其进行了实现:

  • exports和module.exports可以负责对模块中的内容进行导出
  • require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容

代码示例:

main.js需要使用foo.js的变量,通过require函数获取

foo.js将其声明的变量暴露出去

main.js:

const { name, age} = require('./foo.js')

console.log(name);
console.log(age);

foo.js:

const name = "zyk"
const age = 18

module.exports = {
  name,
  age
}

4.node中CommonJS的原理

module.exports指向一个对象,并将这个对象暴露出去,另外一个文件使用require函数,并传入一个文件名的参数,然后内部就会判断这个文件名然后找出需要导出的对象,并通过return返回值返回module.exports对象。也就是说有一个对象,有同时三个变量指向它,这三个变量就是exportsmodule.exports和通过require函数获取的对象

5.module.exports导出

将当前文件定义的变量或者一些函数暴露出去

代码示例:

const name = 'kk'

function foo() {
  
}

module.exports = {
  name,
  foo
}

6.exports导出

原理:

将module.exports等于一个空对象,然后再将module.exports赋值给exports对象

在node内部实现的简单代码:

// exports原理:
module.exports = {}
exports = module.exports

// 如果module.exports 重新赋值一个对象 那么exports就没什么用了

// 记住: 最终能导出的一定是module.exports

基本使用:

const name = 'zyk'
const age = 18
function sum(num1, num2) {
  return num1 + num2
}

exports.name = name
exports.age = age
exports.sum = sum

7.require导入

require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象

常见的查找规则:

情况一:X是一个Node核心模块,比如path、http,直接返回核心模块,并且停止查找。

情况二:X是以 ./ 或 ../ 或 /(根目录)开头的:

  • 第一步:将X当做一个文件在对应的目录下查找:

       有后缀名:按照后缀名的格式查找对应的文件

       无后缀名:按照下面的顺序:

                        1.直接查找文件X

                        2.查找X.js文件

                        3.查找X.json文件

                        4.查找X.node文件            

  • 第二步:没有找到对应的文件,将X作为一个目录

        查找目录下面的index文件

                1.查找X/index.js文件

                2.查找X/index.json文件

                3.查找X/index.node文件

  • 如果没有找到,那么报错:not found

情况三:直接是一个X(没有路径),并且X不是一个核心模块

查找node_modules文件,如果没有,会继续往上一个目录查找node_modules文件,如果上面的路径中都没有找到,那么报错:not found

8.模块的加载过程

  • 模块在被第一次引入时,模块中的js代码会被运行一次
  • 模块被多次引入时,会缓存,最终只加载(运行)一次。每个模块对象module都有一个属性:loaded,这个属性用来记录是否有被加载过,为false表示还没有加载,为true表示已经加载。
  • 如果有循环引入,Node会采用的图结构的深度优先算法

二、ES Module规范

1.介绍

ES Module和CommonJS的模块化区别:

  • ES Module使用了import和export关键字
  • 采用编译期的静态分析(不会运行代码,只会解析import和export关键字的语句),并且也加入了动态引用的方式

ES Module模块采用export和import关键字来实现模块化:

  • export负责将模块内的内容导出
  • import负责从其他模块导入内容

2.基本使用

在浏览器中使用ES Module,注意:

  • 需要在script标签中加入type属性,并且是module
  • 需要在本地服务器中运行
<script src="./main.js" type="module"></script>

main.js:

import导入

import {name, age} from "./foo.js"
console.log(name);
console.log(age);

foo.js:

export导出

export const name = 'zyk'
export const age = 18

3.export关键字

export关键字将一个模块中的变量、函数、类等导出

导出方式:

  • 方式一:在语句声明的前面直接加上export关键字
  • 方式二:将所有需要导出的标识符,放到export后面的 {}中
    • 注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;export {name: name},是错误的写法
  • 方式三:导出时给标识符起一个别名

代码示例:

// 1.导出方式一: export 后面加声明
// export const name = 'zyk'
// export const age = 18

// 2.导出方式二:固定语法 export {} 注意不是对象
// const name = 'zyk'
// const age = 18
// export {
//   name,
//   age
// }

// 3.导出方式三:起别名
const name = 'zyk'
const age = 18
export {
  name as fname,
  age as fage
}

4.import关键字

import关键字负责从另外一个模块中导入内容

导入方式:

  • 方式一:import {标识符列表} from '模块',这里的{}也不是一个对象,里面只是存放导入的标识符列表内容
  • 方式二:导入时给标识符起别名
  • 方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上

代码示例:

// 导入方式一:
// import {name, age} from "./foo.js"
// console.log(name);
// console.log(age);

// 导入方式二: 
// import {fname, fage} from "./foo.js"
// console.log(fname);
// console.log(fage);

// 导入方式三:用*表示全部
import * as foo from "./foo.js"
console.log(foo.fname);
console.log(foo.fage);

5.import与export结合使用

在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中,这样方便指定统一的接口规范,也方便阅读,这个时候,我们就可以使用export和import结合使用。

代码示例:

// 导出方式一
import {sum, sub} from "./math.js"
import {timeFormat, priceFormat} from "./format.js"

export {sum, sub, timeFormat, priceFormat}

// 导出方式二
export {sum, sub} from "./math.js"
export {timeFormat, priceFormat} from "./format.js"

// 导出方式三
export * from './math.js'
export * from './format.js'

6.default默认导出

  • 默认导出export时可以不需要指定名字
  • 在导入时不需要使用 {},并且可以自己来指定名字
  • 它也方便我们和现有的CommonJS等规范相互操作

注意:在一个模块中,只能有一个默认导出(default export)

代码示例:

默认导出:

const name = "zyk"
const age = 18

function foo() {
  console.log("默认导出");
}

// 1.默认导出方式一
export {
  name,
  age,
  // foo as default
}

// 2.默认导出方式二 常见
export default foo

默认导入:

import {name, age} from "./foo.js"

// 默认导入
import zyk from "./foo.js"

console.log(name, age);
zyk()

7.import函数

通过import() 函数来动态加载模块,注意:通过import加载一个模块,是不可以在其放到逻辑代码中的。因为为ES Module在被JS引擎解析时,就必须知道它的依赖关系。由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况

8.ES Module的解析流程

ES Module的解析过程可以划分为三个阶段:构建、实例化、运行

阶段一:

  • 浏览器会从服务器获取main.js文件

  • 对mian.js文件进行静态分析(并不会运行内部代码,只会分析import和export语句)

  • 生成Module Record数据结构,内部有个RequestedModules属性(对依赖模块进行请求),继续请求依赖文件

  • 下载counter.js和display.js文件,对它们进行解析,生成它们对应的Module Record

  • 内部有个Module Map记录着映射关系,记录那个文件已被下载或者正在下载

阶段二和阶段三:

阶段二:

  • 对Module Record进行实例化,分析有没有导出一些东西(count)

  • 生成Module Enviroment Record(模块环境记录),然后将导出的count绑定到Bindings,在内存生成一个对象,并记录count的值,为undefined

  • 在main.js的Module Record发现有导入count,于是创建Module Enviroment Record绑定导入的值,这时count也为undefined

阶段三:

  • 运行count文件里的代码,将之前保存undefined的值改为运行后的值

  • 导入的模块就可从内存取出值并使用

注意:只能导出的变量才可以修改值,导入的变量不能修改

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值