-

call stack是什么错误

来源:小高教学网 作者:本站 时间:2023-05-10 23:38:01 阅读次数:0

近期不少网友都在问:call stack是什么错误,小编也是查阅很多资料,整理了一些相关方面的答案,大家可以参考一下。

【温馨提示】本文共有24522个字,预计阅读完需要62分钟,请仔细阅读哦!


目录:

JavaScript报错了?不要慌!怎么看怎么处理都在这里



在开发中,有时,我们花了几个小时写的 JS 代码,在游览器调试一看,控制台一堆红,瞬间一万头草泥马奔腾而来。至此,本文主要记录 JS 常见的一些报错类型,以及常见的报错信息,分析其报错原因,并给予处理方法。并且将介绍几种捕获异常的方法。


常见的错误类型

RangeError:标记一个错误,当设置的数值超出相应的范围触发。比如,new Array(-20)。


ReferenceError:引用类型错误,当一个不存在的变量被引用时发生的错误。比如:console.log(a)。


SyntaxError:语法错误。比如 if(true) {。


TypeError:类型错误,表示值的类型非预期类型时发生的错误。


常见的错误

RangeError: Maximum call stack size exceeded


含义:超出了最大的堆栈大小


为什么报错?


在使用递归时消耗大量堆栈,导致游览器抛出错误,因为游览器给分配的内存不是无限的。


举个栗子


function pow(x, n) { return x * pow(x, n - 1);}pow(10,5);

处理办法


使用递归的时候,设定一个条件来终止递归,否则会无限循环,直到用尽调用堆栈空间为止。


function pow(x, n) { if (n == 1) return x; return x * pow(x, n - 1); }pow(10,5);



ReferenceError: "x" is not defined


含义:“x”未定义


为什么报错?


当你引用一个没有定义的变量时,抛出一个ReferenceError; 当你使用变量的时候,这个变量必须要声明,或者你可以确保它在你当前的脚本或作用域 (scope) 中可用。


举个栗子


// 变量未声明console.log(a);fn();// 错误的作用域 function sum() { let number1 = 20,number2 = 30; return number1 number2;}console.log(number1);



处理办法


1. 变量使用var|let|const声明


2. 提升变量的作用域


// 变量未声明let a;function fn() {};console.log(a);fn();// 错误的作用域let number1 = 20, number2 = 30;function sum() { return number1 number2;}console.log(number1);



SyntaxError: Identifier 'x' has already been declared


含义: 标识符已声明


为什么报错?


某个变量名称已经作为参数出现了,又在使用let再次声明。


举个栗子


// let 重复声明let a = 0;let a = 2;// 在函数中参数已经出现,函数里使用let重新声明function fn(arg) { let arg = [];}



SyntaxError: Invalid or unexpected token


含义:捕获无效或意外的标记


为什么报错?


代码中有非法的字符或者缺少必要的标识符号,比如减号 ( - ) 与连接符 ( – ) ,或者是英文双引号 ( " ) 与中文双引号 ( “ )。


举个栗子


// 遗漏的字符let str = 'string;let colors = ['#000', #333', '#666'];// 使用特殊字符let str1 = 'string";let str2 = 5#5;// 错配字符(使用中文引号字符)let str3 = ‘string’;



处理办法


检查是否有特殊字符或者是否遗漏一些字符。




SyntaxError: Unexpected end of input


含义:意外的终止输入


为什么报错?


代码中某些地方的括号或引号不匹配缺失,缺少()、[]、{}等。


举个栗子


// 缺少括号if(true)let obj = {id: 1let arr = [1,2,3// 缺少结束符号(function () { console.log('hello world');}()



处理办法


检查是否有特殊字符或者是否遗漏一些字符,括号需要配对出现。




TypeError: Cannot read property 'x' of undefined
TypeError: Cannot set property 'x' of undefined


含义:无法读取属性‘x’, 无法设置属性 'x'


为什么报错?


访问或设置未定义(undefined)或null值的属性时会发生这种报错。


举个栗子


// undefinedlet a = undefined;a.id; // 读取a.id = 1; // 设置// nulllet b = null;b.id; // 读取b.id = 2; // 设置null.filter(item=>item)



处理办法


有一些方法可以避免这种错误。一种简单且适用于小型属性链的方法是使用逻辑运算符&&。


let obj = undefined;console.log(obj&&obj.id);



TypeError: 'x' is not a constructor


含义:表示 ‘x’不是构造函数


为什么报错?


使用不是构造器的对象或者变量来作为构造器使用。比如:new 10;


举个栗子


var Car = 1;new Car();new Math();



处理办法


使用正确的构造函数。Generator functions 也不能作为构造器来使用。


function Car(make, model, year) { this.make = make; this.model = model; this.year = year;}



SyntaxError: Invalid regular expression flags


含义:正则表达式标志无效


为什么报错?


在代码中出现了无效的正则表达式的标记。


举个栗子


let reg = /foo/bar;



处理办法


let reg = /foo/g;



DOMException: Failed to execute 'open' on 'XMLHttpRequest': Invalid URL


含义:无效的Url


为什么报错?


在使用ajax 请求时url错误,导致请求失败


举个栗子


function createXHR(url) { var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function () { }}createXHR('http://192.168.10.8080'); // 错误urlcreateXHR('http:/192.168.10:8080/open'); // 缺少 /,注:有些游览器会自动补全



处理办法


检查url 请求是否正确,保证请求路径的完整。


createXHR('http://192.168.10:8080');



异常调试及捕获

try/catch


JS中处理异常的一种模式,try用于可能会发生错误的代码,catch对错误的处理


try{ // 可能会导致错误的代码}catch(error) { // 错误处理}



举个栗子


try{ console.log(a)}catch(error) { // 打印错误信息 console.log(error); // ReferenceError: a is not defined}



throw


用来抛出一个用户自定义的异常,执行将被停止。


function getUserName(name) { if(!name) throw new Error('用户名无效'); return name;}getUserName();



Promise 的异常处理


Promise执行中,本身自带try...catch的异常处理,出错时,将错误Rejact函数。


new Promise((resolve, reject) => { throw new Error('error!');}).catch(alert);



console.log() 方法


在游览器中,使用console.log打印javaScript的值。


let value = '你最棒了,点个赞呗!'console.log(value);



debugger 断点调试


用于停止执行 JavaScript,并调用调试函数。


let value = 15;debuggerdocument.querySelector('body').innerHTML = '你最棒了,点个赞呗!'



总结

报错就是那么简单,根据这些代码敲一敲,熟悉一些常用的报错信息,便于在报错的时候快速地定位到报错原因。希望对可爱的你有用。


关注我!私信要学习资料 ,源码 ,面试题!
关注我!私信要学习资料 ,源码 ,面试题
关注我!私信要学习资料 ,源码 ,面试题

10个JavaScript最常出现的错误

为了便于阅读,每个错误都被缩短了,让我们更深入地研究每一个问题,以确定是什么导致了这些问题,以及如何避免产生这些问题。


1. Uncaught TypeError: Cannot read property

如果你是一个JavaScript开发人员,你可能已经看到过这个错误。当你读取属性或在未定义对象上调用方法时,Chrome中就会发生这种情况。你可以在Chrome开发者控制台中轻松进行测试。


发生这种情况的原因有很多,但常见的原因是渲染UI组件时状态初始化不当。让我们来看一个在现实应用中如何发生这种情况的示例。我们将选择React,但是不正确初始化的相同原理也适用于Angular,Vue或任何其他框架。


class Quiz extends Component { componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return (

    {this.state.items.map(item =>
  • {item.name}
  • )}
); }}

这里有两件重要的事情要意识到:


  • 组件的状态(例如 this.state)以 undefined 状态开始使用。
  • 当你异步获取数据时,无论数据是在构造函数 componentWillMount 还是 componentDidMount 中获取,组件都将在数据加载之前至少渲染一次。当Quiz第一次渲染时,this.state.items 是 undefined。这反过来又意味着ItemList会得到未定义的items,你会在控制台中得到一个错误——"UncaughtTypeError: Cannot read property 'map' of undefined "的错误。

这很容易解决,最简单的方法:在构造函数中使用合理的默认值初始化状态。


class Quiz extends Component { // 添加了这个: constructor(props) { super(props); this.state = { items: [] // 默认值 }; } componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return (

    {this.state.items.map(item =>
  • {item.name}
  • )}
); }}

你的应用程序中的实际代码可能会有不同,但我希望已经给了你足够的线索,让你在你的应用程序中修复或避免这个问题。如果没有,请继续阅读,因为我将在下面介绍有关相关错误的更多示例。




2. TypeError: ‘undefined’ is not an object (evaluating

这是在Safari中读取属性或调用undefined对象上的方法时发生的错误,你可以在Safari开发者控制台中非常轻松地进行测试。这基本上与上述针对Chrome的错误相同,但Safari使用了不同的错误消息。


3. TypeError: null is not an object (evaluating

这是在Safari中读取属性或调用null对象上的方法时发生的错误,你可以在Safari开发者控制台中非常轻松地进行测试。


有趣的是,在JavaScript中,null和undefined不相同,这就是为什么我们看到两个不同的错误消息的原因。undefined通常是尚未分配的变量,而null表示该值为空白。 要验证它们是否相等,请尝试使用严格相等运算符。


在实际示例中可能发生这种错误的一种方式是,在加载元素之前尝试在JavaScript中使用DOM元素,这是因为DOM API对于空白的对象引用返回null。


任何执行和处理DOM元素的JS代码都应在创建DOM元素后执行。JS代码按照HTML格式从上到下进行解释,所以,如果在DOM元素之前有一个标签,那么在浏览器解析HTML页面的时候,脚本标签内的JS代码就会被执行。如果在加载脚本之前尚未创建DOM元素,则会出现此错误。


在此示例中,我们可以通过添加事件侦听器来解决该问题,该事件侦听器将在页面准备就绪时通知我们。一旦触发了 addEventListener,init() 方法就可以使用DOM元素。


4. (unknown): Script error

当未捕获的JavaScript错误违反跨源策略跨越域边界时,将发生脚本错误。例如,如果你将你的JavaScript代码托管在CDN上,任何未被捕获的错误(冒泡到window.onerror处理程序中的错误,而不是在try-catch中被捕获的错误)都会被报告为 "Script error",而不是包含有用的信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则该域将无法通信。


要获取真实的错误消息,请执行以下操作。


发送Access-Control-Allow-Origin标头

将 Access-Control-Allow-Origin 标头设置为 * 表示可以从任何域正确访问资源。你可以根据需要将 * 替换为您的域:例如,Access-Control-Allow-Origin:www.example.com。但是,处理多个域比较复杂,如果使用CDN可能会出现缓存问题,那么可能不值得花费精力。


以下是一些有关如何在各种环境中设置此标头的示例:


Apache


在将提供JavaScript文件的文件夹中,创建一个具有以下内容的 .htaccess 文件:


Header add Access-Control-Allow-Origin "*"

Nginx


将add_header指令添加到提供JavaScript文件的location块中:


location ~ ^/assets/ { add_header Access-Control-Allow-Origin *;}

HAProxy


将以下内容添加到提供JavaScript文件的asset后端:


rspadd Access-Control-Allow-Origin: *在脚本标签上设置crossorigin =“ anonymous”

在你的HTML源代码中,对于您设置了 Access-Control-Allow-Origin 标头的每个脚本,在script标记上设置crossorigin="anonymous"。在script标记上添加 crossorigin 属性之前,请确保已验证是否已为脚本文件发送了标头。在Firefox中,如果存在 crossorigin 属性,但没有 Access-Control-Allow-Origin 标头,则不会执行脚本。


5. TypeError: Object doesn’t support property

这是在IE中发生的错误,当您调用undefined的方法时,你可以在IE开发人员控制台中对此进行测试。


这等效于Chrome中的错误“ TypeError:‘undefined’ is not a function”。是的,对于相同的逻辑错误,不同的浏览器可能具有不同的错误消息。


这是IE在采用JavaScript命名空间的Web应用程序中常见的问题,在这种情况下,99.9%的问题是IE无法将当前名称空间中的方法绑定到 this 关键字。


例如,如果你的JS命名空间 Rollbar 使用 isAwesome 方法。通常,如果你在 Rollbar 名称空间中,则可以使用以下语法调用 isAwesome 方法:


this.isAwesome();

Chrome,Firefox和Opera将很乐意接受此语法。另一方面,IE则不会。因此,在使用JS命名空间时,最安全的方法是用实际的命名空间作为前缀。


Rollbar.isAwesome();6. TypeError: ‘undefined’ is not a function

这是在Chrome中发生的错误,当你调用undefined的函数时。你可以在Chrome开发者控制台和Mozilla Firefox开发者控制台中对此进行测试。


随着这些年来JavaScript的编码技术和设计模式越来越复杂,在回调和闭包中的自引用作用域也相应地增多,这也是相当常见的这种或那种混乱的根源。


考虑以下示例代码片段:


function clearBoard(){ alert("Cleared");}document.addEventListener("click", function(){ this.clearBoard(); // 这个 "this" 是什么?});

如果执行上述代码,然后单击该页面,则会导致以下错误“ Uncaught TypeError:this.clearBoard not a function”。原因是正在执行的匿名函数是在文档的上下文中,而 clearBoard 是在 window 中定义的。


传统的、与旧浏览器兼容的解决方案是简单地将对它的引用保存在一个变量中,然后闭包可以继承这个变量。例如:


var self = this;document.addEventListener("click", function(){ self.clearBoard();});

另外,在较新的浏览器中,可以使用 bind() 方法传递正确的引用:


document.addEventListener("click",this.clearBoard.bind(this));7. Uncaught RangeError: Maximum call stack

这是Chrome浏览器在几种情况下出现的错误,一种是调用不终止的递归函数。你可以在Chrome开发者控制台中对此进行测试。


如果将值传递给超出范围的函数,也可能会发生这种情况。许多函数的输入值仅接受特定范围的数字,例如,Number.toExponential(digits) 和 Number.toFixed(digits) 接受0到20之间的数字,而Number.toFixed(digits) 接受1到21之间的数字。


var a = newArray(4294967295); //OKvar b = newArray(-1); //range errorvar num = 2.555555;document.writeln(num.toExponential(4)); //OKdocument.writeln(num.toExponential(-2)); //range error!num = 2.9999;document.writeln(num.toFixed(2)); //OKdocument.writeln(num.toFixed(25)); //range error!num = 2.3456;document.writeln(num.toPrecision(1)); //OKdocument.writeln(num.toPrecision(22)); //range error!8. TypeError: Cannot read property ‘length’

这是Chrome浏览器中发生的错误,因为读取undefined的变量的length属性,你可以在Chrome开发者控制台中进行测试。


通常情况下,你可以在数组上找到定义的长度,但如果数组没有初始化或者变量名被隐藏在其他上下文中,你可能会遇到这个错误。通过以下示例让我们了解此错误。


var testArray= ["Test"];function testFunction(testArray) { for (var i = 0; i < testArray.length; i ) { console.log(testArray[i]); }}testFunction();

当你声明一个带参数的函数时,这些参数就变成了局部参数。这意味着即使你具有名称为 testArray 的变量,函数内具有相同名称的参数仍将被视为局部参数。


你可以通过两种方式解决问题:


删除函数声明语句中的参数(事实证明,你想访问那些在函数外部声明的变量,因此你不需要为函数使用参数)


var testArray = ["Test"];/* 前置条件:在函数外部定义testArray */function testFunction(/* No params */) { for (var i = 0; i < testArray.length; i ) { console.log(testArray[i]); }}testFunction()

调用函数,将我们声明的数组传递给它。


var testArray = ["Test"];function testFunction(testArray) { for (var i = 0; i < testArray.length; i ) { console.log(testArray[i]); }}testFunction(testArray);9. Uncaught TypeError: Cannot set property

当我们尝试访问未定义的变量时,它总是返回 undefined,我们无法获取或设置任何 undefined 属性。在这种情况下,应用程序将引发“ Uncaught TypeError:Cannot set property”。


例如,在Chrome浏览器中:


如果 test 对象不存在,则错误将引发“ Uncaught TypeError:Cannot set property”。


10. ReferenceError: event is not defined

当您尝试访问undefined 或超出当前范围的变量时,将引发此错误。你可以在Chrome浏览器中非常轻松地对其进行测试。


如果你在使用事件处理系统时收到这个错误,请确保你使用传入的事件对象作为参数。IE等较旧的浏览器会提供全局变量事件,而Chrome会自动将事件变量附加到处理程序。Firefox不会自动添加它。jQuery之类的库试图规范这种行为,尽管如此,最好还是使用传递给事件处理程序函数的方法。


document.addEventListener("mousemove", function (event) { console.log(event);})总结

事实证明,其中许多都是null或undefined的错误。如果使用严格的编译器选项,像Typescript这样的静态类型检查系统可以帮助你避免使用它们。它可以警告你,如果一个类型是预期的,但还没有被定义。


详聊前端异常原理

大家好,我是Echa哥。


导读

随着近年来前端监控体系建设日益完善,前端工程师对异常更加关注。业界关于 JS 异常介绍大多只谈了异常的捕获方法,对产生的原因和处理办法谈的较少。本文将详细的阐述异常原理,把笔者近 2 年在前端监控领域中与异常打交道的经验分享给大家。


异常定义

异常,Exception, 即预料之外的事件,在程序执行过程中发生,会打断正常的程序运行。


ECMA-262 白皮书 13 版中描述了 8 种异常


  • SyntaxError:语法异常
  • ReferenceError:引用异常
  • RangeError:范围异常
  • Error:异常基类
  • InternalError:内部异常
  • TypeError: 类型异常
  • evalError: Eval 方法异常
  • URIError: URI 相关方法产生的异常
1. SyntaxError

在引擎执行代码之前,编译器需要对 js 进行编译,编辑阶段包括:词法分析,语法分析;如图:


编译阶段发生的异常都是 SyntaxError,但 SyntaxError 不完全都发生于编译阶段;


const a = '3;

比如这行代码,缺少一个引号,就会发生: SyntaxError: Invalid or unexpected token.


其他常见的 SyntaxError:


  • SyntaxError:Unexpected token u in JSON at position 0
  • SyntaxError:Unexpected token '<'
  • SyntaxError:Unexpected identifier

绝大部分 SyntaxError 都可以通过配置编辑器的校验工具,从而在开发阶段避免。


2. ReferenceError

引用异常,比较常见,类似于 Java 语言中最著名的空指针异常 (Null Pointer Exception,NPE).


  • ReferenceError:$ is not defined
  • ReferenceError:Can't find variable: $

上面举的 2 个引用异常例子其实是同一个异常,第一个是发生在 Android,第二个是在 iOS 下,异常对象的 message 有着兼容性的差别。


什么情况下会发生引用异常呢?


这里需要先提一下 LHS 查询和 RHS 查询。


比如 const a = 2; ,对于这一行代码,引擎会为变量 a 进行 LHS 查询。另外一个查找的类型叫作 RHS,即在赋值语句的 Left Hand Side 和 Right Hand Side。RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图找到变量的容器本身,即作用域。


LHS 和 RHS 的含义是 “赋值操作的左侧或右侧” 并不一定意味着就是 “=”。比如 console.log(a) 也会进行异常 RHS。我们再来看一个例子:


function foo(a) { var b = a; return a b; } var c = foo(2);

其中有 function foo;Var c;A = 2;Var b 这 4 次 LHS 和 4 次 RHS


为什么区分 LHS 和 RHS 是一件重要的事情?


因为在变量还没有声明的情况下,这两种查询的行为是不一样的。


如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError。


如果 RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,会抛出另外一种类型的异常,叫作 TypeError。


3. TypeError

TypeError 在对值进行不合理操作时会发生,比如试图对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的值中的属性,那么引擎会抛出这种类型的异常。比如:


TypeError:Cannot read property 'length' of undefined

这是个最常见的异常之一,在判断数组长度时可能发生。


可以做前置条件判空,比如:


if (obj) { res = obj.name; }

也可以改写成逻辑与运算 && 的表达式写法


res = obj && obj.name;

但如果属性较多,这种方法就很难看了,可以使用可选链的写法,如下:


res = obj && obj.a && obj.a.b && obj.a.b.name res = obj?.a?.b?.name;

虽然条件判断、逻辑与判断、可选链判断都可以避免报错,但是还是有 2 个缺点:


  • js 对于变量进行 Bool 强制转换的写法还是不够严谨,可能出现判断失误
  • 这样写法在为空时本行代码不会报错,但是后续逻辑可能还会出问题;只是减少了异常,并没有办法解决这种情况。对于重要的逻辑代码建议使用 try/catch 来处理,必要时可以加上日志来分析。
4. RangeError

范围错误,比如:


  • new Array(-20) 会导致 RangeError: Invalid array length
  • 递归等消耗内存的程序会导致 RangeError: Maximum call stack size exceeded

递归可以使用循环 栈或尾递归的方式来优化


//普通递归 const sum = (n) => { if (n <= 1) return n; return n sum(n-1) } //尾递归 const sum = (n, prevSum = 0) => { if (n <= 1) return n prevSum; return sum(n-1, n prevSum) }

尾递归和一般的递归不同在对内存的占用,普通递归创建 stack 累积而后计算收缩,尾递归只会占用恒量的内存。当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。


5. Error 与自定义异常

Error 是所有错误的基类,其他错误类型继承该类型。所有错误类型都共享相同的属性。


  • Error.prototype.message 错误消息。对于用户创建的 Error 对象,这是构造函数的第一个参数提供的字符串。
  • Error.prototype.name 错误名称。这是由构造函数决定的。
  • Error.prototype.stack 错误堆栈

通过继承 Error 也可以创建自定义的错误类型。创建自定义错误类型时,需要提供 name 属性和 message 属性.


class MyError extends Error { constructor(message) { super(); this.name = 'MyError' this.message = message } }

大多流行框架会封装一些自定义异常,比如 Axios 和 React.


React 在 ErrorDecoder 模块中对自定义错误做了介绍。每个错误都有 ID,比如 ID:185 错误是:在 componentDidUpdate 函数中调用了 this.setState() 方法,导致 componentDidUpdate 陷入死循环。在报错后会输出带有异常介绍链接的日志.


https://reactjs.org/docs/error-decoder.html/?invariant = 异常 ID.


利用链接打开可视化链接,如下:


6. Error: script Error.

它是 Error 类型中最常见的一种;由于没有具体异常堆栈和代码行列号,成为可最神秘的异常之一。


由于浏览器基于安全考虑效避免敏感信息无意中被第三方 (不受控制的) 脚本捕获到,浏览器只允许同域下的脚本捕获具体的错误信息。


但大部分的 JS 文件都存放在 CDN 上面,跟页面的域名不一致。做异常监控只能捕获 Error: Script Error. 无法捕获堆栈和准确的信息。2 步解决:


1、给 script 标签增加 crossorigin 属性,让浏览器允许页面请求资源。


这样请求头 sec-fetch-mode 值就会变成 cors, 默认是 no-cors.


但有些浏览器还不兼容此方法,加上 crossorigin 后仍不能发出 sec-fetch-mode:cors 请求


2、给静态资源服务器增加响应头允许跨域标记。


Access-Control-Allow-Origin: *.58.com

大部分主流 CDN 默认添加了 Access-Control-Allow-Origin 属性。


整个过程可以参考以下流程图:


在加上跨域请求头、响应头后可能还有大量的 ScriptError,就要考虑以下几种情况


  • 通过 append Script 标签异步加载 JS
  • JSONP 请求
  • 第三方 SDK
7. 其他异常

InternalError


这种异常极为少见,在 JS 引擎内部发生,示例场景通常为某些成分过大,例如:


  • “too many switch cases”(过多 case 子句);
  • “too many parentheses in regular expression”(正则表达式中括号过多);
  • “array initializer too large”(数组初始化器过大);

EvalError


在 eval() 方法执行过程中抛出 EvalError 异常。


根据 Ecma2018 版以后,此异常不再会被抛出,但是 EvalError 对象仍然保持兼容性。


URIError


用来表示以一种错误的方式使用全局 URI 处理函数而产生的错误.


decodeURI, decodeURIComponent, encodeURI, encodeURIComponent 这四个方法会产生这种异常;


比如执行 decodeURI('%%') 的异常:Uncaught URIError: URI malformed


异常处理

ECMA-262 第 3 版新增了 try/catch 语句,作为在 JavaScript 中处理异常的一种方式。基本的语法如下所示,跟 Java 中的 try/catch 语句一样。


1. finally

finally 在 try-catch 语句中是可选的,finally 子句一经使用,其代码无论如何都会执行。


function a () { try { return '约会' } catch (e) { return '约会失败' } finally { return '睡觉'; } } console.log('函数结果:', a()) // '睡觉'

上述代码的结果是 ' 睡觉 ',finally 会阻止 return 语句的终止.


2. throw

throw new Error('Boom');

什么时候应该手动抛出异常呢?


一个指导原则就是可预测程序在某种情况下不能正确进行下去,需要告诉调用者异常的详细信息,而不仅仅是异常内容本身。比如上文提到的 React 自定义异常;


一个健壮的函数,会对参数进行类型有效性判断;通常在实参不合理时,为了避免报错阻断程序运行,开发者会通过默认值,return 空等方式处理。


这种方式虽然没有报错,但是程序的结果未必符合预期,默认值设计不合理会造成语义化误解;另外,也可能无法避免后续的代码报错;


3. 断言

上文提到可预测,很容易联想到 Node 中的断言 assert,如果表达式不符合预期,就抛出一个错误。


assert 方法接受两个参数,当第一个参数对应的布尔值为 true 时,不会有任何提示,返回 undefined。当第一个参数对应的布尔值为 false 时,会抛出一个错误,该错误的提示信息就是第二个参数设定的字符串。


var assert = require('assert'); function add (a, b) { return a b; } var expected = add(1,1); assert( expected === 2, '预期1加1等于2');

通常在 TDD 开发模式中,会用于编写测试用例;


不过 ECMA 还没有类似的设计,感兴趣可以简单封装一个 assert 方法。浏览器环境中的 console 对象有类似的 assert 方法。


4. 异步中的异常

非同步的代码,在事件循环中执行的,就无法通过 try catch 到。


主要注意的是,Promise 的 catch 方法用于处理 rejected 状态,而非处理异常。Rejected 状态未处理的话会触发 Uncaught Rejection. 后者可以通过如下方式进行统一的监听。


window.onunhandledrejection = (event) => { console.warn(`REJECTION: ${event.reason}`); };

tips: await 这种 Promise 的同步写法,通常会被开发者忽略 rejected 的处理,可以用 try catch 来捕获。


5. 异常监控

服务端通常会通过服务器的日志进行异常监控,比如观察单台服务器的日志输出,或 kibana 可视化查询。
前端异常监控与之最大的不同,就是需要把客户端发生的异常数据通过网络再收集起来。


可以使用下面几个方式来收集数据:


  • window.onerror 捕获语法异常
  • 可以重写 setTimeout、setInterval 等异步方法,用同步的写法包裹 try 来捕获异步函数中发生的错误
  • window.addEventListener (‘unhandledrejection’,・・・); 捕获未处理的异步 reject
  • window.addEventListener (‘error’, …) 捕获资源异常
  • 重写 fetch, XMLHttpRequest 来捕获接口状态
总结

本文详细讲解了 ECMA 中 8 种异常的产生原理,涉及了 LHS&RHS、递归优化、ScriptError、finally、Promise 等知识点,希望在处理异常的工作中能给你带来帮助。


参考
  • ecma-262: https://www.ecma-international.org/publications-and-standards/standards/ecma-262/
  • ES6th 白皮书: https://262.ecma-international.org/6.0
  • React Error Decoder: https://reactjs.org/docs/error-decoder.html/?invariant=1
  • 《Js 高级程序设计 第四版》
  • 《你不知道的 JS》

(建议收藏)关于单片机检查变量的方法,你会几种?

这些单片机调试方法你真的知道吗?


导读:授人以鱼不如授人以渔,为什么那些前辈们能快捷定位问题,这个系列的文章将揭秘 KEIL 调试那些不为人知的事。


以下内容更适用于 STM32 单片机(51 也支持局部)。掌握了它们将加速你的调试速度,不信吗?试试看咯。


程序中最重要的是什么,数据。很多时候程序运行有问题和你的数据密切相关,假如你能实时观察程序中的数据,你觉得怎么样?


///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程和毕业设计指导,想要的同学找我拿就行了免費的,私信我就可以哦~点我头像白色字体加我也能领取哦,记得口令一哥///


数据分为两种,一种是可变的,一种为不可变的。假如 RAM 数据为可变的,FLASH 数据为不可变的(实际上也能变更,不然你怎么把程序烧写到 FLASH 中呢),还有一种极其特殊的存在:寄存器数据。


1、变量查看


首先说说可变数据的查看方式,比如你声明的一些变量,可以通过 Watch 窗口查看。


通过以下方式可打开 Watch 窗口(任选一个窗口打开即可):





在这里可以查看变量(这里选择 Watch 1):





是否发现上面的显示不太对劲?,这是啥意思?这个是说明 KEIL 无法找到这个变量。就我所知,有两种情况会出现这种现象:


1)、这个变量不存在:有可能你之前声明过这个变量,后来发现没用到,删除了。


2)、使用 static 声明的变量。


比如像这样的:





如果是第二种情况,那么可以通过将程序运行到使用该变量的地方,然后停止就可以查看了。


添加变量:


那么如何添加你需要查看的变量呢?通常可以使用如下方法:


1)、光标处于变量位置,然后右击会出现一个界面,最后选择添加到你需要的窗口:





2)、直接将你的变量拖到你的 Watch 窗口(前提是你已经打开了 Watch 窗口):





3)、复制变量名,然后将变量名粘贴在窗口里面就可以了。


