前端工程化基础(一):Node模块化

Node模块化

Node.js是什么

官方定义:Node.js是一个基于V8 JavaScript引擎的JavaScript运行时的环境

  • Node.js基于V8引擎来执行 JavaScript代码,但是Node.js中不仅仅有V8
    • 我们知道,V8可以嵌入到C++应用程序中,因此无论是Chrome还是Node.js,都是嵌入了V8引擎来执行的JavaScript代码
    • 在Chrome浏览器中,除了运行JavaScript代码之外,还要解析、渲染HTML、CSS等相关渲染引擎,同时还需要提供支持浏览器操作的API、浏览器自己的事件循环等
    • 在Node.js中我们也需要进行一些额外的操作,比如 文件系统的读写、网络IO、加密、压缩解压文件等操作
    • Node.js是JS代码的运行环境,由JS/C++/C语音编写(libuv就是C语音编写

image.png

Node.js应用场景

  • 应用一:目前 前端开发的库都是以node包的形式进行管理的
  • 应用二:npm/yarn/pnpm工具成为前端开发使用的最多工具
  • 应用三:越来越多的公司使用 Node.js作为web服务器开发、中间件、代理服务器
  • 应用四:大量项目需要 借助Node.js完成前后端渲染的同构应用
  • 应用五:资深前端工程师,使用Node.js编写脚本工具
  • 应用六:使用Electron来开发桌面应用程序

Node.js的输入输出

  • 我们在用命令行执行JS文件的时候,可以在命令后面敲空格,之后输入内容 node js.js num=10
  • JS中可以通过process.argv接收
console.log(process.argv);
[
  'F:\\nodejs\\node.exe',
  'D:\\Mrzhang\\Study\\前端\\CSS\\code\\js.js',
  'num=10',
]
  • 输出 console.log()即可

Node.js中的全局对象

  • global:相当于浏览器中window
  • process:进程相关的内容:process.argv是比较常用的
  • console
  • 定时器函数
    • setTimeout
    • setInterval
    • setImmediate(function () {})
    • process.nextTick(function(){})

特殊的全局对象

这些对象实际上是 模块中的变量,只是 每个模块都有,看起来是全局变量

在命令行交互中不可以使用

  • 包括:__dirname/__filename/exports/module/require()
//显示文件所在目录(不包含文件名称)
console.log(__dirname);
//显示文件所在目录(包含文件名称)
console.log(__filename);

认识模块化开发

  • 目前的程序代码量是十分庞大的
  • 模块化的目的是将庞大程序的代码,拆分成一个个小的结构
  • 而这个结构有属于 自己的逻辑代码,有自己的作用域,定义变量的时候,不会影响到其他的结构
  • 同时 这个结构的某些变量,函数以及对象等,又希望暴露出去,让其余结构访问
  • 其余结构可以通过某种方式,导入 另外结构的变量、函数对象等内容
  • 上面所说的 结构就是 模块,按照这种 结构划分开发程序的过程,就是模块化开发的过程

模块化的提出,主要是为了应对前端页面更加复杂的局面

在ES正式提出模块化前,社区提出了模块化的规范CommonJS(依旧再用),AMD、CMD(后面的两者均不在用了)

在ES6的时候,正式提出了标准的模块化ESModule

CommonJS规范和Node关系

CommonJS是一个规范,最开始提出来的时候,主要应用于服务器的

  • Node是CommonJS在服务器端一个具有代表性的实现

  • Browserify是CommonJS在浏览器中的一种实现

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

  • 因为Node对CmmmonJS进行了支持和实现,因此在开发Node过程中可以使用模块化开发

    • 在Node中每一个JS文件都是单独的模块
    • exports和module.exports可以负责对模块中的内容进行导出
    • require函数可以帮助我们 导入其他模块中的内容
  • 需要导出的文件

let until_name = "until";

function foo() {
   
  console.log("zhangcheng");
}

function bar() {
   
  console.log("bar");
}

//导出相关变量
exports.until_name = until_name;
exports.foo = foo;
exports.bar = bar;
  • 需要导入的文件
//引入变量
const until = require("./until.js");

//可以通过until.的方式访问变量
console.log(until.until_name);
until.foo();
until.bar();
-----------------------------------------
//我们可以借助解构赋值的方式,简化代码
//引入变量
const {
    until_name, foo, bar } = require("./until.js");

console.log(until_name);
foo();
bar();

exports导出的本质

exports实际上是一个对象,通过require函数,将导入文件中的变量与exports进行了引用赋值

image.png

  • 以上可以通过代码进行验证,更改 exports.name的值,并在两个文件中打印,即可观察到

module.exports导出

CommonJS导出,实际上是通过module.exports进行导出的,而module.exports和exports是同一个对象

  • 因为module.exports和exports是同一个对象,因此可以写出以下导出代码
let until_name = "until";

function foo() {
   
  console.log("zhangcheng");
}

function bar() {
   
  console.log("bar");
}

//导出相关变量
module.exports.until_name = until_name;
module.exports.foo = foo;
module.exports.bar = bar;
  • 而在真实的开发中,我们常写出以下代码
let until_name = "until";

function foo() {
   
  console.log("zhangcheng");
}

function bar() {
   
  console.log("bar");
}

//导出相关变量
module.exports = {
   
  until_name,
  foo,
  bar,
};
  • 若在最后,通过exports进行更改相关变量,则在导入的文件中,不会受到影响
let until_name = "until";

function foo() {
   
  console.log("zhangcheng");
}

function bar() {
   
  console.log("bar");
}

//导出相关变量
module.exports = {
   
  until_name,
  foo,
  bar,
};
//在导入的时候,until_name为“until”,而不是“hhhh”
exports.until_name = "hhhh"
  • 接下来看以下内存图
    • 通过 module.exports进行导出

image.png

通过 module.exports = {}进行导出

image.png

  • 因此通过以上两幅图,可以看出,在内存中 module.exportsexports的对应关系

  • 那么我们通过维基百科中对 CommonJS规范的解析

    • CommonJS中没有module.exports的概念
    • 但是为了实现模块的导出,Node中使用的是 Module类每一个模块都是Module的一个实例,也就是 module
    • 所以在Node中真正用于导出的其实不是 exports,而是module.exports
    • 因为 module.exports = exports,所以,exports也可以进行导出

require的细节

我们知道 require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象,接下来,我们就要看一下它的查找规则是什么样的

require(X)

  • 情况一:X是Node核心模块,比如path、http
//会直接返回核心模块,并停止查找
const http = require("http")
  • 情况二:X是 以./或者…/或者根目录开头的
    • 第一步:将X当作一个文件在对应的目录下查找
      • 如果有后缀名,就按照后缀名的格式查找对应的文件
      • 如果没有后缀名,会按照如下顺序
        • 直接查找文件X
        • 查找X.js文件
        • 查找X.json文件
        • 查找X.node文件
    • 第二步:若没有查找到X对应的文件,将X作为一个目录
      • 查找目录下面的index文件
        • 查找X/index.js文件
        • 查找X/index.json文件
        • 查找X/index.node文件
    • 如果都没有找到,那么就会报错
const until = require(./until)
//首先会将until当成文件,查找until.js/until.json/until.node
//若没有查到,就会将until当成目录,查找它下面的index.js/index.json/index.node文件
  • 情况三:直接是一个X,该X没有路径,且不是一个核心模块
    • 会查找本目录下,以及上级目录下中 node_module目录中的模块
const axios = require("axios")
//会在node_module目录中查找axios

模块加载过程

  • 结论一:模块在被第一次引入时,模块中的JS代码会被运行一次
const until = require(./until)
//until.js中的代码会先运行一次
  • 模块被多次引入时,会进行缓存,最终只加载一次
    • 因为每个模块对象module中有一个loaded属性
    • false表示还没有被加载,为true表示已经加载
    • 已经加载的模块,不会再次被加载
//打印------
console.log(------);
//运行until.js中的代码           
const until = require(./until)
//打印+++++
console.log(+++++);

//下面的引入不会再被加载
const until1 = require(./until)  
const until2 = require(./until)
const until3 = require(./until)
  • 如果出现循环引入的情况
    • 会按照图结构,进行深度优先算法进行加载

CommonJS的缺点

  • CommonJS加载模块是同步的
    • 这就意味着只有 等到对应的模块加载完毕,当前模块中的内容才能被运行
    • 这个再服务器中不会出现问题,因为服务器 加载的js文件都是本地文件,加载速度比较快
  • 如果应用到浏览器中
    • 浏览器 加载js文件需要先从服务器将文件下载下来,之后 再加载运行
    • 如果 引入的某个js文件运行时间过长,就会阻塞后面的js代码无法运行,即使是一些简单的DOM操作

认识ESModule

ES6提出的模块化,前提是浏览器支持

  • 与CommonJS不同之处

    • 一方面使用了import和export(对应的CommonJS的是require和module.exports
    • 另一方面采用了编译期的静态分析,并且加入了动态引用的方式
  • 采用ES Module会默认采用严格模式


  • 创建一个HTML文件
    • 引入 script标签,在标签中写入type = “module”
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
        <!--注意,在本地测试的时候,ES Module需要启用live server进行测试,单纯运行文件不行-->
    <script src="./main.js" type="module"></script>
		<!--until.js暴露变量-->
	<script src="./until.js" type="module"></script>
  </body>
</html>

  • 创建until.js暴露变量
    • 通过export进行暴露
    • 而此 export并不是对象,只是特殊语法,{标识符}
let until_name = "until";

function foo() {
   
  console.log("zhangcheng");
}

function bar() {
   
  console.log("bar");
}

//导出相关变量
export {
    until_name, foo, bar };
  • 创建 main.js引入 until.js暴露的变量
    • 通过 import进行引入跟的文件名一定要写后缀
import {
    until_name, foo, bar } from "./until.js";
console.log(until_name);
foo();
bar();

ESModule的导入导出扩展

  • 导出的三种方式
//通过export直接导出
export{
   name,foo}

//导出的时候取别名
export{
   name as unName}

//直接导出变量
export let name = "zhangcheng"
  • 导入的三种方式
//直接导入
import {
   name} from "./until.js"

//导入的时候取别名
import {
   name as unName} from "./until.js"

//导入的时候将这个module取别名
import * as foo from "./until.js"
foo.name

export和import结合使用

常见于开源的框架中

image.png

image.png

  • index.js中一般不需要写逻辑代码,仅做模块的导入导出即可

  • index.html文件
    • 该文件中通过 script标签引入了main.js文件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./main.js" type="module"></script>
  </body>
</html>

  • main.js文件
    • 该文件直接引入了index.js文件
import {
    name, foo } from "./until/index.js";

console.log(name);
console.log(foo());
  • index.js文件
    • 该文件引入了tool.js和tool2.js文件,同时将变量暴露
//统一引入
import {
    name } from "./tool.js";
import {
    foo } from "./tool2.js";

//统一导出
export {
    name, foo };

------------------还可以做出以下优化
//export和import的结合
export {
    name } from "./tool.js";
export {
    foo } from "./tool2.js";


//写成这样也可以
export * from "./tool.js";
export * from "./tool2.js";
  • tool.js文件
export let name = "zhangcheng";
  • tool2.js文件
export function foo() {
   
  return "foo";
}

default用法

前面用到的都是有名字的导出,default是默认导出

  • 默认导出 是不需要指定名字的
  • 导入的时候不需要加{},且名字可以自己命名
  • 注意:一个文件只有一个默认导出

  • 默认导出方式
//默认导出方式一
function foo() {
   
  console.log(123);
}

export default foo;

//默认导出方式二
export default function(){
   
    console.log(123)
}
  • 引入方式
import aaa from "./until.js"
aaa()

import函数

当我们需要动态引入文件的时候,需要用到import函数

  • 正常使用import引入文件的时候,需要写在代码最顶层
import {
   name} from "./index.js"
//逻辑代码
  • 但是有时候需要按需引入一些文件,这时候就可以用到import函数
    • import函数返回的是一个Promise
let flag = true
if(flag){
   
    import("./index.js").then(res=>{
   
        console.log(res)
    })
}

相关推荐

  1. Node.js -- 模块

    2024-01-27 02:18:04       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-27 02:18:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-27 02:18:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-27 02:18:04       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-27 02:18:04       20 阅读

热门阅读

  1. vue开发的PC端项目使用postcss-to-viewport适配移动端

    2024-01-27 02:18:04       40 阅读
  2. WordPress wp-file-manager 文件上传漏洞 CVE-2020-25213

    2024-01-27 02:18:04       31 阅读
  3. element中form校验中清除校验不通过的提示语

    2024-01-27 02:18:04       37 阅读
  4. 【Git】Conventional Commit提交规范

    2024-01-27 02:18:04       30 阅读
  5. [GN] Vue3.2 快速上手 ----常用API及其新组件

    2024-01-27 02:18:04       32 阅读
  6. CentOS7开机自动执行脚本

    2024-01-27 02:18:04       38 阅读