资讯专栏INFORMATION COLUMN

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

eccozhou / 2258人阅读

摘要:常出现的错误前十位为了可读性,错误名称进行了一定的简写。让我们深入了解每个错误发生的原因以及解决方法。这个问题很容易解决。当未捕获的错误跨越违法跨域策略的域边界时,会发生脚本错误。这是当你在中试图调用的方法时出现的错误。

JavaScript常出现的错误前十位

为了可读性,错误名称进行了一定的简写。让我们深入了解每个错误发生的原因以及解决方法。

1. Uncaught TypeError: Cannot Read Property

如果你是一名JavaScript开发人员,你可能已经记不清楚多少次看到这个错误了。当你读取一个undefined对象的属性或是调用其上的方法时,就会出现这个错误。你可以再Chrome Console中进行测试。

导致这个问题的原因有许多,最常见的是渲染UI组件时对state不恰当的初始化。让我们看一个真实APP中可能出现该情况的例子。我们选择了React,但是这样的不良初始化也适用于Angular,Vue或是其它的框架。

class Quiz extends Component {
  componentWillMount() {
    axios.get("/thedata").then(res => {
      this.setState({items: res.data});
    });
  }
  render() {
    return (
      
    {this.state.items.map(item =>
  • {item.name}
  • )}
); } }

这里要注意两件重要的事情:

组件的state(比如 this.state)在生命周期开始时为undefined。

当你异步获取数据的时候,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 (
      
    {this.state.items.map(item =>
  • {item.name}
  • )}
); } }

这和你的项目中的代码不一定完全相同,但是我们希望给你提供一个解决或是避免该问题的思路。

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

这是一个在Safari中在undefined对象上访问属性或方法时报的错。你可以在Safari的控制台上进行测试。这个错误和之前在Chrome中出现的错误是相同,只是报错信息不同。

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

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

有趣的是,在JavaScript中,null和undefined是不同的,所以我们看到了两个不同的报错信息。Undefined通常是指一个尚未赋值的变量,而null是指该变量的值为空。要想判断二者不等,应当使用严格的相等操作符:

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

任何对DOM元素进行处理的JS代码都应该都在DOM元素创建完成之后进行。JS代码按照HTML中的规定按从上到下的顺序进行解释。所以,如果在DOM元素之前存在标签,则脚本标签内的JS代码将在浏览器解析HTML页面时执行。如果在加载脚本之前尚未创建相关的DOM元素,就会出现此错误。

在这个例子中,我们通过添加一个事件监听器通知我们页面已经完成加载,来解决这个问题。一旦addEventListener被触发,init()方法就能够使用DOM元素。


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对象的方法时:

这等价于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的方法时出现的错误。

随着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中出现的一种错误。情况之一是当你调用了一个没有终止的递归方法:

当你向方法传了一个超越规定范围的值也可能会出现这个报错。很多方法只接受特定范围的值作为输入。比如,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属性时报的错。

你通常可以在array中找到length属性,但是你也可能在array还没有初始化或是变量名被隐藏在另一个上下文中时遇到这个错误。让我们用下面这个例子理解一下这个报错:

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,而我们不能获取或是设置undefined的属性。这时候,应用就会抛出“Uncaught TypeError cannot set property of undefined.”报错。

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

10. ReferenceError: Event Is Not Defined

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

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

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

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

我们希望你能够学到一些新的内容,并且在未来能够避免这些错误,也可能这个指南帮你解决了一些头疼的问题。无论如何,即便是最佳实践,在编码过程中还是会出现意料之外的错误。了解影响用户使用的错误并且拥有可以快速解决问题的工具是很重要的。


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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107532.html

相关文章

  • 头鹰深夜翻译:JAVA中异常处理最佳实践

    摘要:无需检查的异常也是的子类。从低层抛出的需检查异常强制要求调用方捕获或是抛出该异常。当前执行的线程将会停止并报告该异常。单元测试允许我在使用中查看异常,并且作为一个可以被执行的文档来使用。不要捕获最高层异常继承的异常同样是的子类。 前言 异常处理的问题之一是知道何时以及如何去使用它。我会讨论一些异常处理的最佳实践,也会总结最近在异常处理上的一些争论。 作为程序员,我们想要写高质量的能够解...

    W_BinaryTree 评论0 收藏0
  • 头鹰深夜翻译:持久化容器存储

    摘要:如果我们的容器使用,文件如下在这个例子中,我们可以重复创建和销毁,同一个持久存储会被提供给新的,无论容器位于哪个节点上。 前言 临时性存储是容器的一个很大的买点。根据一个镜像启动容器,随意变更,然后停止变更重启一个容器。你看,一个全新的文件系统又诞生了。 在docker的语境下: # docker run -it centos [root@d42876f95c6a /]# echo H...

    tianhang 评论0 收藏0
  • 头鹰深夜翻译:持久化容器存储

    摘要:如果我们的容器使用,文件如下在这个例子中,我们可以重复创建和销毁,同一个持久存储会被提供给新的,无论容器位于哪个节点上。 前言 临时性存储是容器的一个很大的买点。根据一个镜像启动容器,随意变更,然后停止变更重启一个容器。你看,一个全新的文件系统又诞生了。 在docker的语境下: # docker run -it centos [root@d42876f95c6a /]# echo H...

    xiao7cn 评论0 收藏0
  • 头鹰深夜翻译如何优化MYSQL查询

    摘要:如果我们用语句分析一下上面的查询,会得到如下的分析结果可以看到,优化器展示出了非常重要的信息,这些信息可以帮助我们微调数据库表。使用分析得到如下结果如上所示,将扫描全部行数据,这将使得查询极其缓慢。 1. 在所有用于where,order by和group by的列上添加索引 索引除了能够确保唯一的标记一条记录,还能是MySQL服务器更快的从数据库中获取结果。索引在排序中的作用也非常大...

    ThreeWords 评论0 收藏0
  • 头鹰深夜翻译:Volatile原子性, 可见性和有序性

    摘要:有可能一个线程中的动作相对于另一个线程出现乱序。当实际输出取决于线程交错的结果时,这种情况被称为竞争条件。这里的问题在于代码块不是原子性的,而且实例的变化对别的线程不可见。这种不能同时在多个线程上执行的部分被称为关键部分。 为什么要额外写一篇文章来研究volatile呢?是因为这可能是并发中最令人困惑以及最被误解的结构。我看过不少解释volatile的博客,但是大多数要么不完整,要么难...

    Lionad-Morotar 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<