为了回馈我们的开发者社区,我们查看了数千个项目的数据库,发现了 JavaScript 的 10 大错误。我们将向你展示这些错误的原因,以及如何防止这些错误发生。如果你避免了这些 “陷阱” ,这将使你成为一个更好的开发人员。由于数据是国王,我们收集,分析并排名前十的 JavaScript 错误。 Rollbar 会收集每个项目的所有错误,并总结每个项目发生的次数。 我们根据 指纹 对错误进行分组,来做到这一点。基本上,如果第二个错误只是第一个错误的重复,我们会把两个错误分到同一组。 这给用户一个很好的概括,而不是像在日志文件中看到的那些压迫性的一大堆垃圾描述。我们专注于最有可能影响你和你的用户的错误。 为此,我们通过横跨不同公司的项目数来排列错误。 如果我们只查看每个错误发生的总次数,那么大流量的项目可能会淹没与大多数读者无关的错误的数据集。以下是排名前 10 的 JavaScript 错误:  为了便于阅读,没有花大段的文字来描述每个错误。让我们深入到每一个错误,来确定什么可以导致它,以及如何避免它发生。1.Uncaught TypeError: Cannot read property如果你是一个 JavaScript 开发人员,你可能已经看到这个错误的次数比你敢承认的要多。当你读取一个属性或调用一个未定义的对象的方法时,这个错误会在 Chrome 中发生。你可以在 Chrome 开发者工具的控制台中轻松测试。发生这种情况的原因很多,但常见的一种情况是在渲染UI组件时不恰当地初始化了 state(状态)。 我们来看一个在真实应用程序中如何发生的例子。 我们将选择 React,但不正确初始化的原则也适用于Angular,Vue或任何其他框架。 classQuizextendsComponent{ componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return( <ul> {this.state.items.map(item => <li key={item.id}>{item.name}</li> )} </ul> ); } }
组件的状态(例如 this.state )从 undefined 开始。 当您异步获取数据时,组件在数据加载之前至少会渲染一次,而不管它是在构造函数 componentWillMount 还是 componentDidMount 中获取的。 当 Quiz 第一次渲染时,this.state.items 是 undefined 。 这又意味着 ItemList 将 items 定义为 undefined ,并且在控制台中出现错误 – “Uncaught TypeError: Cannot read property ‘map’ of undefined”。 这个问题很容易解决。最简单的方法:在构造函数中用合理的默认值来初始化 state。classQuizextendsComponent{ // 添加这些代码: constructor(props) { super(props); // 分配 state(状态) 本身,以及 items 的默认值 this.state = { items: [] }; } componentWillMount() { axios.get('/thedata').then(res => { this.setState({items: res.data}); }); } render() { return( <ul> {this.state.items.map(item => <li key={item.id}>{item.name}</li> )} </ul> ); } }
你的应用中的确切代码可能会有所不同,但是我们希望我们已经给了你足够的线索,来解决或避免在你的应用程序中出现这个问题。如果你还没有碰到,请继续阅读,因为我们将在下面覆盖更多相关错误的示例。2.TypeError: ‘undefined’ is not an object (evaluating这是在 Safari 中读取属性或调用未定义对象上的方法时发生的错误。你可以在 Safari Developer Console 中轻松测试。这与 Chrome 的上述错误基本相同,但 Safari 使用不同的错误消息。3.TypeError: null is not an object (evaluating这是在Safari中读取属性或调用 空对象(null) 上的方法时发生的错误。您可以在 Safari Developer Console中轻松测试。有趣的是,在 JavaScript 中,null 和 undefined 是不一样的,这就是为什么我们看到两个不同的错误信息。 undefined 通常是一个尚未分配的变量,而 null 表示该值为空。 要验证它们不相等,请尝试使用严格相等运算符 === :  在现实的例子中,这种错误可能发生的一种场景是:如果在加载元素之前尝试在 JavaScript 中使用 DOM 元素。这是因为 DOM API 对于空白的对象引用返回 null 。任何执行和处理 DOM 元素的 JS 代码都应在 DOM 元素创建后执行。JS 代码按照 HTML 中的规定从上到下进行解析。所以,如果 DOM 元素之前有一个 script 标签, script 标签内的JS代码将在浏览器解析 HTML 页面时执行。如果在加载脚本之前尚未创建 DOM 元素,则会出现此错误。在这个例子中,我们可以通过添加一个事件监听器来解决这个问题,这个监听器会在页面准备好的时候通知我们。一旦 addEventListener 被触发,init() 方法就可以使用 DOM 元素。<script> function init() { var myButton = document.getElementById('myButton'); var myTextfield = document.getElementById('myTextfield'); myButton.onclick = function() { var userName = myTextfield.value; } } document.addEventListener('readystatechange', function() { if(document.readyState === 'complete') { init(); } }); </script> <form> <inputtype='text'id='myTextfield'placeholder='Type your name'/> <inputtype='button'id='myButton'value='Go'/> </form>
4.(unknown): Script error当一个未捕获的 JavaScript 错误违反了跨域策略时,就会出现这类脚本错误。例如,如果你将 JavaScript 代码托管在 CDN 上,任何未被捕获的错误(这个会冒泡到 window.onerror 处理程序,而不是在 try-catch 捕获)将被报告为简单的 “脚本错误” ,而不会包含有用的信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则将不允许进行通信。发送 Access-Control-Allow-Origin 头信息 将 Access-Control-Allow-Origin 头信息设置为 * ,表示可以从任何域正确访问资源。如有必要,您可以用你的域名替换 * ,例如 Access-Control-Allow-Origin: www.example.com 。但是,处理多个域名会有些棘手,如果你使用 CDN ,由此出现的缓存问题可能会让你感觉不值得付出努力。 点击这里 看到更多。下面是一些如何在不同环境中设置 Access-Control-Allow-Origin 头信息的例子。Apache在 JavaScript 文件所在的文件夹中,使用以下内容创建一个 .htaccess 文件:HeaderaddAccess-Control-Allow-Origin'*'
Nginx将 add_header 指令添加到提供 JavaScript 文件的位置块中:location ~ ^/assets/{ add_header Access-Control-Allow-Origin*; }
HAProxy将以下内容添加到提供资源服务的后端,并提供 JavaScript 文件:rspadd Access-Control-Allow-Origin:\ *
在 script 标签上设置 属性 在你的 HTML 源代码中,对于你设置的 Access-Control-Allow-Origin 头信息的每个脚本,在 script 标签上设置 。在添加脚本标记上的 crossorigin 属性之前,请确保验证上述头信息是否正确发送。在 Firefox 中,如果存在 crossorigin 属性,但 Access-Control-Allow-Origin 头信息不存在,则脚本将不会执行。5.TypeError: Object doesn’t support property这是你在调用未定义方法时发生在IE中的错误。你可以在IE开发者工具的控制台进行测试。 这相当于 Chrome 中的错误:”TypeError: ‘undefined’ is not a function” 。是的,对于相同的逻辑错误,不同的浏览器可能会有不同的错误消息。在使用 JavaScript 命名空间的Web应用程序中,这中错误对于 IE 来说是一个常见问题。在这种情况下,这种问题 99.9% 是 IE 无法将当前名称空间内的方法绑定到 this 关键字。例如,如果你的 JS 命名空间 Rollbar 中有 isAwesome 方法。通常,如果你在 Rollbar 命名空间内,则可以使用以下语法调用 isAwesome 方法:Chrome,Firefox 和 Opera 会欣然地接受这个语法。 IE 则不会。 因此,使用 JS 命名空间时最安全的选择是始终以实际命名空间作为前缀。Rollbar.isAwesome();
6.TypeError: ‘undefined’ is not a function当你调用未定义的函数时,在 Chrome 中会发生这种错误。 你可以在 Chrome 开发者工具的控制台和 Mozilla Firefox 开发者工具的控制台中对此进行测试。 随着 JavaScript 编码技术和设计模式在这些年来越来越复杂,回调和闭包内的自引用作用域也相应增加,这是使用 this/that 混乱的一个相当常见的原因。function clearBoard(){ alert('Cleared'); } document.addEventListener('click', function(){ this.clearBoard(); // what is “this” ? });
如果你执行上面的代码然后点击页面,会导致以下错误: “Uncaught TypeError: this.clearBoard is not a function”。原因是正在执行的匿名函数在 document 上下文中, 而 clearBoard 定义在 window 中。一个传统的,旧浏览器兼容的解决方案是简单地将你的 this 保存在一个变量,然后该变量可以被闭包继承。 例如:varself=this; // save reference to 'this', while it's still 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.toPrecision(digits) 接受从1到21的数字。此外,如果您将值传递给超出范围的函数,也可能会发生这种情况。 许多函数只接受其输入值的特定范围的数字。 例如:Number.toExponential(digits) 和 Number.toFixed(digits) 接受 0 到 20 的数字,Number.toPrecision(digits) 接受 1 到 21 的数字。var a = newArray(4294967295); //OK var b = newArray(-1); //range error var num = 2.555555; document.writeln(num.toExponential(4)); //OK document.writeln(num.toExponential(-2)); //range error! num = 2.9999; document.writeln(num.toFixed(2)); //OK document.writeln(num.toFixed(25)); //range error! num = 2.3456; document.writeln(num.toPrecision(1)); //OK document.writeln(num.toPrecision(22)); //range error!
8.TypeError: Cannot read property ‘length’这是 Chrome 中发生的错误,因为读取未定义变量的长度属性。你可以在Chrome开发者工具的控制台中进行测试。 你通常会在数组中找到定义的长度,但是如果数组未初始化或变量名隐藏在另一个上下文中,则可能会遇到此错误。让我们用下面的例子来理解这个错误。var testArray= ['Test']; function testFunction(testArray) { for(var i = 0; i < testArray.length; i++) { console.log(testArray[i]); } } testFunction();
当你用参数声明一个函数时,这些参数变成了本地参数。这意味着即使你有名称为 testArray 的变量,函数中具有相同名称的参数仍将被视为 本地参数。删除函数声明语句中的参数(事实证明如果你想访问那些在函数之外声明的变量,你不需要将其作为你函数的参数传入):
var testArray = ['Test']; /* Precondition: defined testArray outside of a function */ 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 of undefined.” 错误。 如果 test 对象不存在,错误将会抛出 “Uncaught TypeError cannot set property of undefined.” 。10.ReferenceError: event is not defined当你尝试访问未定义的变量或超出当前作用域的变量时,会引发此错误。你可以在Chrome浏览器中轻松测试。 如果你在使用事件处理时遇到这种错误,请确保你使用传入的事件对象作为参数。像IE这样的老浏览器提供了一个全局变量事件, Chrome 会自动将事件变量附加到处理程序。Firefox 不会自动添加它。像jQuery这样的库试图规范化这种行为。不过,最佳实践是使用传递到事件处理程序函数的方法。document.addEventListener('mousemove', function(event) { console.log(event); })
总结事实证明很多都是一些 null 或 undefined 错误。如果您使用严格的编译器选项,比如 Typescript 这样的好的静态类型检查系统可以帮助您避免它们。它可以警告你,如果一个类型是预期的,但尚未定义。我们希望你学到了一些新的东西,并且可以避免将来出现这些错误,或者本指南帮助你解决了头痛的问题。尽管如此,即使有最佳做法,生产环境中还是会出现意想不到的错误。了解影响用户的错误非常重要,并有很好的工具来快速解决它们。Rollbar 为你提供生产环境 JavaScript 错误的可视性,并为您提供更多上下文来快速解决它们。例如,它提供了额外的调试功能,例如 遥测功能,可以告诉你用户的浏览器发生了什么导致错误。这可以使在本地开发者工具的控制台之外发现问题。你可以在 Rollbar 的 JavaScript 应用程序的完整功能列表 中了解更多信息。
|