移除变量


能添加,也就能移除,可以通过以下方法移除你的变量(注意程序应该处于停止状态):





当然还有一种方法就是直接删除这个变量名,这也可以达到移除的效果。


如果你希望使用十进制的方式显示你的数据,那么试试去掉上面的 Hexadecimal Display 勾选吧。


2、内存查看


如果你想查看 FLASH 的数据怎么办?那么试试这个窗口:





比如说你想看看 FLASH 地址开始处是什么数据,只要把 0x0800 0000 输入进去后按回车键就可以了(注意数字中间没有空格,只是为了看起来方便才用空格分开的):





四字节显示不爽?那试试改变显示格式吧,无符号,有符号,char、int、float……任你选(如果不想用十进制表示,必须去掉 Decimal 的勾选):





如果需要修改某个地址的数据,也可以通过上面的方式在某个数据上右击后选择修改(Modify)。


事实上,除了 FLASH 数据,RAM 数据也是可以通过它观察的:




从这里能够看到,Memory 在数据显示上比 Watch 窗口更壮大,它能够对单片机上的所有数据进行查看,缺少点就是你不知道谁是谁了(没有变量名显示,只能靠地址分辨了)。


对于以上知识可能很多人都了解过,下面说一说一般人不知道的点:


对于单片机来说,片上外设决定了你单片机的功能,所以多数情况下都须要查看外设寄存器的值,那么该怎么样查看呢?


