1000 多个项目中的十大 JavaScript 错误(以及如何避免它们)
8. TypeError:无法读取属性“length”
注:本文最初发表于Rollbar 的博客上。
为了回馈我们的开发者社区,我们分析了数千个项目的数据库,并找出了 JavaScript 中十大常见错误。我们将向您展示导致这些错误的原因以及如何避免它们。如果您能避免这些“陷阱”,就能成为更优秀的开发者。
数据为王,我们收集、分析并排序了十大JavaScript 错误。Rollbar 收集每个项目的所有错误,并汇总每个错误的发生次数。我们根据错误指纹对其进行分组。基本上,如果第二个错误只是第一个错误的重复,我们会将其归为一类。这为用户提供了清晰的概览,而不是像日志文件中那样,看到一堆令人眼花缭乱的转储。
我们专注于最有可能影响您和您的用户的错误。为此,我们根据不同公司中出现此类错误的项目数量对错误进行了排序。如果我们只关注每种错误发生的总次数,那么高访问量的客户可能会让数据集充斥着与大多数读者无关的错误。
以下是 10 个最严重的 JavaScript 错误:
为了方便阅读,每个错误都已精简。让我们深入研究每个错误,找出导致错误的原因以及如何避免。
1. 未捕获的 TypeError:无法读取属性
如果您是 JavaScript 开发者,您可能见过这个错误很多次,甚至有些不愿承认。在 Chrome 中,当您读取未定义对象的属性或调用其方法时,就会出现此错误。您可以在 Chrome 开发者控制台中轻松测试此错误。
造成这种情况的原因有很多,但最常见的原因是渲染 UI 组件时状态初始化不当。让我们看一个实际应用中可能发生这种情况的示例。我们以 React 为例,但同样的初始化不当原则也适用于 Angular、Vue 或任何其他框架。
class Quiz extends Component {
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
获取到的条目为 undefined,并且会在控制台中收到错误:“Uncaught TypeError: Cannot read property 'map' of undefined”。
这很容易修复。最简单的方法:在构造函数中使用合理的默认值初始化状态。
class Quiz extends Component {
// Added this:
constructor(props) {
super(props);
// Assign state itself, and a default value for 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”不是对象(评估
在 Safari 中,当您读取未定义对象的属性或调用其方法时,就会出现此错误。您可以在 Safari 开发者控制台中轻松测试此错误。此错误与上述 Chrome 浏览器的错误基本相同,但 Safari 使用的错误消息有所不同。
3. TypeError:null 不是对象(评估
在 Safari 中,当您读取空对象的属性或调用空对象的方法时,就会发生此错误。您可以在 Safari 开发者控制台中轻松测试此错误。
有趣的是,在 JavaScript 中,null 和 undefined 并不相同,这就是为什么我们会看到两条不同的错误消息。Undefined 通常表示尚未赋值的变量,而 null 表示值为空。要验证它们是否不相等,请尝试使用严格相等运算符:
在实际示例中,如果您在 JavaScript 中尝试在元素加载之前使用该 DOM 元素,则可能会发生此错误。这是因为 DOM API 会为空的对象引用返回 null。
任何执行并处理 DOM 元素的 JS 代码都应在 DOM 元素创建后执行。JS 代码按照 HTML 布局从上到下进行解释。因此,如果 DOM 元素前有一个标签,则 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>
<input type="text" id="myTextfield" placeholder="Type your name" />
<input type="button" id="myButton" value="Go" />
</form>
4.(未知):脚本错误
当未捕获的 JavaScript 错误跨越域边界,违反了跨域策略时,就会发生脚本错误。例如,如果您将 JavaScript 代码托管在 CDN 上,任何未捕获的错误(冒泡到 window.onerror 处理程序,而不是被 try-catch 捕获的错误)都将被简单地报告为“脚本错误”,而不会包含有用的信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则这些域将不允许通信。
要获取真正的错误消息,请执行以下操作:
1. 发送 Access-Control-Allow-Origin 标头
将标头设置Access-Control-Allow-Origin
为 * 表示可以从任何域名正常访问该资源。您可以根据需要将 * 替换为您的域名:例如 。Access-Control-Allow-Origin: www.example.com
但是,处理多个域名会比较棘手,如果您使用 CDN,由于可能出现的缓存问题,处理多个域名可能不值得。更多信息请参见此处。
以下是如何在各种环境中设置此标头的一些示例:
阿帕奇
在将提供 JavaScript 文件的文件夹中,创建一个.htaccess
包含以下内容的文件:
Header add Access-Control-Allow-Origin "*"
Nginx
将 add_header 指令添加到提供 JavaScript 文件的位置块:
location ~ ^/assets/ {
add_header Access-Control-Allow-Origin *;
}
HAProxy
将以下内容添加到提供 JavaScript 文件的资产后端:
rspadd Access-Control-Allow-Origin:\ *
2. 在脚本标签上设置 crossorigin="anonymous"
在 HTML 源代码中,对于已设置标头的每个脚本Access-Control-Allow-Origin
,请crossorigin="anonymous"
在 SCRIPT 标签上进行设置。在将crossorigin
属性添加到 script 标签之前,请确保已验证脚本文件的标头已发送。在 Firefox 中,如果crossorigin
存在该属性但Access-Control-Allow-Origin
标头不存在,则脚本将不会被执行。
5. TypeError:对象不支持属性
这是在 IE 中调用未定义方法时发生的错误。您可以在 IE 开发者控制台中测试此错误。
这相当于 Chrome 中的“TypeError: 'undefined' is not a function”错误。是的,不同的浏览器对于相同的逻辑错误可能会有不同的错误消息。
这是在使用 JavaScript 命名空间的 Web 应用程序中,IE 浏览器的一个常见问题。在这种情况下,99.9% 的情况是 IE 无法将当前命名空间中的方法绑定到关键字。例如,假设你有一个包含方法的this
JS 命名空间,通常情况下,如果你在命名空间内,可以使用以下语法调用该方法:Rollbar
isAwesome.
Rollbar
isAwesome
this.isAwesome();
Chrome、Firefox 和 Opera 都能顺利接受这种语法。而 IE 则不会。因此,使用 JS 命名空间时最安全的做法是始终在前缀中添加实际的命名空间。
Rollbar.isAwesome();
6. TypeError:“undefined”不是一个函数
这是在 Chrome 中调用未定义函数时发生的错误。您可以在 Chrome 开发者控制台和 Mozilla Firefox 开发者控制台中测试此错误。
随着 JavaScript 编码技术和设计模式多年来变得越来越复杂,回调和闭包中的自引用范围的扩散也相应增加,这是造成这种/那种混淆的一个相当常见的原因。
考虑这个示例代码片段:
function testFunction() {
this.clearLocalStorage();
this.timer = setTimeout(function() {
this.clearBoard(); // what is "this"?
}, 0);
};
执行上述代码会导致以下错误:“Uncaught TypeError: undefined is not a function”。出现上述错误的原因是,当你调用 时setTimeout()
,实际上是在调用window.setTimeout()
。因此,传递给 的匿名函数setTimeout()
是在 window 对象的上下文中定义的,而该对象没有clearBoard()
方法。
一个传统的、兼容旧浏览器的解决方案是简单地将引用保存到this
一个变量中,然后该变量可以被闭包继承。例如:
function testFunction () {
this.clearLocalStorage();
var self = this; // save reference to 'this', while it's still this!
this.timer = setTimeout(function(){
self.clearBoard();
}, 0);
};
或者,在较新的浏览器中,您可以使用该bind()
方法传递正确的引用:
function testFunction () {
this.clearLocalStorage();
this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this'
};
function testFunction(){
this.clearBoard(); //back in the context of the right 'this'!
};
7. 未捕获的RangeError:最大调用堆栈
Chrome 中,在几种情况下都会出现此错误。一种情况是调用未终止的递归函数。您可以在 Chrome 开发者控制台中测试此错误。
如果您将值传递给超出范围的函数,也可能会发生这种情况。许多函数仅接受特定范围的数字作为输入值。例如,Number.toExponential(digits)
Number.toFixed(digits)
接受 0 到 20 之间的数字,也Number.toPrecision(digits)
接受 1 到 21 之间的数字。
var a = new Array(4294967295); //OK
var b = new Array(-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:无法读取属性“length”
这是 Chrome 中由于读取未定义变量的 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"];
/* 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. 未捕获的 TypeError:无法设置属性
当我们尝试访问未定义的变量时,它总是会返回错误,undefined
并且我们无法获取或设置其任何属性undefined
。在这种情况下,应用程序将抛出“Uncaught TypeError:无法设置 undefined 的属性”。
例如在Chrome浏览器中:
如果test
对象不存在,则会抛出错误“Uncaught TypeError 无法设置未定义的属性。”
10. ReferenceError:事件未定义
当你尝试访问未定义或超出当前范围的变量时,会抛出此错误。你可以在 Chrome 浏览器中轻松测试。
如果您在使用事件处理系统时遇到此错误,请确保使用传入的事件对象作为参数。像 IE 这样的老版本浏览器提供了全局变量事件,但并非所有浏览器都支持。像 jQuery 这样的库试图规范化这种行为。不过,最佳做法是使用传入事件处理函数的事件对象。
function myFunction(event) {
event = event.which || event.keyCode;
if(event.keyCode===13){
alert(event.keyCode);
}
}
结论
我们希望您学到一些新知识,避免将来再次犯错,或者希望本指南能帮助您解决一些棘手的问题。然而,即使采用最佳实践,生产环境中也难免会出现意外错误。了解影响用户的错误,并拥有快速解决问题的良好工具至关重要。
Rollbar 为 JavaScript 应用提供了独特的功能,例如遥测功能,它可以告诉您用户浏览器在出现错误之前发生了什么。这是您在本地开发者控制台之外无法获得的洞察。了解更多信息,请参阅 Rollbar 的JavaScript 应用完整功能列表。
鏂囩珷鏉ユ簮锛�https://dev.to/mostlyjason/top-10-javascript-errors-from-1000-projects-and-how-to-avoid-them-3bkh