前端工程化开发(二)—— CommonJS

112 阅读7分钟

前端工程化开发(二)

认识模块化开发

  • 模块化开发最终的目的就是将一个大的程序进行拆分,划分为一个一个小的结构
  • 进行模块化开发的结构都是具备属于自己的业务逻辑代码,拥有自己的作用域,定义变量的时候不会影响其他结构
  • 这个结构是可以主动的把自己需要想要暴露出去给其他结构使用的变量、函数、对象通过模块化的关键字,暴露给其他结构使用
  • 也可以通过导入其他结构来实现我们的使用其他结构的变量、函数以及对象
  • 在早期的前端开发中,实际上是没有我们的模块化开发的
  • 当时实现的是避免我们的命名冲突的模块化开发,我们就是使用的是 IIFE ,立即执行函数
  • 同时以前的话,我们的定义变量 var 的缺陷,导致了命名冲突的可能性急剧增大
  • 在前端还没有确定我们的模块化开发方案的时候,社区中就出现了几个十分出名的模块化方案: commonJS | AMD | CMD 规范
  • 在 ES6 之后,ECMA 就正式的提出了模块化的方案: ESModule

CommonJS 模块化规范和 NodeJS 的联系

  • CommonJS 是前端开发中一种流行的方案之一,最初这种规范的提出是在我们的浏览器之外的环境下使用,所以说别名 ServerJS,后来为了体现出这种规范的广泛性,修改为了: CommonJS —— CJS

    • NodeJS 实现开发web 服务器的时候,使用的模块化规范就是 CommonJS

    • Browserify 也是我们的 CommonJS 在浏览器中的一种体现

    • webpack 打包工具具备对 CommonJS 的支持以及转换

    • 该规范给我们规范了一些关于结构的导入和导出的规则

      • 导入就是使用的是我们的导入的规则
      • 导出就是使用的是我你们的导出的规则

先来没有出现模块化开发的开发模式

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<!-- 开始实现引入两个模块 -->
<script src="./01.js"></script>
<script src="./02.js"></script>
</body>
</html>
(function() {
    console.log("我是以前的模块化开发模式...01")
})()
(function() {
    console.log("我是以前的模块化开发模式...02")
})()
  • 通过上面我们的配置我们就可以实现的一点是: 通过我们 script 标签进行导入其他的 JS 文件来实现我们的模块的开发
  • 但是我们的模块化开发模式来说的,实际上的话,是需要一个单独的作用域的
  • 所以说这里我们就是直接使用的是我们的立即执行函数来实现的
  • 这个就是我们的以前的模块化开发的模式

image-20241130231942388.png

现在使用我们的 commonJS 的开发模式

  • 在我们的 Node 中,每一个 JS 文件的话,实际上的话都是单独的被称之为一个模块
  • 在我们的模块化开发中,我们的 CommonJS 开发规范中,我们实现使用的是: exportsmodule.exportsrequire
  • 模块化开发的出现就是为了方便我们后期的 Node 模块化开发的好处,提供了一定的便捷
  • 但是我们的 CommonJS 是不支持默认的使用我们的 CommonJS 的规范的,这个时候就需要使用我们的 Browserify
  • 但是我们的 NodeJS 的服务端的开发是默认支持使用我们的 CommonJS 的开发规范的
  • 注意下面的代码是在我们的 Node 环境中运行的