通过 Watch 窗口就能够了。怎么做?


以最为常用的串口外设为例说明:





之后你就可以看到寄存器的内容了:





有没有很方便啊。那到底添加什么标志符才能显示出来呢?实际上这个标志符就是那些外设宏定义了。怎么看?前面一哥说过搜索也算一个调试功能,那你在工程内搜索之后就会发现这个定义:





明白了吧,你输写的 USART1 其实就是一个指针,然后 KEIL 就会从这个地址里读出数据并依照你的指针构造体显示出来。知道了这个,你应该也就知道该怎么样查看 GPIO、SPI 等外设了。


其实这里还有一个额外的益处,不知道你是否发现了。我们都知道,使用宏定义虽好,但它有一个很麻烦的地方,就是不能很直观的知道这个值到底是多少,那么通过这个你也就能够知道 USART1 的值就是 0x4001 3800 了,也就是 USART1?外设基地址就是它:





事实上通过 Memory 窗口也是可以的:





只是没有 Watch 窗口那么直观而已。


那么为什么须要支持这两种方式呢?我们知道有些变量空间非常大,假如串口缓存数组,可能有好几 K,假如你通过 Watch 窗口查看的话,你会发现它会严重干扰你的程序运行,表现情况就是数据刷新缓慢,但是通过 Memory 就不一样了,相当流畅。所以假如你要看大数据的话,用 Memory 效果最好。


