前端译趣我爱编程

来自1000个项目的十大JS错误及规避方法

2018-06-05  本文已影响39人  linc2046
来自1000个项目的十大JS错误及规避方法

引言

为了回馈社区开发者,我们查找数据库中数以千计的项目,找到JS中出现十大错误。

本文将会深入展示错误原因和解决措施。同时如果你编码中能避免这些错误,相信你会成为一名更好的开发者。

1.无法捕获的类型错误: 属性无法读取

这是一条开发者常见的错误,当你在Chrome中读取或调用不存在对象的属性或方法,就会报这个错。

错误起因有很多种,但最常见的是组件渲染过程中,状态没有正常初始化。

我们可以看下现实世界的例子,这里以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>
    );
  }
}

这里有两点需要注意:

当Quiz组件第一次渲染时,this.state.items是未定义的,所以items的循环获取item也是未定义,此时会抛这个错误。

最简单的修复方式是使用默认值进行构造函数状态初始化。

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.类型错误: 对象未定义

当你在Safari浏览器中读取或调用未定义对象的属性或方法,这里错误和第一条类似,只不过Safari使用不同的提示。

3.类型错误: 对象为空

此类错误发生在Safari中,读取或调用空对象属性或方法。

有趣的是,js中的null和undefined并不一致,这也是我们看到两种不同提示的原因。

未定义是变量还未赋值,null意味着值为空。可以使用严格等号运算符比较是否相等。

现实编码中常见的例子是元素加载前就在JS中使用DOM元素,调用DOM API就会返回空或空对象引用。

任何执行或处理DOM元素的JS代码都应该在DOM元素创建之后执行。

JS代码根据html中的顺序,自上而下进行解释执行。当DOM元素中存在脚本标签时,当浏览器解析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.未知脚本错误

当JS错误跨月域限制,同时和跨域政策违背时,便会发送脚本错误。例如,將JS文件托管在CDN上,任何未知错误(错误会冒泡至window.onerror处理器,而不是

由try-catch捕获)只会简单提示脚本错误,而不会提示有用信息。

这是浏览器安全措施限制跨域数据传递,因此不允许错误信息的传递。

可以通过下面错误还原此类错误提示:

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

设置Access-Control-Allow-Origin*预示着资源可以从任何域进行访问。

你可以將*替换成需要的域名,但是处理多个域名会变得麻烦,如果你用CDN导致缓存问题,其实也不值得处理。

这里可以了解跨域设置问题

下面是在不同开发环境中设置头的示例:

在服务端JS文件夹中创建.htaccess文件,内容写入:

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

直接在对应location块中添加add_header指令:

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

添加下面代码到后台资源服务中:

rspadd Access-Control-Allow-Origin:\ *

2.脚本标签设置 crossorigin=”anonymous”

html源代码中在每个已经后台设置Access-Control-Allow-Origin的脚本标签上设置crossorigin="anonymous"

在添加属性前确保服务端头设置已经完成。火狐中如果有crossorigin="anonymous",但服务端头没有设置,脚本将不会执行。

5.类型错误: 对象不支持的属性

IE浏览器中调用未定义的方法会发生此类错误。

这和谷歌中的类型错误,函数未定义是一样的逻辑错误。

这在使用js命名空间的IE浏览器的web应用中很常见。

99.9%的问题都是IE不能正确把当前方法的命名空间绑定至this关键字导致的。

例如,如果JS命名空间Rollbar存在方法isAwesome, 正常来说,在命名空间下你可以像这样调用方法:

this.isAwesome();

谷歌、火狐和Opera浏览器支持上面的语法,但IE不支持,使用JS命名空间最安全的方式是使用真正的命名空间名字。

Rollbar.isAwesome();

6.类型错误: 函数未定义

谷歌浏览器下调用未定义函数会抛此类错误。

这些年来随着JS代码技巧和设计模式日益复杂,回调和闭包中作用域进行自我引用的次数也频繁增加,这也是一般导致此类错误的根源。

考虑下面的代码:

function clearBoard(){
  alert("Cleared");
}
document.addEventListener("click", function(){
  this.clearBoard(); // what is “this” ?
});

如果你点击页面,控制台会报: 类型错误, this.clearBoard不是函数。

错误原因是:匿名函数在document上下文中执行,然而clearBoard却在window中定义。

兼容旧版本浏览器的一般解法是在函数外部將this引用指向另外的变量,同时在闭包中调用继承的变量:

var self=this;  // save reference to 'this', while it's still this!
document.addEventListener("click", function(){
  self.clearBoard();
});

高级浏览器中可以直接使用bind方法传递引用:

document.addEventListener("click",this.clearBoard.bind(this));

7.无法捕获的范围错误: 最大化调用栈溢出

Chrome下有很多情况会发生此类错误。

其中一种是调用递归函数而不进行终止。

var a = [];
function recurse(a){
    a[0] = [1];
    recurse(a[0]);
}
recurse(a);

另外一种是原生函数接收范围溢出的参数。

很多原生函数只接收特定范围的输入值。

如: 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.类型错误: 无法读取length属性

Chrome下读取未定义变量的长度时会发生此类错误。

一般数组才有length, 如果数组没有初始化或变量名称隐藏至其他上下文时,也会报错。

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

可以通过两种方式解决上面的问题:

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.无法捕获类型错误: 无法设置属性

当访问未定义变量时,总是返回undefined, 我们不能获取或设置undefined的属性。

var test = undefined;

test.value = 0;

10.引用错误: event未定义

当试图访问未定义变量或超出当前作用域的变量时便会抛此类错误。

function test(){
    var foo;
}

console.log(foo);

使用事件处理器时抛此类错误,确保事件对象作为参数传递。

旧式IE浏览器提供全局事件对象,谷歌会自动绑定事件变量到对应的处理函数中。火狐不会自动绑定。

最好是事件处理函数中使用传递的事件对象。

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

总结

从上面错误来看,很多都是空或未定义错误。

如果你使用严格编译器,像静态类型检查工具,如: TypeScript可以帮助你避免这些问题。

工具可以提示期望却尚未定义的类型。

即使没有TypeScript, 也能帮助确保使用时对象已经定义。

译者注

上一篇 下一篇

猜你喜欢

热点阅读