JavaScript模块化

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解JavaScript模块化

错误处理方案

函数出现错误处理

通过throw抛出错误信息并终止程序,强制调用者修改错误

/**
 * 如果我们有一个函数, 在调用这个函数时, 如果出现了错误, 那么我们应该是去修复这个错误.
 */

function sum(num1, num2) {
  // 当传入的参数的类型不正确时, 应该告知调用者一个错误
  if (typeof num1 !== "number" || typeof num2 !== "number") {
    // return undefined
    throw "parameters is error type~"
  }

  return num1 + num2
}

// 调用者(如果没有对错误进行处理, 那么程序会直接终止)
// console.log(sum({ name: "why" }, true))
console.log(sum(20, 30))

console.log("后续的代码会继续运行~")

抛出异常的补充

抛出异常的分类有:

  1. 抛出一个字符串类型(基本的数据类型):throw "error"
  2. 比较常见的是抛出一个对象类型:throw { errorCode: -1001, errorMessage: "type不能为0~" }
  3. 创建类, 并且创建这个类对应的对象:throw new HYError(-1001, "type不能为0~")
  4. 提供了一个Error(默认创建出来会有很多信息,了解即可)

Error包含三个属性:

  • messsage:创建Error对象时传入的message;
  • name:Error的名称,通常和类的名称一致;
  • stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack;

    Error有一些自己的子类:

  • RangeError:下标值越界时使用的错误类型;
  • SyntaxError:解析语法错误时使用的错误类型;
  • TypeError:出现类型错误时,使用的错误类型;
const err = new Error("type不能为0")
// 可以修改error的名称和栈,但一般不会修改
err.name = "why"
err.stack = "aaaa"
  1. Error的子类:const err = new TypeError("当前type类型是错误的~")

如果函数中已经抛出了异常, 那么后续的代码都不会继续执行了

处理抛出的异常

两种处理方法:

  1. 第一种是不处理, 那么异常会进一步的抛出, 直到最顶层的调用

如果在最顶层也没有对这个异常进行处理, 那么我们的程序就会终止执行, 并且报错

  1. 使用try catch来捕获异常
function foo(type) {
  if (type === 0) {
    throw new Error("foo error message~")
  }
}

// 1.不处理, bar函数会继续将收到的异常直接抛出去
function bar() {
  foo(0)
}

// test 拿到 bar 抛出的异常
function test() {
// 2. 在上层使用try catch来捕获异常也是可以的
  try {
    bar()
  } catch (error) {
    console.log("error:", error)
  }
}

function demo() {
  test()
}
// 有对异常进行处理就会继续执行后续代码
console.log("后续的代码执行~")

补充(finally语法解释):不管有没有发生异常,finally里面的代码都会执行

try {
  
} catch (err) {

}finally {
  
}

什么是模块化

那么,到底什么是模块化开发呢?

  • 事实上模块化开发最终的目的是将程序划分成一个个小的结构
  • 这个结构中编写属于自己的逻辑代码,自己的作用域,不会影响到其他的结构;

image.png

  • 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;
  • 也可以通过某种方式,导入另外结构中的变量、函数、对象等;
// 导出
exports = {sum,add,sub} 
// 导入
const {sum,add} = require('abc.js')

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

CommonJS规范和Node关系

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

  • Node是CommonJS在服务器端一个具有代表性的实现;
  • Browserify是CommonJS在浏览器中的一种实现; (很少用了)
  • webpack打包工具具备对CommonJS的支持和转换;

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

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

前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现:

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

案例时间:

Snipaste_2022-04-22_11-45-36.png

1. exports导出:

注意:exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出;

Snipaste_2022-04-22_11-47-18.png
另外一个文件中可以导入:
Snipaste_2022-04-22_11-47-30.png
上面这行完成了什么操作呢?理解下面这句话,Node中的模块化一目了然

  • 意味着main中的bar变量等于exports对象;
  • 也就是require通过各种查找方式,最终找到了exports这个对象;
  • 并且将这个exports对象赋值给了bar变量;
  • bar变量就是exports对象了;