还有一个益处就是,它能随时更变更量的显示方式,假如说你把一个浮点数据放在了四个字节数组变量中,那么我想查看这个浮点数据是什么怎么办,我不可能通过浮点数据的存储格式手工计算一下吧?假如你能计算出来还好,说明你很厉害,但是万一不懂存储格式或者计算错了呢?使用 Memory 就不同了,你只有把这个数组的地址给它,然后设置显示方式为浮点型就能够了,相当方便。还有就是当使用宏定义时,查看这个宏定义的值非常不方便,使用 Memory 就能够轻松查看。


假如查看 USART1 的 DR 寄存器地址,在 Watch 窗口显示是这样的:





如果你要知道 DR 的地址,你就需要通过基地址 0x400 13800 和偏移地址 0x04 知道它的地址为 0x400 13804,即使用 Watch 单独查看 DR 也是一样:





但是通过 Memory 就是这样的:





这里千万要注意的是要使用取地址符 &,否则它就变成了这样:





外设地址怎么可能是 0,所以肯定错了。


事实上你用 Watch 也是可以的,但显得比较诡异,会让你觉得这是一个指针变量:





实际上它只是一个常量而已,并不是指针变量。


在这里你会发现,这些窗口支持运算符,看这个:





还有这个:





变量的查看也是如此,是不是特别方便啊。须要注意的是,Watch 窗口和 Memory 都支持在线修改数据,对于须要临时更改数据情况下非常有用。


