来自1000多个项目的10大JavaScript错误浅析

时间:2022-05-05
本文章向大家介绍来自1000多个项目的10大JavaScript错误浅析,主要内容包括1.Uncaught TypeError: Cannot read property、2. TypeError: ’undefined’ is not an object、3. TypeError: null is not an object、5. TypeError: Object doesn’t support property、6. TypeError: ‘undefined’ is not a function、7. Uncaught RangeError: Maximum call stack、8. TypeError: Cannot read property ‘length’、9. Uncaught TypeError: Cannot set property、10. ReferenceError: event is not defined、结论、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

出于可读性方面的考虑,每个错误的描述经过精简。

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 (
   <ul>
    {this.state.items.map(item =>
     <li key={item.id}>{item.name}</li>
    )}
   </ul>
  );
 }}

这里要注意两件事:

  1. 组件的状态(如this.state)在一开始就是undefined。
  2. 如果是通过异步的方式来加载数据,那么在数据加载进来之前,至少要渲染一次组件——不管是在构造器、componentWillMout()还是componentDidMout()中加载数据。Quiz在进行第一次渲染时,this.state.items是undefined,那么ItemList就会得到undefined的数据项,这样就会在控制台看到这个错误——“Uncaught TypeError:Cannot read property ‘map’ of undefined”。

要解决这个问题其实很简单,在构造器里使用适当的默认值进行初始化。

class Quiz extends Component {
 // 增加这个: constructor(props) {
  super(props);

  // 使用空数组给state赋值
  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

在Safari里读取未定义对象的属性或调用未定义对象的方法时就会发生这个错误,在Safari开发者控制台可以很容易地重现这个错误。这个错误与发生在Chrome里的是差不多的,只是Safari为它提供了不同的错误信息。

3. TypeError: null is not an object

在Safari里读取空(null)对象的属性或调用空对象的方法时就会发生这个错误,在Safari开发者控制台可以很容易地重现这个错误。

有意思的是,在JavaScript里,null和undefined其实是不一样的,所以我们会看到两个不同的错误消息。undefined表示未赋值的变量,而null表示变量值为空。可以使用严格等于号来证明它们不是同一个东西。

<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>
location ~ ^/assets/ {
  add_header Access-Control-Allow-Origin *;
}

HAProxy

在JavaScript文件对应的backend配置块中加入如下内容:

rspadd Access-Control-Allow-Origin: *

2). 在script标签里设置crossorigin=“anonymous”

在每个设置了Access-Control-Allow-Origin字段的HTML页面里,将它们的script标签的crossorigin属性设置为“anonymous”。在Firefox里,如果出现了crossorigin,但没有设置Access-Control-Allow-Origin,JavaScript脚本就不会被执行。

5. TypeError: Object doesn’t support property

在IE里读取未定义对象的属性或调用未定义对象的方法时就会发生这个错误,在IE开发者控制台可以很容易地重现这个错误。

这个错误与Chrome里的“TypeError: ‘undefined’ is not a function”是同一个东西。不同的浏览器为相同的错误提供的错误消息可能是不一样的。

在IE里使用JavaScript的命名空间时,就很容易碰到这个错误。发生这个错误十有八九是因为IE无法将当前命名空间里的方法绑定到this关键字上。例如,假设有个命名空间Rollbar,它有一个方法叫isAwesome()。在Rollbar命名空间中,可以直接使用this关键字来调用这个方法:

this.isAwesome();

在Chrome、Firefox和Opera中这样做都是没有问题的,但在IE中就不行。所以,最安全的做法是指定全命名空间:

Rollbar.isAwesome();

6. TypeError: ‘undefined’ is not a function

在Chrome里调用一个未定义的函数时就会发生这个错误,可以在Chrome开发者控制台和Mozilla开发者控制台重现这个错误。

近年来,JavaScript的编码技术和设计模式变得日趋复杂,回调和闭包中的自引用情况越来越普遍,让人搞不清楚代码中的this/that表示的是什么意思。

比如下面这段代码:

function testFunction() {
 this.clearLocalStorage();
 this.timer = setTimeout(function() {
  this.clearBoard();  // 这里的”this"是指什么?
 }, 0);};

执行上面的代码会出现这样的错误:“Uncaught TypeError: undefined is not a function”。因为在调用setTimeout()方法时,实际上是在调用window.setTimeout()。传给setTimeout()的匿名函数的上下文实际上是window,而window并不包含clearBoard()方法。

对于旧浏览器,以往的解决办法是将this赋值给某个变量,然后在闭包里使用这个变量。例如:

function testFunction () {
 this.clearLocalStorage();
 var self = this;  // 将this赋值给self
 this.timer = setTimeout(function(){
  self.clearBoard();   }, 0);};

在新浏览器中,可以使用bind()方法来传递引用:

function testFunction () {
 this.clearLocalStorage();
 this.timer = setTimeout(this.reset.bind(this), 0); // 绑定到 'this'};function testFunction(){
  this.clearBoard();  // 以’this’作为上下文};

7. Uncaught RangeError: Maximum call stack

在Chrome里,有几种情况会发生这个错误,其中一个就是无限递归调用一个函数。这个错误可以在Chrome开发者控制台重现。

当传给函数的值超出可接受的范围时也会出现这个错误。很多函数只接受指定范围的数值,例如,Number.toExponential(digits)和Number.toFixed(digits)只接受0到20的数值,而Number.toPrecision(digits)只接受1到21的数值。

var a = new Array(4294967295); //OKvar b = new Array(-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开发者控制台重现。

length是数组的属性,但如果数组没有初始化或者数组的变量名被另一个上下文隐藏起来的话,访问length属性就会发生这个错误。例如:

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

函数的参数名会覆盖全局的变量名。也就是说,全局的testArray被函数的参数名覆盖了,所以在函数体里访问到的是本地的testArray,但本地并没有定义testArray,所以出现了这个错误。

有两种方法可用于解决这个问题:

1). 将函数的参数名移除(这就表示函数里要访问的变量已经在函数外面定义好了,所以函数不需要参数):

var testArray = ["Test"];/* 前提是要在函数外面定义好testArray */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 of undefined”异常。

例如,在Chrome中:

如果test对象不存在,就会抛出“Uncaught TypeError: cannot set property of undefined”异常。

10. ReferenceError: event is not defined

在访问一个未定义的对象或超出当前作用域的对象时就会发生这个错误,这个错误可以在Chrome开发者控制台重现。

如果在进行事件处理时遇到这个错误,请确保事件对象被作为参数传入到函数当中。旧浏览器(IE)提供了全局的event变量,但并不是所有的浏览器都会这样。尽管jQuery尝试对这种行为进行规范化,但最好还是使用传给函数的event对象:

function myFunction(event) {
  event = event.which || event.keyCode;
  if(event.keyCode===13){    alert(event.keyCode);
  }}

结论

我们希望这些内容能够帮助大家在未来避免这些错误,解决大家的痛点。不过,即使有了这些最佳实践,在生产环境中仍然会出现各种不可预期的错误。关键是要及时发现那些影响用户体验的错误,并使用适当的工具快速解决这些问题。