返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript错误和调用栈常识都有哪些
  • 373
分享到

JavaScript错误和调用栈常识都有哪些

2024-04-02 19:04:59 373人浏览 泡泡鱼
摘要

javascript错误和调用栈常识都有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。大多数工程师可能并没留意过 js 中错误对象、错误堆

javascript错误和调用栈常识都有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

大多数工程师可能并没留意过 js 中错误对象、错误堆栈的细节,即使他们每天的日常工作会面临不少的报错,部分同学甚至在 console  的错误面前一脸懵逼,不知道从何开始排查,如果你对本文讲解的内容有系统的了解,就会从容很多。而错误堆栈清理能让你有效去掉噪音信息,聚焦在真正重要的地方,此外,如果理解了  Error 的各种属性到底是什么,你就能更好的利用他。

接下来,我们就直奔主题。

调用栈的工作机制

在探讨 JS 中的错误之前,我们必须理解调用栈(Call Stack)的工作机制,其实这个机制非常简单,如果你对这个已经一清二楚了,可以直接跳过这部分内容。

简单的说:函数被调用时,就会被加入到调用栈顶部,执行结束之后,就会从调用栈顶部移除该函数,这种数据结构的关键在于后进先出,即大家所熟知的 LIFO。比如,当我们在函数 y 内部调用函数 x 的时候,调用栈从下往上的顺序就是 y -> x 。

我们再举个代码实例:

function c() {console.log('c');
}function b() {console.log('b');
    c();
}function a() {console.log('a');
    b();
}

a();

这段代码运行时,首先 a 会被加入到调用栈的顶部,然后,因为 a 内部调用了 b,紧接着 b 被加入到调用栈的顶部,当 b 内部调用 c  的时候也是类似的。在调用 c的时候,我们的调用栈从下往上会是这样的顺序:a -> b -> c。在 c 执行完毕之后,c  被从调用栈中移除,控制流回到 b 上,调用栈会变成:a -> b,然后 b 执行完之后,调用栈会变成:a,当 a  执行完,也会被从调用栈移除。

为了更好的说明调用栈的工作机制,我们对上面的代码稍作改动,使用 console.trace 来把当前的调用栈输出到 console 中,你可以认为console.trace 打印出来的调用栈的每一行出现的原因是它下面的那行调用而引起的。

function c() {console.log('c');console.trace();
}function b() {console.log('b');
    c();
}function a() {console.log('a');
    b();
}

a();

当我们在 node.js 的 REPL 中运行这段代码,会得到如下的结果:

Traceat c (repl:3:9)
    at b (repl:3:1)
    at a (repl:3:1)
    at repl:1:1 // <-- 从这行往下的内容可以忽略,因为这些都是 node 内部的东西at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)

显而易见,当我们在 c 内部调用 console.trace 的时候,调用栈从下往上的结构是:a -> b -> c。如果把代码再稍作改动,在 b 中 c 执行完之后调用,如下:

function c() {console.log('c');
}function b() {console.log('b');
    c();console.trace();
}function a() {console.log('a');
    b();
}

a();

通过输出结果可以看到,此时打印的调用栈从下往上是:a -> b,已经没有 c 了,因为 c 执行完之后就从调用栈移除了。

Trace
    at b (repl:4:9)
    at a (repl:3:1)
    at repl:1:1  // <-- 从这行往下的内容可以忽略,因为这些都是 Node 内部的东西at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:513:10)

再总结下调用栈的工作机制:调用函数的时候,会被推到调用栈的顶部,而执行完毕之后,就会从调用栈移除。

Error 对象及错误处理

当代码中发生错误时,我们通常会抛出一个 Error 对象。Error 对象可以作为扩展和创建自定义错误类型的原型。Error 对象的 prototype 具有以下属性:

  • constructor &ndash; 负责该实例的原型构造函数;

  • message &ndash; 错误信息;

  • name &ndash; 错误的名字;