3、临时变量查看


以上数据查看都有一个特点,那就是数据的地址都是固定的,这样通过地址就能知道你的数据是什么,但还有一种数据,只会在函数运行的时候才会创建,一旦函数运行完,变量空间也就消失了,这就是局部变量。


局部变量使用的空间是栈,在进入函数时分配,离开函数的时候就消失了,所以你没法确定一个局部变量的地址(事实上你能得到局部变量的地址,但这个地址是随时变化的,所以即便你得到了也没用,由于你只能得到这一次的内存地址,下一次又会变化的)。


那么该怎么样观察局部变量的值呢?


假如一个简略延时函数,我想知道传入函数的参数是什么,那么通过窗口 Call Stack Locals 就能够了。这个是专门查看局部变量的,当然也能够在函数中查看局部静态变量(关于这个你能够看 【C语言之static】)。


当你把断点设置在函数内部,当程序停下在函数内部时,就能通过这个窗口查看了。





当程序停止在上面的第一个断点时,就可以在窗口上看到这个:





不知道你发现没有,nms 变量显示为,用有道词典取词后你就知道这是说变量不在范围内。什么意思?这是由于你的断点在函数的初始处,程序运行到这里时这个空间的值还没有意义,所以并没有显示出来(事实上由于 nms 为函数的第一个参数,所以这个 nms 其实是寄存器的值,而不是内存变量),但是当你的程序运行到第二个断点处你就会发现窗口变成了这样:





