错误处理方案
函数出现错误处理
通过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("后续的代码会继续运行~")
抛出异常的补充
抛出异常的分类有:
- 抛出一个字符串类型(基本的数据类型):
throw "error"
- 比较常见的是抛出一个对象类型:
throw { errorCode: -1001, errorMessage: "type不能为0~" }
- 创建类, 并且创建这个类对应的对象:
throw new HYError(-1001, "type不能为0~")
- 提供了一个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"
- Error的子类:
const err = new TypeError("当前type类型是错误的~")
处理抛出的异常
两种处理方法:
- 第一种是不处理, 那么异常会进一步的抛出, 直到最顶层的调用
如果在最顶层也没有对这个异常进行处理, 那么我们的程序就会终止执行, 并且报错
- 使用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 {
}
什么是模块化
那么,到底什么是模块化开发呢?
- 事实上模块化开发最终的目的是将程序划分成一个个小的结构;
- 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;
- 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;
- 也可以通过某种方式,导入另外结构中的变量、函数、对象等;
// 导出
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中对其进行了实现:
案例时间:
1. exports导出:
注意:exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出;
另外一个文件中可以导入:
上面这行完成了什么操作呢?理解下面这句话,Node中的模块化一目了然
- 意味着main中的bar变量等于exports对象;
- 也就是require通过各种查找方式,最终找到了exports这个对象;
- 并且将这个exports对象赋值给了bar变量;
- bar变量就是exports对象了;
深入:module.exports
其实导出的真正实现者的是module.exports,只是官方默认module.exports=exports
一旦module.exports修改了对象,则导出的对象就与exports无关了
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’)
path是查找路径(上图可以由console.log(module)得到path)
如果上面的路径中都没有找到,那么报错:not found
3. 模块的加载过程
结论一:模块在被第一次引入时,模块中的js代码会被运行一次
扩: 加载过程是同步的
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次
- 为什么只会加载运行一次呢?
- 这是因为每个模块对象module都有一个属性:loaded。
- 为false表示还没有加载,为true表示已经加载;
结论三:如果有循环引入,那么加载顺序是什么?
如果出现下图模块的引用关系,那么加载顺序是什么呢?
这个其实是一种数据结构:图结构;
图结构在遍历的过程中,有深度优先搜索(DFS, depth first search)和广度优先搜索(BFS, breadth first search);
Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb
4. CommonJS缺点
5. CMD规范(了解即可)
CMD规范也是应用于浏览器的一种模块化规范:
- CMD 是Common Module Definition(通用模块定义)的缩写;
- 它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来;
- 但是目前CMD使用也非常少了;
CMD也有自己比较优秀的实现方案:
- SeaJS
SeaJs
第一步:下载SeaJS
- 下载地址:https://github.com/seajs/seajs
- 找到dist文件夹下的sea.js
第二步:引入sea.js和使用主入口文件
- seajs是指定主入口文件的
认识 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.案例代码结构组件
如果直接在浏览器中运行代码,会报如下错误:
这个在MDN上面有给出解释:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
- 你需要注意本地测试 — 如果你通过本地加载Html 文件 (比如一个 file://路径的文件), 你将会遇到 CORS 错误,因
- 为Javascript 模块安全性需要。
- 你需要通过一个服务器来测试。
这里可以用VSCode中有一个插件:Live Server 来解决
2. export关键字
export关键字将一个模块中的变量、函数、类等导出;
我们希望将其他中内容全部导出,它可以有如下的方式:
方式一:在语句声明的前面直接加上export关键字
方式二:将所有需要导出的标识符,放到export后面的 {}中
注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;
所以: export {name: name},是错误的写法;
方式三:导出时给标识符起一个别名
3.import关键字
import关键字负责从另外一个模块中导入内容
导入内容的方式也有多种:
- 方式一:import {标识符列表} from '模块';
注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容;
- 方式二:导入时给标识符起别名
- 方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上
4. Export和import结合使用
补充:export和import可以结合使用
为什么要这样做呢?
5. default用法
前面我们学习的导出功能都是有名字的导出(named exports):
- 在导出export时指定了名字;
- 在导入import时需要知道具体的名字;
还有一种导出叫做默认导出(default export)
- 默认导出export时可以不需要指定名字;
- 在导入时不需要使用 {},并且可以自己来指定名字;
- 它也方便我们和现有的CommonJS等规范相互操作;
注意:在一个模块中,只能有一个默认导出(default export);
6. import函数
通过import加载一个模块,是不可以在其放到逻辑代码中的:
因为:
将属于Parse阶段的放入了运行阶段会导致JS引擎识别不了,会出错
而解决这个问题则要用‘import函数’:使用 import() 函数来动态加载(import() 函数是异步加载的,import函数返回的结果是一个Promise);
ES Module解析流程
三个阶段:
- 阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record);
- 阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
- 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中;
阶段一:构建阶段
解析的过程是一个静态分析(不会运行代码)的过程,且不会请求重复的js文件