0


[Node] Node.js JavaScrpt模块化开发

🖥️ Node.js专栏:Node.js 初级知识
🧑‍💼 个人简介:一个不甘平庸的平凡人🍬

✨ 个人主页:CoderHing的个人主页

🍀 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️

👉 你的一键三连是我更新的最大动力❤️


1 认识模块化

什么是模块化?

模块化开发最终的目的是将程序划分成一个个小的结构;

这个结构中编写属于 自己的逻辑代码自己的作用域 定义变量名称时不会影响到其他的结构

这个结构可以将自己希望暴露的变量 函数 对象 等导出给其他 结构使用

也可以通过某种方式 导入 另外结构中的变量 函数对象 等

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

2 CommonJS和Node

CommonJS规范和Node关系

CommonJS是一个规范 可以简称为CJS

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

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

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

Node中对CommonJS进行了支持和实现 在Node中每一个js文件都是一个单独的模块 在这个模块中包括CommonJS规范的核心变量:exports.exports require 我们可以使用这些变量来方便进行模块化开发

exports和module.exports可以负责对模块中的内容进行导出;

require函数 可以帮助我们导入其他模块(自定义模块 系统模块 第三方模块)中的内容

exports导出

exports是一个对象 可以给对象添加很多个属性 添加的属性可以被导出

JavaScript
exports.name = name;
exports.age = age;
exports.sayHello = sayHello

另一个文件导入:

JavaScript
const bar = require('./bar')

main中的bar变量等于 exports对象

也就是 require通过各种查找方式 最终找到了exports对象

并且将这根exports对象赋值给了bar变量;

bar变量就是exports对象.

module.exports 导出

在Node中我们经常导出东西的时候,又是通过module.exports导出的:

module.exports和exports有什么关系或者区别呢?

CommonJS中没有module.exports的概念的;

为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module;

在Node中真正用于导出的其实 根本不是exports 而是module.exports

module才是导出的真正实现者.

为什么exports也可以导出?

module对象的exports属性是exports对象的一个引用.

3 require函数解析

require细节

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

require的查找规则:

导入格式如下:require(X)

情况一:X是一个Node核心模块 如 path http

直接返回核心模块 且停止查找

JavaScript
// 2 导入node提供的内置模块
// require('内置模块的名称')
const path = require('path')
const http = require('path')
console.log(path,http);

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

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

1.1 如果有后缀名,按照后缀名的格式查找对应的文件

1.2 如果没有后缀名,会按照如下顺序:

1.2.1 直接查找文件X

1.2.1 查找X.js文件

1.2.1 查找X.json文件

1.2.1 查找X.node文件

JavaScript
const foo = require('./foo')

2:没有找到对应的文件,将X作为一个目录

2.1 查找目录下面的index文件

2.1.1 查找X/index.js文件

2.1.2 查找X/index.json文件

2.1.3 查找X/index.node文件

如果都没有找到就报错! Not found

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

模块的加载工程

结论一:模块在被第一次引入时,模块中的js代码会被运行一次

结论二:模块被多次引入时,会缓存,最终只加载(运行)一次

为什么只会加载运行一次呢?

因为每个模块对象module都有一个属性loaded

为false表示没加载 true表示已加载

结论三:如果有循环引入 那么加载顺序是什么?

Node采用的是深度优先算法:main=>aaa=>ccc=>ddd=>eee=>bbb

CommonJS规范缺点:

CommonJS加载模块是同步的:

只有等到对应的模块加载完毕,当前模块中的内容才能被运行

在服务器中不会有什么问题,因为服务器 加载的js文件都是本地文件 加载速度非常快

将它应用于浏览器中?

浏览器加载js文件需要先从服务器将文件下载下来,之后 再加载运行

采用同步的就意味着后续的js代码都无法正常运行,即便是一些简单的DOM操作

在浏览器中,通常不使用CommonJS规范

