10种最常见的Javascript错误(频率最⾼)
为了回馈我们的开发者社区,我们查看了数千个项⽬的数据库,发现了 JavaScript 中频度最⾼的 10 种错误。我们会告诉你什么原因导致了这些错误,以及如何防⽌这些错误发⽣。如果你能够避免落⼊这些 “陷阱”,你将会成为⼀个更好的开发者。
我们专注于最有可能影响您和您的⽤户的错误。为此,我们通过研究各种不同公司的项⽬集来对于错误进⾏排列。如果我们只查看每个错误发⽣的总次数,那么客户量⼤的的项⽬产⽣的错误可能会压倒其他错误,导致实际收集到的是与⼤多数读者⽆关的错误数据集。
以下是 JavaScript 错误 Top 10:
为了便于阅读,我们将每个错误描述都缩短了。接下来,让我们深⼊到每⼀个错误,来确定什么会导致它,以及如何避免创建它。
1. Uncaught TypeError: Cannot read property
如果你是⼀个 JavaScript 开发⼈员,可能你看到这个错误的次数⽐你敢承认的要多(LOL…)。当你读取⼀个未定义的对象的属性或调⽤其⽅法时,这个错误会在 Chrome 中出现。您可以很容易的在 Chrome 开发者控制台中进⾏测试(尝试)。
发⽣这种情况的原因很多,但常见的⼀种是在渲染 UI 组件时对于状态的初始化操作不当。
我们来看⼀个在真实应⽤程序中发⽣的例⼦:我们选择 React,但该情况也同样适⽤于 Angular、Vue 或任何其他框架。
class Quiz extends Component {
componentWillMount() {
<('/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 是未定义的。这⼜意味着 ItemList 将 items 定义为 undefined,并且在控制台中出现错误 - “Uncaught TypeError: Cannot read property
‘map' of undefined”。
这很容易解决。最简单的⽅法:在构造函数中⽤合理的默认值来初始化 state。
class Quiz extends Component {
// Added this:
constructor(props) {
super(props);
// Assign state itself, and a default value for items
this.state = {
items: []
};
}
componentWillMount() {
<('/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
这是在 Safari 中读取属性或调⽤未定义对象上的⽅法时发⽣的错误。您可以在 Safari Developer Console 中轻松测试。这与 1 中提到的 Chrome 的错误基本相同,但 Safari 使⽤了不同的错误消息提⽰语。
3. TypeError: null is not an object
这是在 Safari 中读取属性或调⽤空对象上的⽅法时发⽣的错误。您可以在 Safari Developer Console 中轻松测试。
有趣的是,在 JavaScript 中,null 和 undefined 是不⼀样的,这就是为什么我们看到两个不同的错误信息。undefined 通常是⼀个尚未分配的变量,⽽ null 表⽰该值为空。要验证它们不相等,请尝试使⽤严格的相等运算符 ===:
在现实世界的例⼦中,这种错误可能发⽣的⼀种场景是:如果在加载元素之前尝试在 JavaScript 中使⽤元素。因为 DOM API 对于空⽩的对象引⽤返回值为null。
任何执⾏和处理 DOM 元素的 JS 代码都应该在创建 DOM 元素之后执⾏。 JS 代码按照 HTML 中的规定从上到下进⾏解释。所以,如果 DOM 元素之前有⼀个标签,脚本标签内的 JS 代码将在浏览器解析 HTML 页⾯时执⾏。如果在加载脚本之前尚未创建 DOM 元素,则会出现此错误。
在这个例⼦中,我们可以通过添加⼀个事件来解决这个问题,这个会在页⾯准备好的时候通知我们。⼀旦 addEventListener被触发,init() ⽅法就可以使⽤ DOM 元素。
<script>
function init() {
var myButton = ElementById("myButton");
var myTextfield = ElementById("myTextfield");
var userName = myTextfield.value;
}
}
document.addEventListener('readystatechange', function() {
if (adyState === "complete") {
init();
}
});
</script>
当前页面脚本发生错误<form>
<input type="text" id="myTextfield" placeholder="Type your name" />
<input type="button" id="myButton" value="Go" />
</form>
4. (unknown): Script error
当未捕获的 JavaScript 错误(通过r处理程序引发的错误,⽽不是捕获在try-catch中)被浏览器的跨域策略限制时,会产⽣这类的脚本错误。例如,如果您将您的 JavaScript 代码托管在 CDN 上,则任何未被捕获的错误将被报告为“脚本错误” ⽽不是包含有⽤的堆栈信息。这是⼀种浏览器安全措施,旨在防⽌跨域传递数据,否则将不允许进⾏通信。
要获得真正的错误消息,请执⾏以下操作:
1. 发送 ‘Access-Control-Allow-Origin' 头部
将 Access-Control-Allow-Origin 标头设置为 * 表⽰可以从任何域正确访问资源。如有必要,您可以将域替换为您的域:例如,Access-Control-Allow-Origin:ample。但是,处理多个域会变得棘⼿,如果你使⽤ CDN,可能由此产⽣更多的缓存问题会让你感觉到这种努⼒并不值得。在这⾥看到更多。
这⾥有⼀些关于如何在各种环境中设置这个头⽂件的例⼦:
Apache
在 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. 在 <script> 中设置 crossorigin="anonymous"
在您的 HTML 代码中,对于您设置了Access-Control-Allow-Origin header 的每个脚本,在 script 标签上设置crossorigin =“anonymous”。在脚本标记中添加crossorigin 属性之前,请确保验证上述 header 正确发送。在 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 l浏览器的常见的问题。在这种情况下,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 中产⽣的错误。您可以在 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()的匿名函数,该函数没有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 = set.bind(this), 0); // bind to 'this'
};
function testFunction(){
this.clearBoard(); //back in the context of the right 'this'!
};
7. Uncaught RangeError: Maximum call stack
这是 Chrome 在⼀些情况下会发⽣的错误。⼀个是当你调⽤⼀个不终⽌的递归函数。您可以在 Chrome 开发者控制台中进⾏测试。
此外,如果您将值传递给超出范围的函数,也可能会发⽣这种情况。许多函数只接受其输⼊值的特定范围的数字。例如:Exponential(digits) 和Fixed(digits) 接受 0 到 20 的数字,Precision(digits) 接受 1 到 21 的数字。
var a = new Array(4294967295); //OK
var b = new Array(-1); //range error
var num = 2.555555;
document.Exponential(4)); //OK
document.Exponential(-2)); //range error!
num = 2.9999;
document.Fixed(2)); //OK
document.Fixed(25)); //range error!
num = 2.3456;
document.Precision(1)); //OK
document.Precision(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 的变量,在⼀个函数中具有相同名字的参数也会被视为本地参数。
您有两种⽅法可以解决您的问题:
1. 删除函数声明语句中的参数(事实上你想访问那些声明在函数之外的变量,所以你不需要函数的参数):
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();
2. ⽤声明的数组调⽤该函数:
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,我们不能获取或设置任何未定义的属性。在这种情况下,应⽤程序将抛出 “Uncaught TypeError: Cannot set property”。
例如,在 Chrome 浏览器中:
如果测试对象不存在,错误将会抛出 “Uncaught TypeErrorUncaught TypeError: Cannot set property”。
10. ReferenceError: event is not defined
当您尝试访问未定义的变量或超出当前范围的变量时,会引发此错误。您可以在 Chrome 浏览器中轻松测试。
发布评论