这是因为后面的代码将函数的参数传入到变量 nms 中了,导致这个变量有初始值了,并且可以看到这值为 0x00 0A,即传入参数为 10,事实上它传入的就是这个值:





但是你也能够看到,变量 Osprey 的值是能够看到的,为什么?由于它是局部静态变量,意味着它有固定地址,在没有初始化的时候就会被默认初始化为 0。


所以使用 Call Stack Locals 窗口能够很方便的查看局部变量的数据。


下面再说一点关于这个窗口少有人知道的点:


1、能够查看函数的调用顺序:


为了说明这个,我构造几个函数出来:





Osprey_fun3 被 main 函数调用,而 Osprey_fun3 本身调用 Osprey_fun2,Osprey_fun2 调用函数 Osprey_fun1。


如何知道这个关系呢?


看这个:




最新调用的函数在最下面(所谓的压栈),从下往上看就是,Osprey_fun3 调用 Osprey_fun2,Osprey_fun2 调用函数 Osprey_fun1,而主函数 main 这个最上层调用者却并不显示在这里(假如你使用操作系统,假如 uCOS,你是没办法在任务函数中观察到这个的,由于任务函数的调用由操作系统负责)。


2、显示调用关系:





这个功能能够查看当前函数的上层调用函数位置,通过选中某一个函数后右击选择第一个就能够进入上一层调用者的函数内部了(在这里就会跳到 Osprey_fun2 的函数内部)。而第二个是进入你选中函数的内部。


