猫头鹰的深夜翻译:从1000+JS项目中汇总的10个最容易出现的错误(以及如何解决)

JavaScript常出现的错误前十位

clipboard.png

为了可读性,错误名称进行了必定的简写。让咱们深刻了解每一个错误发生的缘由以及解决方法。ios

1. Uncaught TypeError: Cannot Read Property

若是你是一名JavaScript开发人员,你可能已经记不清楚多少次看到这个错误了。当你读取一个undefined对象的属性或是调用其上的方法时,就会出现这个错误。你能够再Chrome Console中进行测试。
clipboard.png
致使这个问题的缘由有许多,最多见的是渲染UI组件时对state不恰当的初始化。让咱们看一个真实APP中可能出现该状况的例子。咱们选择了React,可是这样的不良初始化也适用于Angular,Vue或是其它的框架。git

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. 组件的state(好比 this.state)在生命周期开始时为undefined。
  2. 当你异步获取数据的时候,component会在数据加载以前至少渲染一次 - 不管是否在constructor中获取数据,都会运行componentWillMount或是componentDidMount。当Quiz第一次渲染的时候,this.state.items为undefined。所以,item列表得到的值为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() {
    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>
    );
  }
}

这和你的项目中的代码不必定彻底相同,可是咱们但愿给你提供一个解决或是避免该问题的思路。axios

2. TypeError: ‘undefined’ Is Not an Object (evaluating...)

这是一个在Safari中在undefined对象上访问属性或方法时报的错。你能够在Safari的控制台上进行测试。这个错误和以前在Chrome中出现的错误是相同,只是报错信息不一样。
clipboard.png设计模式

3. TypeError: Null Is Not an Object (evaluating...)

这是在Safari中在访问null对象上的属性或方法时报的错。
clipboard.png跨域

有趣的是,在JavaScript中,null和undefined是不一样的,因此咱们看到了两个不一样的报错信息。Undefined一般是指一个还没有赋值的变量,而null是指该变量的值为空。要想判断两者不等,应当使用严格的相等操做符:
clipboard.png浏览器

在真实世界中,这种错误可能出现的缘由之一是你试图在元素加载完成以前访问DOM元素。对于空白的对象引用,DOM API会返回null。缓存

任何对DOM元素进行处理的JS代码都应该都在DOM元素建立完成以后进行。JS代码按照HTML中的规定按从上到下的顺序进行解释。因此,若是在DOM元素以前存在标签,则脚本标签内的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. (unknown): Script Error

当未捕获的JavaScript错误跨越违法跨域策略的域边界时,会发生脚本错误。好比,若是你将你的JavaScript代码托管到CDN上,任何未被捕捉的错误(没有被try-catch块捕获,被冒泡至window.onerror处理器的错误)将会被简单的报告为Script Error,不包含任何有用的信息。这是浏览器的一种安全措施,旨在防止跨域传递数据。

要想得到真正的报错信息,作如下几步:

1. 发送Access-Control-Allow-Origin头

Access-Control-Allow-Origin设置为.来标记该资源从任何域均可以正常访问。若是须要的话,也能够将其设置为本身的域名:好比,Access-Control-Allow-Origin: www.example.com。可是,处理多个域会变的棘手,并且若是你是出于缓存的问题而使用CDN,那么这样子的代价可能不值得。详情参考这里

这里给出一些在不一样的环境中设置header的例子:
Apache
在你存放JavaScript的文件夹中添加一个.htacess文件,包含如下内容:

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

Nginx
将add_header指令添加到为JavaScript文件提供服务的位置块:

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

HAProxy
将如下内容添加到提供JavaScript的asset backend

rspadd Access-Control-Allow-Origin:\ *

2. 在script标签上设置crossorigin="annonymous"属性

在HTML中,对于每个设置了Access-Control-Allow-Origin头的脚本,在脚本的标签上添加crossorigin="anonymous"属性。在将crossorigin属性添加到脚本以前,请确保验证是否为脚本文件设置了header。在火狐浏览器中,若是设置了crossorigin属性可是没有设置Access-Control-Allow-Origin头,该脚本不会执行。

5. TypeError: Object Doesn’t Support Property

这是在IE浏览器中报的错,当你试图调用一个undefined对象的方法时:
clipboard.png

这等价于Chrome中的TypeError: ‘undefined’ is not a function错误。是的,不一样的浏览器对相同的错误会产生不一样的报错信息。

对于使用JavaScript命名空间的Web程序,在IE上运行时常常会遇到这个错误。当这个错误出现时,99.9%的状况是由于IE不能将当前的命名空间的方法绑定到this关键字上。好比,假设你有一个JS命名空间Rollbar,其下有一个方法isAwesome()。一般在Rollbar命名空间下你会用以下的语法调用isAwesome方法:

this.isAwesome();

Chrome,Firfox和Opera都会愉快的接受这个语法。可是,IE并不会。所以,使用JS命名空间时最安全的选择是始终以实际的命名空间做为前缀。

Rollbar.isAwesome();

6. TypeError: ‘undefined’ Is Not a Function

这是当你在Chrome中试图调用undefined的方法时出现的错误。
clipboard.png

随着JavaScript的编程技巧和设计模式在这几年来愈来愈复杂,在回调和闭包中自我引用范围的扩散也相应的增长,致使对this出现困惑。
看下面这段代码:

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对象,而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 reset(){
    this.clearBoard();    //back in the context of the right 'this'!
};

7. Uncaught RangeError: Maximum Call Stack

这是在Chrome中出现的一种错误。状况之一是当你调用了一个没有终止的递归方法:
clipboard.png

当你向方法传了一个超越规定范围的值也可能会出现这个报错。不少方法只接受特定范围的值做为输入。好比,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: Cannot Read Property ‘length’

这是在Chrome中读取一个undefined对象的length属性时报的错。
clipboard.png

你一般能够在array中找到length属性,可是你也可能在array尚未初始化或是变量名被隐藏在另外一个上下文中时遇到这个错误。让咱们用下面这个例子理解一下这个报错:

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();
  1. 向方法传入声明的参数
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,而咱们不能获取或是设置undefined的属性。这时候,应用就会抛出“Uncaught TypeError cannot set property of undefined.”报错。

clipboard.png

若是test对象不存在,也会抛出“Uncaught TypeError cannot set property of undefined.”

10. ReferenceError: Event Is Not Defined

当你试图访问的变量为undifined或是不在当前做用域范围内时,会抛出这个错误:
clipboard.png

若是你在使用事件处理系统时遇到这个报错,请确保你将事件对象做为参数传入了处理方法中。老的浏览器器如IE会提供一个全局的事件变量,而Chrome会自动将事件变量附属到handler上。Firfox不会自动添加它。而相似jQuery之类的库则试图规范化这个行为。总之,你最好将event做为采纳数传入事件处理方法中:

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

总结

看来大多数的错误都是null或是undefined相关的错误。若是你在使用编译器的严格模式选项,一个良好的类型检查系统如Typescript可以帮助你避免这些问题。它会在一个预期类型没有被定义时警告你。即使没有Typescript, 它也能帮助咱们使用防护性编程,在调用对象以前检查对象是不是undefined。

咱们但愿你可以学到一些新的内容,而且在将来可以避免这些错误,也可能这个指南帮你解决了一些头疼的问题。不管如何,即使是最佳实践,在编码过程当中仍是会出现意料以外的错误。了解影响用户使用的错误而且拥有能够快速解决问题的工具是很重要的。

clipboard.png
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注个人微信公众号!将会不按期的发放福利哦~

相关文章
相关标签/搜索