# JavaScript 错误处理、LHS 左查询、RHS 右查询

TIP

错误处理在编程中的重要性毋庸置疑,所有现代桌面浏览器都会通过控制台暴露错误。

作为开发人员,了解这些错误,并正确的处理他们是非常必要的。

接下来我们会从以下三个方面来展开讲解

  • 常见的错误类型
  • try...catch 错误捕获与处理
  • finally 子句
  • throw 抛出自定义错误

# 一、常见错误类型

TIP

代码执行过程中会发生各种类型的错误,每种类型的错误都对应一个错误对象,常见的错误类型有以上 8 种

  • SyntaxError 语法错误
  • ReferenceError 引用错误
  • TypeError 类型错误
  • RangeError 范围错误
  • URIError URI 错误
  • EvalError eval 错误
  • InternalError 内部错误(非标准)
  • Error 错误对象,用于自定义错误

接下来,我们就一个一个简单介绍下

# 1、SyntaxError 语法错误

TIP

JavaScript 引擎发现了不符合语法规范的代码,会出现此错误

// 变量名不规范
var this = 1;
var 2a=3;

// if 少了 }
if (1) {
    console.log(2);

image-20221025184448555

# 2、ReferenceError 引用错误

TIP

会在找不到对象时发生,比如常见的访问不存在的变量时,就会报引用错误

console.log(a); // a变量不存在
var num = c; // c变是不存在

image-20221025184933070

# 3、TypeError 类型错误

TIP

  • 当变量不是预期类型时,就会引发这种类型错误。
  • 比如常见引发这一错误的行为:把一个基本数据类型当函数调用。
var a = 1;
a();

console.log("name" in true); // true不是一个对象

var arr = [1, 2, 3];
console.log(arr.call(null)); // arr 不是一个函数

image-20221025185530556

# 4、RangeError 范围错误

TIP

当数字超出允许的值范围时,将会抛出此类型的错误

var arr = [1, 2, 3];
arr.length = -1; // 数组长度

var num = 1.233;
num.toFixed(-1); // 参数在0-100之间

image-20221025194106345

# 5、URIError URI 错误

TIP

在了解 URIError 错误之前,我们先来了解下什么是 URI、URL、URN

  • URI(Uniform Resource Identifier):统一资源标识符 ,用来唯一标识资源,是一种语义上的抽象概念
  • URL(Uniform Resource Locator):统一资源定位符,用来定位唯一的资源, 必须提供足够的定位信息
  • URN(Uniform Resource Name):统一资源名称,定义了资源的身份(命名)

他们三者的关系如下图

image-20221025201015940

如果用一个人来做比喻的话

  • URN 相当人的身份证号,唯 一标识。
  • URL 相当于找到这个人的方式,如住址: x 省 xx 市 xx 区 ... xx 单元 xx 室的身份证号为 xxx 的人
  • URI 相当身份证,上面有身份证号,也有地址。

如果用一个网址来表示的话

  • URL: https://www.arryblog.com/guide/html5/html-html5-course.htm
  • URN:guide/html5/html-html5-course.htm
  • URI: 可以是上面的 ULR,也可以是 URN

# 6、URIError 错误

TIP

  • URL 错误只会在encodeURI()decodeURI() 传入了格式错误的 URI 时发生。但我们很难看到这种错误,因为前面两个函数非常稳键。
  • encodeURI() 对传入的 URI 进行编码
  • decodeURI() 对传入的 URI 进行解码
var url = "https://www.arryblog.com/html/文档笔记.html";
var enurl = encodeURI(url);
var deurl = decodeURI(enurl);
console.log(enurl);
console.log(deurl);

image-20221025203328098

# 7、EvalError eval 错误

TIP

在使用eval() 函数时发生异常时抛出错误。因为eval被禁用,所以了解就好。

# 8、InternalError(非标准)内部错误

TIP

该错误在 JS 引擎内部发生发生异常时会抛出。如果真发生了这种错误,很可能代码哪里弄错了或者有危险了。

# 9、Error 自定义错误

throw new Error("自定义错误");

image-20221025204448640

# 10、try ... catch 语句

TIP

try...catch 语句用来处理 JS 中的异常

其语法如下

try {
  // 可能会出错的代码
} catch (e) {
  // 出错时要做什么
}

如果try块中的代码发生错误,则会立即退出执行,并跳到catch块中,catch块此时会接收到一个对象,这个对象包含发生错误的相关信息。

var a = 1;
try {
  // 可能出错的代码
  a();
  console.log(2);
} catch (e) {
  // 出错时要做的事
  console.log(e);
  console.log(e.message);
}

image-20221025210450182

try捕获到错误后,不会再执行try错误后的代码,同时将错误交给catch来处理,然后再执行后面的代码

var a = 1;
try {
  // 可能出错的代码
  a();
  console.log(22); // 不执行
} catch (e) {
  // 出错时要做的事
  console.log(e); // 执行
}
console.log("try后面代码" + a); // 执行

image-20221025210852010

# 11、finally 子句

TIP

  • finally子句是try...catch语句中的一部分,为可选的
  • 不管try中代码是否发生错误,最终都会执行finally子句中的代码
try {
  // 可能会出错的代码
} catch (e) {
  // 出错时要做什么
} finally {
  //永远都会执行的代码
}
var a = 1;
try {
  // 可能出错的代码
  // a();
  console.log(22);
} catch (e) {
  // 出错时要做的事
  console.log(e);
} finally {
  console.log("不管try中代码是否报错,我都会执行");
}

image-20221025211437695

trycatch中的return 语句也无法阻止 finally 代码块的执行

var a = 1;
function fn(a) {
  try {
    // 可能出错的代码
    a();
    return 1;
  } catch (e) {
    // 出错时要做的事
    return 2;
  } finally {
    return 3;
  }
}
console.log(fn(a));

image-20221025211905573

# 12、throw 抛出自定义错误

TIP

throw 操作符,用于抛出自定义错误。throw 操作符后面必须有一个值,值可以是任意类型。

throw "123";
throw "abc";
throw true;

console.log(2); // 不会被执行

// 使用 throw操作符时,代码立即停止执行,除非用try...catch捕获了抛出的错误

throwtry...catch 结合

try {
  throw 123;
} catch (e) {
  console.log(e.message); // undefined
}
console.log(2); // 正常执行

可以自定义一个错误类型的消息,浏览器会像处理自己生成的错误一样来处理这个自定义错误

try {
  // throw new Error("自定义Error错误");
  // throw new TypeError("自定义TypeError错误");
  // throw new SyntaxError("自定义SyntaxError错误");
  throw new RangeError("自定义RangeError错误");
} catch (e) {
  console.log(e);
}
console.log(2); // 正常执行
function fn(arr) {
  try {
    return arr.slice(0, 1);
  } catch (e) {
    throw new TypeError("arr参数,必须是一个数组");
  }
  console.log(arr); // 上面抛出错误,这里不会执行
}
fn(1);

image-20221025214614144

# 二、LHS 和 RHS 查询

TIP

LHS字面理解是 Left Hand Sid 即左手边,RHS字面理解是 Right Hand Side 即右手边。

那谁的左边和右边呢?

# 1、LHS 和 RHS 片面的理解

TIP

一般简单片面的理解为 赋值操作符的左边和右边

  • 变量出现在赋值操作符的左侧时进行LHS 查询,出现在右侧时进行RHS 查询
  • LHS 查询是试图找到变量的容器本身,然后对其赋值
  • RHS 查询是试图找到变量的中对应的值
var a;
var b;

a = 1;
b = a;

对以上代码进行解读

① 编译阶段

会在当前作用域中声明 2 个变量 a 和 b

② 执行阶段

  • a = 1,这里的 a 会进行 LHS 查询,查询是否存在变量 a,并不关心 a 的值是什么。如果变量 a 存在,就给他赋值为 1
  • b = a,首先会对 a 进行 RHS 查询,查询是否存在变量 a,存在,并把对应的值 1 拿到
  • 然后再对 b 进行 LHS 查询,查询是否存在变量 b,存在,并把对应的值 1 赋值给到变量 b

# 2、LHS 和 RHS 更准确的理解

TIP

通过上面分析,我们知道

  • LHS 查询只关心是否存在这个变量,并不关心变量对应的值是什么
  • RHS 查询除了关心变量是否存在之外,还关心变量中对应的值,因为最终就是为了找到变量对应值。

所以,更准确的说

  • LHS 查询可以理解为赋值操作的目标是谁 ? 即我要把值赋值给谁
  • RHS 查询可以理解为 赋值操作的源头是谁? 我要用什么来内容完成赋值
function fn(c) {
  console.log(c);
  console.log(b);
  console.log(a);
  a = 1;
  var b = a;
  b();
}
fn(2);

以上代码在执行过程中,经历了几次 LHS 查询和 RHS 查询

我们先来分析下

整个代码从编译到执行的整个过程

① 全局代码运行时

编译阶段

声明函数 fn,fn 是 window 对象身上的属性,即window.fn=function(){......}

执行阶段

fn(2)在调用前,会对 fn 进行 RHS 查询,查询是否存在 fn,并拿到 fn 的值,然后开始执行

② fn 函数调用时

编译阶段:

  • 在当前作用域中声明 2 个变量 c 和 b,同时c = 2
  • 这个过程 c 也会进行 LHS 查询,找到是否存在变量 c,然后存在将值 2 赋值给到他

执行阶段:

  • 当执行到console.log(c) ,会对 c 进行 RHS 查询,取得 c 的值为 2,还会对 console 进行 RHS 查询
  • 当执行到 console.log(b),会对 b 进行 RHS 查询,取得 c 的值为 undefined,对 console 进行 RHS 查询
  • 当执行到console.log(a),会对 c 进行 RHS 查询,发现找不到变量 a,然后报出 引用错误,....
  • 当执行到a = 1,会对 a 进行 LHS 查询,发现找不到变量 a
    • 在非严格模式下,此时会全局作用域中创建一个同名的变量,然后将 1 赋值给 a
    • 在严格模式下,会报 引用错误,找不到变量 a
  • 当执行到b = a
    • 首先对 a 进行 RHS 查询,找到变量 a,并取得 a 的值 1
    • 然后对 b 进行 LHS 查询,找到变量 b,把对应的值 1 赋值给 b
  • 当执行到b()时,会对 b 进行 RHS 查询,找到了,但 b 的值不是一个函数,你对 b 执行函数调用,会抛出类型错误(TypeError)

# 3、总结:LHS 和 RHS

TIP

LHS 查询

  • 只关心有没有要查找的变量,并不关心变量的值是多少
  • 如果找到这个变量,就将对应值赋值给他。
  • 如果找不到,在严格模式下,会报引用错误,非严格模式下,会在全局作用域中创建一个同名的变量
  • LHS 查询可以理解为 赋值操作的目标是谁 ? 即我要把值赋值给谁

RHS 查询

  • 他不仅关心有没有要查找的变量,还关心变量的值是多少,最终是要取到变量的值。
  • 如果找不到变量,则会报引用错误,如果找到,则把对应的值拿到
  • 如果对找到的变量,做不合理的操作,会抛出类型错误(TypeError)
  • RHS 查询可以理解为 赋值操作的源头是谁 ? 我要用什么来内容完成赋值

JS 引擎在查找某个变量时,会先判断是要 LHS 查询还是 RHS 查询,然后再根据对应规则去查询变量,做相应处理

# 三、总结重难点

TIP

总结上一章节和本章重难点知识,理清思路,把握重难点。并能轻松回答以下问题,说明自己就真正的掌握了。

用于故而知新,快速复习。

# 1、重点

TIP

  • 什么是执行上下文、执行上下文的分类 ?

执行上下文分类:全局执行上下文,函数执行上下文,eval 执行上下文

  • 什么是执行上下文栈(调用栈)?
  • 什么是栈溢出 ?什么情况下会出现栈溢出
  • 什么是变量对象(VO)、活动对象及创建过程 ?
    • 第一步创建一个AO = { }
    • 第二步处理 arguments,把 arguments 当对 AO 对象身上的属性
    • 第三步处理形参和对应实参 AO = {a: 2}
    • 第四步处理 函数内部的函数声明 AO = {fn: function(){ }}
    • 第四步处理 函数内部的 变量声明 AO = {b: undefined}
  • 说说变量查找的整个过程 ?
  • 掌握try...catch的用法

# 2、难点

TIP

  • 什么是闭包 ?
  • 什么情况下会形成闭包 ?
  • 闭包主要是解决什么问题 ?
  • 闭包的实际应用
  • 什么是垃圾回收 ?
  • 垃圾回收的两种策略 ?如何手动标记垃圾
  • 什么是内存泄露,什么是内存溢出 ?
  • 闭包会造成内存泄露吗 ?
  • IIFE 的的作用
上次更新时间: 6/8/2023, 9:23:17 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X