这个功能有什么用?在这里你可能觉得很鸡肋,由于函数之间的调用关系很明显啊,但是在中断处理函数中却非常有用。假如说 USART1_IRQHandler 处理函数,由于这个中断可能在主程序运行的任何时候发生,所以可能在普通函数的任何位置中断它,进而进入到中断处理函数里面,而通过这个功能你就能知道是哪个函数被中断了。


实际上,你可能并不关怀被串口中断的代码位置在哪,但是对于一些错误中断就不一样了,一旦进入错误中断,你就必需找到错误代码位置才行,怎么找?假如常见的硬件错误中断?HardFault_Handler,假如进入这个中断,你该怎么定位?就是使用这个功能了(关于错误中断的处理我会单独用一小节详细介绍)。



4、寄存器变量查看


在单片机中,有一种及其特殊的变量,就是寄存器(不是那些外设寄存器),而能和 CPU 直接打交道的其实就是这些寄存器(所谓的变量操作其实都要首先通过这些寄存器才能进行的,有一个比喻是:CPU 是君王,寄存器就是君王身边的太监,而内存变量就是那些官员了,官员要和君主说话,首先要通过太监传话才行)。这些寄存器没有所谓的地址,所以你没有办法通过取址符&获取一个申明为 register 的变量(寄存器的存取速度超快,所以假如一个变量的使用得非常频繁,那么申明为 register 是一个明智之举,但这只是建议编译器去这么做罢了,编译器听不听就不知道了,所以即便你声明一个变量为 register,它还可能是内存变量),假如这个错误:





那么通过什么方法查看呢?看左边窗口:






所有的寄存器都在这显示,当寄存器的值在发生变化后(与上一次停下时的值比较),就会变更背景颜色(Watch 窗口也是如此)。


这些寄存器的值在一般情况下根本没啥用,但是对于汇编层面的调试却很有用。假如说一条代码,没有提示任何语法错误,但就是和你想要运行的结果不同,那么假如你懂点汇编,再配合这个寄存器调试,你就能很快的定位问题。


5、注意


这里要注意的一个问题是,为了显示窗口的变量能够实时更新数据,须要在View?里勾选这个:






为了更好的观察变量,这些窗口是可以单独关闭或打开的,当然也可以通过鼠标按住窗口后拖动到你想要的地方去(可以看到这里有多个选择的位置):






有的时候窗口弄得比较乱,怎么办?通过这个就可以复位窗口到默认状态:





接下来就是外设窗口展现局部,我把它也归为根底调试,由于它很常用,很有必要进修。敬请期待!



但是当我在后期查找BUG的时候,一哥发现自己主要的调试伎俩已经变成了它,其次才是Watch之类根本调试功能。


其次就是ITM,这个也是调试利器,一定程度上能够缓解 KEIL 命令行的缺少点之处。