Node中实现CommonJS的本质就是对象的引用赋值Snipaste_2022-04-22_11-50-49.png
Snipaste_2022-04-22_11-53-45.png

深入:module.exports

其实导出的真正实现者的是module.exports,只是官方默认module.exports=exportsSnipaste_2022-04-22_12-33-35.png
一旦module.exports修改了对象,则导出的对象就与exports无关了Snipaste_2022-04-22_12-35-14.png

2. require细节

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

比较常见的查找规则

导入格式如下:require(X)
情况一:X是一个核心模块,比如path、http

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

情况二:X是以 ./ 或 ../ 或 /(根目录)开头的 (在本地目录中查找)
第一步:将X当做一个文件在对应的目录下查找;

  • 1.如果有后缀名,按照后缀名的格式查找对应的文件
  • 2.如果没有后缀名,会按照如下顺序:

    • 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不是一个核心模块
/Users/coderwhy/Desktop/Node/TestCode/04_learn_node/05_javascript-module/02_commonjs/main.js中编写require('why’)
Snipaste_2022-04-23_14-07-18.png
path是查找路径(上图可以由console.log(module)得到path)
如果上面的路径中都没有找到,那么报错:not found

3. 模块的加载过程

结论一:模块在被第一次引入时,模块中的js代码会被运行一次
扩: 加载过程是同步的
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次

  • 为什么只会加载运行一次呢?
  • 这是因为每个模块对象module都有一个属性:loaded。
  • 为false表示还没有加载,为true表示已经加载;

结论三:如果有循环引入,那么加载顺序是什么?
如果出现下图模块的引用关系,那么加载顺序是什么呢?
Snipaste_2022-04-23_14-26-29.png
这个其实是一种数据结构:图结构;
图结构在遍历的过程中,有深度优先搜索(DFS, depth first search)和广度优先搜索(BFS, breadth first search);
Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb

4. CommonJS缺点

image.png

5. CMD规范(了解即可)

CMD规范也是应用于浏览器的一种模块化规范:

  • CMD 是Common Module Definition(通用模块定义)的缩写;
  • 它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来;
  • 但是目前CMD使用也非常少了;

CMD也有自己比较优秀的实现方案:

  • SeaJS

SeaJs
第一步:下载SeaJS

第二步:引入sea.js和使用主入口文件

  • seajs是指定主入口文件的

Snipaste_2022-04-23_15-03-01.pngSnipaste_2022-04-23_15-01-33.pngSnipaste_2022-04-23_15-01-10.pngSnipaste_2022-04-23_15-01-57.png

认识 ES Module

JavaScript没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJS、AMD、CMD等,
所以在ES推出自己的模块化系统时,大家也是兴奋异常。
ES Module和CommonJS的模块化有一些不同之处:

  • 一方面它使用了import和export关键字;
  • 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;

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

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

了解:采用ES Module将自动采用严格模式:use strict
如果你不熟悉严格模式可以简单看一下MDN上的解析;
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

1.案例代码结构组件

Snipaste_2022-04-23_16-25-14.png
如果直接在浏览器中运行代码,会报如下错误:
Snipaste_2022-04-23_16-31-03.png
这个在MDN上面有给出解释:

这里可以用VSCode中有一个插件:Live Server 来解决

2. export关键字

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

我们希望将其他中内容全部导出,它可以有如下的方式:
方式一:在语句声明的前面直接加上export关键字
方式二:将所有需要导出的标识符,放到export后面的 {}中
Snipaste_2022-04-23_16-40-34.png
注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;
所以: export {name: name},是错误的写法;
方式三:导出时给标识符起一个别名
Snipaste_2022-04-23_16-41-18.png

3.import关键字

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

导入内容的方式也有多种:

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

    注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容;
    Snipaste_2022-04-23_16-43-07.png

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

Snipaste_2022-04-23_16-44-20.png

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

Snipaste_2022-04-23_16-45-10.png

4. Export和import结合使用

补充:export和import可以结合使用Snipaste_2022-04-23_16-57-07.png
为什么要这样做呢?

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

5. default用法

前面我们学习的导出功能都是有名字的导出(named exports):

  • 在导出export时指定了名字;
  • 在导入import时需要知道具体的名字;

还有一种导出叫做默认导出(default export)

  • 默认导出export时可以不需要指定名字Snipaste_2022-04-23_17-02-36.png
  • 在导入时不需要使用 {},并且可以自己来指定名字; Snipaste_2022-04-23_17-03-28.png

Snipaste_2022-04-23_17-04-00.png

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

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

6. import函数

通过import加载一个模块,是不可以在其放到逻辑代码中的:
Snipaste_2022-04-23_17-24-48.png
因为:
Snipaste_2022-04-23_17-31-00.png
将属于Parse阶段的放入了运行阶段会导致JS引擎识别不了,会出错
而解决这个问题则要用‘import函数’:使用 import() 函数来动态加载(import() 函数是异步加载的,import函数返回的结果是一个Promise);
Snipaste_2022-04-23_17-36-52.png

ES Module解析流程

三个阶段:

  • 阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record);
  • 阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
  • 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中;