const constant_variable = "util_name"// 开始定义我们的方法
function func01() {
    console.log(constant_variable)
}
​
function func02() {
    console.log(constant_variable)
}
​
// 开始导出我们的可以给外面进行使用的部分
exports.func01 = func01
exports.func02 = func02
// 开始导入其他的JS 模块实现导出的部分
// 从下面的开发模式中,我们可以知道的是其实这里就是实现导入就是我们的对象
// 所以说这个时候,我们就可以直接实现的是通过 es6 的解构赋值来实现导入的
// 注意我们进行导出的时候,是可以忽略 .js 后缀的
const utils = require("./02.js")
const {func01, func02} = require("./02.js")
​
console.log(utils)  // { func01: [Function: func01], func02: [Function: func02] }
​
utils.func01()  // util_name
utils.func02()  // util_namefunc01()  // util_name
func02()  // util_name
  • exportsmodule.exports 就是负责的是我们的模块的导出

  • require 函数就是导入其他的 JS 模块的方法(自定义模块,系统模块,第三方库

  • 但是需要主要的是,这里的话实际上导出是对象,所以说这里就可以通过解构赋值来实现导入自己需要的部分

  • exports 自身就是一个导出对象{},我们是可以通过给这个对象添加我们需要导出的内容进行导出的功能实现

    • require 就是实现的是导入的 exports 导出对象(本质就是我们的引用赋值)

module.exports 和 exports 导出方式

  • 上面的导出模式时使用的是我们的在 exports 对象上面添加属性来实现的导出,但是这样进行书写的话,我们是十分难受的

  • 所以说这个时候,我们就可以直接通过 module.exports 来实现导出

    • 该导出模式既可以单独的导出,又可以实现对象导出
  • const constant_variable = "util_name"// 开始定义我们的方法
    function func01() {
        console.log(constant_variable)
    }
    ​
    function func02() {
        console.log(constant_variable)
    }
    ​
    // 先进行我们的单独导出
    // module.exports.func01 = func01
    // module.exports.func02 = func02
    ​
    ​
    // 现在我们同时还可以直接实现我们的导出对象
    module.exports = {
        func01,
        func02
    }
    
  • 实际上的话,我们的 exports === module.exports

    • 进行单独导出的时候二者没有什么区别
    • 但是的话后者是可以实现直接导出对象的
    • 但是两者同时存在的时候,我们的 require 进行导入的内容就是选择的是 module.exports 导出的内容

image-20241201002521065.png

require 的导入规则

  • require 是我们的一个函数,实现的是导入其他的 JS 文件

  • 实现导入的步骤是: require(x)

  • 导入自定义模块的时候

    • utils --- index.js
    • 这个时候我们的导入规则就是: require(./utils)
    • 或者说也可以直接把把目录名写完也行: require(./utils/index.js)
    • 同时还可以直接把我们的 .js 后缀名省略即可: require(./utils/index)
    • 我们实现导入一个目录名的时候,我们require 的查找规则的话都是首先查找的是每一个 index 名的文件
  • 使用 require 导入我们的内置模块

    • require("path")
    • require(“http”)
  • 但是实际上的话,我们的 require 真真的查找规则是查找的是我们的 node_modules 文件目录,该文件是我们的包管理目录

  • 如果在本层级没有该文件,那么就会往上一层目录进行查找 node_modules

  • 同时也会查找我们的 全局进行安装的 node 模块的,就是使用的是 npm install node_module_name -g 的包也是会查找的

模块的加载过程

  • 模块在第一次被引入时,模块中的 JS代码会被运行一次,等待其代码被运行完后,直接停止

  • 如果我们的模块被多次引入后,但是这个时候会出现缓存,来实现最终的只是运行一次

    • 因为我们进行导入一个模块的时候,module 对象中是含有一个属性: loaded
    • 使用该属性来标识该模块是否被执行过,false 表示没有被加载,true 表示已经被加载过了
  • 如果含有循环引入的话,那么还是实现的是我们的直接一个模块一个模块的运行,只要遇到了引入的操作,那么就会运行

    • 该导入的模块中的 JS 代码
    • 这种算法就是我们的深度优先算法(DFS,depth first search) ,底层原理的话,利用的数据结构是我们的图结构
    • 图结构具有的算法含有: 深度优先算法 + 广度优先算法

总结

该部分我们讲解了关于前端以及 NodeJS 服务端 的模块化开发模式: CommonJS 的模块化开发规范

同时我们还浅谈了 CommonJS 中进行导入导出模块的更多的细节