所以既然各位在茫茫文章中看到了这篇文章,不如把我认为比较好的技能一起进修了吧。


切记一点,假如时长充裕的话,别收藏了,直接看完,收藏后很大可能你是不会再去看的。


但是看完之后,肯定会对你今后的软件开发提供非常大的帮助。


想要学习单片机的朋友 ,做毕业设计的同学,关注我们,口令一哥,与导师一起学习成长,共同进步,还有更多资料领取。


说了这么多,大家记得留意下方评论第一条(或者私信我)有干货~




STM32硬件错误HardFault_Handler的处理方法

在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler函数里的死循环while(1)中。这说明STM32出现了硬件错误。


/** * @brief This function handles Hard Fault exception. * @param None * @retval None */void HardFault_Handler(void){ /* Go to infinite loop when Hard Fault exception occurs */ while (1) { }}

STM32出现硬件错误可能有以下原因:


(1)数组越界操作;


(2)内存溢出,访问越界;


(3)堆栈溢出,程序跑飞;


(4)中断处理错误;


遇到这种情况,可以通过以下2种方式来定位到出错代码段。


方法1

1.1在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。


1.2 在Keil菜单栏点击“View”——“Registers Window”,在寄存器查看窗口查找R14(LR)的值。如果R14(LR) = 0xFFFFFFE9,继续查看MSP(主堆栈指针)的值,如果R14(LR) = 0xFFFFFFFD,继续查看PSP(进程栈指针)的值。我的程序R14(LR) = 0xFFFFFFF9,接下来以此为例。


1.3 在Keil菜单栏点击“View”——“Memory Windows”——“Memory1”,在“Address”地址栏中输入MSP的值:0x20001288,然后在对应的行里找到地址。地址一般以0x08开头的32位数。本例中,地址为0x08003CB9。


1.4 在Keil菜单栏点击“View”——“Disassembly Window”,在“Disassembly”窗口中右击,在下拉菜单中选择“Show Disassemblyat Address...”。在弹出框“Show Code atAdress”的地址框中输入地址0x08003CB9进行搜索,然后就会找到相对应的代码。这里的代码就是进入循环中断之前的情况。仔细查看附近区域的相关代码来排查错误具体原因。


方法2

2.1在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。


2.2 在Keil菜单栏点击“View”——“CALL STACK Window”弹出“Call Stack Locals”对话框。然后在对话框中右键选择“Show Caller Code”,就会跳转到出错之前的函数处,仔细查看这部分函数被调用或者数组内存使用情况。


方法3

另一种方法:


默认的HardFault_Handler处理方法不是B .这样的死循环么?楼主将它改成BX LR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿


__asm void wait(){ BX lr}void HardFault_Handler(void){ /* Go to infinite loop when Hard Fault exception occurs */ wait();}

在HardFault_Handler函数里加上一行软中断:__asm voalite ("BKPT #1");打开编译器的CALL STACK,全速跑一下,如果进入了软中断,查看CALL STACK就知道是哪一个函数进入HardFault_Handler了。


方法4

使用库:CmBacktrace-master,结合一个小工具可以定位,需要axf文件。


在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler函数里的死循环while(1)中。这说明STM32出现了硬件错误。


/** * @brief This function handles Hard Fault exception. * @param None * @retval None */void HardFault_Handler(void){ /* Go to infinite loop when Hard Fault exception occurs */ while (1) { }}

STM32出现硬件错误可能有以下原因:


(1)数组越界操作;


(2)内存溢出,访问越界;


(3)堆栈溢出,程序跑飞;


(4)中断处理错误;


遇到这种情况,可以通过以下2种方式来定位到出错代码段。


方法1

1.1在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。


1.2 在Keil菜单栏点击“View”——“Registers Window”,在寄存器查看窗口查找R14(LR)的值。如果R14(LR) = 0xFFFFFFE9,继续查看MSP(主堆栈指针)的值,如果R14(LR) = 0xFFFFFFFD,继续查看PSP(进程栈指针)的值。我的程序R14(LR) = 0xFFFFFFF9,接下来以此为例。


1.3 在Keil菜单栏点击“View”——“Memory Windows”——“Memory1”,在“Address”地址栏中输入MSP的值:0x20001288,然后在对应的行里找到地址。地址一般以0x08开头的32位数。本例中,地址为0x08003CB9。


1.4 在Keil菜单栏点击“View”——“Disassembly Window”,在“Disassembly”窗口中右击,在下拉菜单中选择“Show Disassemblyat Address...”。在弹出框“Show Code atAdress”的地址框中输入地址0x08003CB9进行搜索,然后就会找到相对应的代码。这里的代码就是进入循环中断之前的情况。仔细查看附近区域的相关代码来排查错误具体原因。


方法2

2.1在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。


2.2 在Keil菜单栏点击“View”——“CALL STACK Window”弹出“Call Stack Locals”对话框。然后在对话框中右键选择“Show Caller Code”,就会跳转到出错之前的函数处,仔细查看这部分函数被调用或者数组内存使用情况。


方法3

另一种方法:


默认的HardFault_Handler处理方法不是B .这样的死循环么?楼主将它改成BX LR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿


__asm void wait(){ BX lr}void HardFault_Handler(void){ /* Go to infinite loop when Hard Fault exception occurs */ wait();}

在HardFault_Handler函数里加上一行软中断:__asm voalite ("BKPT #1");打开编译器的CALL STACK,全速跑一下,如果进入了软中断,查看CALL STACK就知道是哪一个函数进入HardFault_Handler了。


方法4

使用库:CmBacktrace-master,结合一个小工具可以定位,需要axf文件。


:
【本文标题和链接】call stack是什么错误 http://www.hzhe123.cn//n/149110.html 页面最新缓存时间: 2024年05月20日 星期一