阶段一:构建阶段

解析的过程是一个静态分析(不会运行代码)的过程,且不会请求重复的js文件
image.png

阶段二和三:实例化阶段 – 求值阶段

image.png

目录
相关文章
|
8月前
|
JavaScript 前端开发 测试技术
如何编写JavaScript模块化代码
如何编写JavaScript模块化代码
47 0
|
4月前
|
JavaScript UED
js之模块化(2)
js之模块化(2)
|
5月前
|
JavaScript 前端开发 编译器
解锁JavaScript模块化编程新纪元:从CommonJS的基石到ES Modules的飞跃,探索代码组织的艺术与科学
【8月更文挑战第27天】随着Web应用复杂度的提升,JavaScript模块化编程变得至关重要,它能有效降低代码耦合度并提高项目可维护性及扩展性。从CommonJS到ES Modules,模块化标准经历了显著的发展。CommonJS最初专为服务器端设计,通过`require()`同步加载模块。而ES Modules作为官方标准,支持异步加载,更适合浏览器环境,并且能够进行静态分析以优化性能。这两种标准各有特色,但ES Modules凭借其更广泛的跨平台兼容性和现代语法逐渐成为主流。这一演进不仅标志着JavaScript模块化的成熟,也反映了整个JavaScript生态系统的不断完善。
59 3
|
4月前
|
JavaScript 前端开发 开发者
js之模块化(1)
js之模块化(1)
|
3月前
|
缓存 JavaScript 前端开发
Node.js模块化的基本概念和分类及使用方法
Node.js模块化的基本概念和分类及使用方法
53 0
|
4月前
|
JavaScript
js之模块化(3)
js之模块化(3)
|
5月前
|
前端开发 JavaScript
前端必会的JavaScript模块化
【8月更文挑战第3天】JavaScript模块化
37 1
|
5月前
|
存储 缓存 JavaScript
JavaScript——请列出目前主流的 JavaScript 模块化实现的技术有哪些?说出它们的区别?
JavaScript——请列出目前主流的 JavaScript 模块化实现的技术有哪些?说出它们的区别?
37 0
|
6月前
|
JavaScript 前端开发
node.js 导入导出模块(CommonJS模块化规范,ES6模块化规范)
node.js 导入导出模块(CommonJS模块化规范,ES6模块化规范)
72 1
|
7月前
|
JavaScript 前端开发
JavaScript进阶-Class与模块化编程
【6月更文挑战第21天】**ES6引入Class和模块化,提升JavaScript的代码组织和复用。Class是原型机制的语法糖,简化面向对象编程。模块化通过`import/export`管理代码,支持默认和命名导出。常见问题包括`this`指向和循环依赖。理解这些问题及避免策略,能助你写出更高效、可维护的代码。**
69 5