4 ESModule用法详解

export关键字

将一个模块中的变量 函数 类等导出

有三种方式将内容导出:

方式一:在语句声明的前面直接加上export关键字

JavaScript
export const name = 'zb'
export const age = 18

方式二:将所有需要导出的标识符,放到export后面的{}中

这里的{}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的

所以:export{name:name} 是错误的写法

JavaScript
export {
name,
age,
sayHello
}

方式三:导出时 给表示符起一个别名

通过as关键字起别名

JavaScript
export {

  • name as fname,*
    
  • age,*
    *sayHello*
    
  • }*

import关键字

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

导入内容的方式:

方式一:importi{标识符列表} from '模块';

注: 这里的{}不是一个对象,里面只是存放导入的标识符列表内容

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

方式二:导入时给标识符起别名

通过as关键字起别名

JavaScript
import {name as fname ,age,sayHello} from "./foo.js"

方式三: 通过 * 将模块功能放到一个模块功能对象(a module object)上

JavaScript
导入时 可以给整个模块起别名
import * as foo from "./foo.js"

export和import结合使用(相对高级写法)

补充:export和import可以结合使用

JavaScript
export { formatCount,formatDate } from './format.js'

为什么曜这样做呢

在开发和封装一个一个功能库时 希望将暴露的所有接口放到一个文件中

方便指定统一的接口规范 也方便阅读

我们就可以使用export和import结合使用

default用法

导出功能的都是 有名字的导出(named exports):

在导出export时指定了名字

在导入import时需要知道具体的名字

一种导出叫 默认导出(default export)

默认导出export时 不需要指定名字

在 导入时 不需要使用{} 并且可以自己来指定名字

它也方便我们和现有的CommonJS等规范相互操作

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

import函数

通过import加载一个模块 是不可以在其放到逻辑代码中的 如:

JavaScript
// // 在导入的时候 必须放到顶层
import {name,age,sayHello} from "./foo.js"

JavaScript
// 不允许在逻辑代码中编写import 是不允许这样做的
let flag = true
if(flag){
import {name,age,sayHello} from "./foo.js"
console.log(name,age);
}

在某些情况下,确实希望动态的来加载某个模块

这个时候 我们就需要使用import()函数来动态加载

import函数返回一个Promise,可以通过then获取结果

JavaScript
let flag = true
if(flag){
// 如果确实是逻辑成立时 才需要导入某个模块
// import函数
const importPromise = import("./foo.js")
importPromise.then(res=>{
console.log(res);
})

 console.log("================");

}

但是上面这种写法 又比较麻烦,真实开发中是这样写的

JavaScript
let flag = true
if(flag){
import("./foo.js").then(res=>{
console.log(res);
})
console.log("================");
}

为什么会出现这个情况?

因为ES Module在被JS引擎解析时 就必须知道它的依赖关系.

由于这个时候js代码没有任何的运行,所以无法进行类似于if判断中根据代码的执行情况.

甚至 拼接路径的写法也是错误的,因为我们必须到运行时能确定path值

(扩展)import meta

import.meta是一个给JavaScript模块暴露特定上下文的元数据属性的对象

它包含了这个模块的信息,如这个模块的URL

在ES11 (ES2020)中新增的特性

5 ESModule运行原理

ES Module的解析流程

ES Module的解析过程可以划分为三个阶段:

一:构建(Constuction),根据地址查找js文件并下载,将其解析成模块记录(Module Record)

二:实例化(Instantiation),对模块记录进行实例化,并分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址

三:运行(Evaluation)运行代码,计算值.并且将值填充到内存地址中

阶段一:构建过程

阶段二:实例化阶段 求值阶段


本文转载自: https://blog.csdn.net/coderHing/article/details/127330765
版权归原作者 coderHing 所有, 如有侵权,请联系我们删除。

“[Node] Node.js JavaScrpt模块化开发”的评论:

还没有评论