上面都是标准属性,有些 JS 运行环境还提供了标准属性之外的属性,如 Node.js、Firefox、Chrome、Edge、IE 10、Opera 和 Safari 6+ 中会有 stack 属性,它包含了错误代码的调用栈,接下来我们简称错误堆栈。错误堆栈包含了产生该错误时完整的调用栈信息。如果您想了解更多关于 Error 对象的非标准属性,我强烈建议你阅读 MDN 的这篇文章。

抛出错误时,你必须使用 throw 关键字。为了捕获抛出的错误,则必须使用 try catch 语句把可能出错的代码块包起来,catch  的时候可以接收一个参数,该参数就是被抛出的错误。与 Java 中类似,JS 中也可以在 try catch 语句之后有  finally,不论前面代码是否抛出错误 finally 里面的代码都会执行,这种语言的常见用途有:在 finally 中做些清理的工作。

此外,你可以使用没有 catch 的 try 语句,但是后面必须跟上 finally,这意味着我们可以使用三种不同形式的 try 语句:

  • try &hellip; catch

  • try &hellip; finally

  • try &hellip; catch &hellip; finally

try 语句还可以嵌套在 try 语句中,比如:

try {try {throw new Error('Nested error.'); // 这里的错误会被自己紧接着的 catch 捕获} catch (nestedErr) {console.log('Nested catch'); // 这里会运行}
} catch (err) {console.log('This will not run.');  // 这里不会运行}

try 语句也可以嵌套在 catch 和 finally 语句中,比如下面的两个例子:

try {throw new Error('First error');
} catch (err) {console.log('First catch running');try {throw new Error('Second error');
    } catch (nestedErr) {console.log('Second catch running.');
    }
}
try {console.log('The try block is running...');
} finally {try {throw new Error('Error inside finally.');
    } catch (err) {console.log('Caught an error inside the finally block.');
    }
}

同样需要注意的是,你可以抛出不是 Error 对象的任意值。这可能看起来很酷,但在工程上却是强烈不建议的做法。如果恰巧你需要处理错误的调用栈信息和其他有意义的元数据,抛出非 Error 对象的错误会让你的处境很尴尬。

假如我们有如下的代码:

function runWithoutThrowing(func) {try {
        func();
    } catch (e) {console.log('There was an error, but I will not throw it.');console.log('The error\'s message was: ' + e.message)
    }
}function funcThatThrowsError() {throw new TypeError('I am a TypeError.');
}

runWithoutThrowing(funcThatThrowsError);

如果 runWithoutThrowing 的调用者传入的函数都能抛出 Error 对象,这段代码不会有任何问题,如果他们抛出了字符串那就有问题了,比如:

function runWithoutThrowing(func) {try {
        func();
    } catch (e) {console.log('There was an error, but I will not throw it.');console.log('The error\'s message was: ' + e.message)
    }
}function funcThatThrowsString() {throw 'I am a String.';
}

runWithoutThrowing(funcThatThrowsString);

这段代码运行时,runWithoutThrowing 中的第 2 次 console.log 会抛出错误,因为 e.message  是未定义的。这些看起来似乎没什么大不了的,但如果你的代码需要使用 Error  对象的某些特定属性,那么你就需要做很多额外的工作来确保一切正常。如果你抛出的值不是 Error 对象,你就不会拿到错误相关的重要信息,比如  stack,虽然这个属性在部分 JS 运行环境中才会有。

Error 对象也可以向其他对象那样使用,你可以不用抛出错误,而只是把错误传递出去,Node.js 中的错误优先回调就是这种做法的典型范例,比如 Node.js 中的 fs.readdir 函数:

const fs = require('fs');

fs.readdir('/example/i-do-not-exist', function callback(err, dirs) {if (err) {// `readdir` will throw an error because that directory does not exist// We will now be able to use the error object passed by it in our callback functionconsole.log('Error Message: ' + err.message);console.log('See? We can use Errors without using try statements.');
    } else {console.log(dirs);
    }
});

此外,Error 对象还可以用于 Promise.reject 的时候,这样可以更容易的处理 Promise 失败,比如下面的例子:

new Promise(function(resolve, reject) {
    reject(new Error('The promise was rejected.'));
}).then(function() {console.log('I am an error.');
}).catch(function(err) {if (err instanceof Error) {console.log('The promise was rejected with an error.');console.log('Error Message: ' + err.message);
    }
});

错误堆栈的裁剪

Node.js 才支持这个特性,通过 Error.captureStackTrace  来实现,Error.captureStackTrace 接收一个 object 作为第 1 个参数,以及可选的 function 作为第 2  个参数。其作用是捕获当前的调用栈并对其进行裁剪,捕获到的调用栈会记录在第 1 个参数的 stack 属性上,裁剪的参照点是第 2  个参数,也就是说,此函数之前的调用会被记录到调用栈上面,而之后的不会。

让我们用代码来说明,首先,把当前的调用栈捕获并放到 myObj 上:

const myObj = {};function c() {
}function b() {// 把当前调用栈写到 myObj 上Error.captureStackTrace(myObj);
    c();
}function a() {
    b();
}// 调用函数 aa();// 打印 myObj.stackconsole.log(myObj.stack);// 输出会是这样//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack//    at a (repl:2:1)//    at repl:1:1 <-- Node internals below this line//    at realRunInThisContextScript (vm.js:22:35)//    at sigintHandlersWrap (vm.js:98:12)//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)//    at REPLServer.defaultEval (repl.js:313:29)//    at bound (domain.js:280:14)//    at REPLServer.runBound [as eval] (domain.js:293:12)//    at REPLServer.onLine (repl.js:513:10)

上面的调用栈中只有 a -> b,因为我们在 b 调用 c 之前就捕获了调用栈。现在对上面的代码稍作修改,然后看看会发生什么:

const myObj = {};function d() {// 我们把当前调用栈存储到 myObj 上,但是会去掉 b 和 b 之后的部分Error.captureStackTrace(myObj, b);
}function c() {
    d();
}function b() {
    c();
}function a() {
    b();
}// 执行代码a();// 打印 myObj.stackconsole.log(myObj.stack);// 输出如下//    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called//    at repl:1:1 <-- Node internals below this line//    at realRunInThisContextScript (vm.js:22:35)//    at sigintHandlersWrap (vm.js:98:12)//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)//    at REPLServer.defaultEval (repl.js:313:29)//    at bound (domain.js:280:14)//    at REPLServer.runBound [as eval] (domain.js:293:12)//    at REPLServer.onLine (repl.js:513:10)//    at emitOne (events.js:101:20)

在这段代码里面,因为我们在调用 Error.captureStackTrace 的时候传入了 b,这样 b 之后的调用栈都会被隐藏。

现在你可能会问,知道这些到底有啥用?如果你想对用户隐藏跟他业务无关的错误堆栈(比如某个库的内部实现)就可以试用这个技巧。

看完上述内容,你们掌握JavaScript错误和调用栈常识都有哪些的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程网JavaScript频道,感谢各位的阅读!

--结束END--

本文标题: JavaScript错误和调用栈常识都有哪些

本文链接: https://lsjlt.com/news/86117.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JavaScript错误和调用栈常识都有哪些
    JavaScript错误和调用栈常识都有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。大多数工程师可能并没留意过 JS 中错误对象、错误堆...
    99+
    2024-04-02
  • Python3错误和异常知识点有哪些
    这篇文章主要讲解了“Python3错误和异常知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python3错误和异常知识点有哪些”吧!语法错误Python 的语法错误或者称之为解析...
    99+
    2023-06-02
  • DIV+CSS编码常见错误都有哪些
    这期内容当中小编将会给大家带来有关DIV+CSS编码常见错误都有哪些,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。DIV+CSS是网站标准(...
    99+
    2024-04-02
  • mysql常识和基本操作都有哪些
    这篇文章给大家介绍mysql常识和基本操作都有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 mysql常识和基本操作 字段类型    1.INT[(...
    99+
    2024-04-02
  • 避免的常见JavaScript错误的9个编码都有哪些
    这篇文章给大家介绍避免的常见JavaScript错误的9个编码都有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。有一些非常常见的编程错误是我们要避免的,以保持代码的干净和可读性。一...
    99+
    2024-04-02
  • JavaScript中常见的错误有哪些
    这篇文章主要讲解了“JavaScript中常见的错误有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript中常见的错误有哪些”吧!Java...
    99+
    2024-04-02
  • mysql5.6错误代码都有哪些
    这期内容当中小编将会给大家带来有关mysql5.6错误代码都有哪些,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。版本5.6  MySQL 5.6参考手...
    99+
    2024-04-02
  • JavaScript错误有哪些
    这篇“JavaScript错误有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java...
    99+
    2024-04-02
  • 常见的JavaScript内存错误有哪些
    本文小编为大家详细介绍“常见的JavaScript内存错误有哪些”,内容详细,步骤清晰,细节处理妥当,希望这篇“常见的JavaScript内存错误有哪些”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,...
    99+
    2024-04-02
  • 常用的JavaScript知识点有哪些
    这篇文章主要介绍“常用的JavaScript知识点有哪些”,在日常操作中,相信很多人在常用的JavaScript知识点有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”常用的JavaScript知识点有哪些...
    99+
    2023-07-05
  • Python3使用中有哪些错误和异常
    这篇文章主要讲解了“Python3使用中有哪些错误和异常”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python3使用中有哪些错误和异常”吧!语法错误Python 的语法错误或者称之为解析...
    99+
    2023-06-27
  • JavaScript常见标识符有哪些
    这篇文章给大家分享的是有关JavaScript常见标识符有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。JavaScript常见标识符有哪些JavaScript有什么特点1、...
    99+
    2024-04-02
  • MySQL常见错误有哪些
    小编给大家分享一下MySQL常见错误有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、Can’t connect to M...
    99+
    2024-04-02
  • 常见CSS错误有哪些
    这篇文章将为大家详细讲解有关常见CSS错误有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。代码如下:区别IE6与FF: background:orange;*background:blue; 区别IE...
    99+
    2023-06-08
  • golang常见错误有哪些
    golang常见错误有:1、空指针引用错误;2、切片越界错误;3、并发竞争;4、垃圾回收性能问题;5、死锁;6、异常处理不当。本教程操作环境:windows10系统、golang1.20.1版本、DELL G3电脑。Golang是一种在近年...
    99+
    2023-07-10
  • 用promise的常见错误有哪些
    这篇文章主要为大家展示了“用promise的常见错误有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“用promise的常见错误有哪些”这篇文章吧。Promi...
    99+
    2024-04-02
  • JavaScript之ES6常用基础知识有哪些
    这篇文章主要介绍了JavaScript之ES6常用基础知识有哪些,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。ES6 let与const及其...
    99+
    2024-04-02
  • 常见的JavaScript知识点有哪些
    这篇文章主要讲解了“常见的JavaScript知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“常见的JavaScript知识点有哪些”吧!1.声明...
    99+
    2024-04-02
  • JavaScript中栈和堆的区别有哪些
    本篇内容主要讲解“JavaScript中栈和堆的区别有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript中栈和堆的区别有哪些”吧! ...
    99+
    2024-04-02
  • php异常和错误的区别有哪些
    这篇文章给大家分享的是有关php异常和错误的区别有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。php异常和错误的区别:1、PHP错误是属于php程序自身的问题,一般是由非法的语法,环境问题导致的;2、PHP...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作