🖥️ 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 |
另一个文件导入:
JavaScript |
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 |
情况二: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 |
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后面的{}中
这里的{}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的
所以:export{name:name} 是错误的写法
JavaScript |
方式三:导出时 给表示符起一个别名
通过as关键字起别名
JavaScript |
import关键字
import关键字负责从另外一个模块中导入内容
导入内容的方式:
方式一:importi{标识符列表} from '模块';
注: 这里的{}不是一个对象,里面只是存放导入的标识符列表内容
JavaScript |
方式二:导入时给标识符起别名
通过as关键字起别名
JavaScript |
方式三: 通过 * 将模块功能放到一个模块功能对象(a module object)上
JavaScript |
export和import结合使用(相对高级写法)
补充:export和import可以结合使用
JavaScript |
为什么曜这样做呢
在开发和封装一个一个功能库时 希望将暴露的所有接口放到一个文件中
方便指定统一的接口规范 也方便阅读
我们就可以使用export和import结合使用
default用法
导出功能的都是 有名字的导出(named exports):
在导出export时指定了名字
在导入import时需要知道具体的名字
一种导出叫 默认导出(default export)
默认导出export时 不需要指定名字
在 导入时 不需要使用{} 并且可以自己来指定名字
它也方便我们和现有的CommonJS等规范相互操作
注:在一个模块中 只能有一个默认导出(default export)
import函数
通过import加载一个模块 是不可以在其放到逻辑代码中的 如:
JavaScript |
JavaScript |
在某些情况下,确实希望动态的来加载某个模块
这个时候 我们就需要使用import()函数来动态加载
import函数返回一个Promise,可以通过then获取结果
JavaScript |
但是上面这种写法 又比较麻烦,真实开发中是这样写的
JavaScript |
为什么会出现这个情况?
因为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)运行代码,计算值.并且将值填充到内存地址中
阶段一:构建过程
阶段二:实例化阶段 求值阶段