引言
为了回馈社区开发者,我们查找数据库中数以千计的项目,找到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>
);
}
}
这里有两点需要注意:
-
1.组件状态默认是未定义;
-
2.当组件异步获取数据时,无论数据获取时机发生在构造器、将要挂载或完成挂载期间,组件在数据取得之前至少会渲染一次。
当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导致缓存问题,其实也不值得处理。
这里可以了解跨域设置问题
下面是在不同开发环境中设置头的示例:
- Apache
在服务端JS文件夹中创建.htaccess文件,内容写入:
Header add Access-Control-Allow-Origin "*"
- Nginx
直接在对应location块中添加add_header指令:
location ~ ^/assets/ {
add_header Access-Control-Allow-Origin *;
}
- HAProxy
添加下面代码到后台资源服务中:
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();
可以通过两种方式解决上面的问题:
- 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();
- 传递正确的实参
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, 也能帮助确保使用时对象已经定义。
译者注
-
原文有删减,因译者水平有限,如有错误,欢迎留言指正交流
网友评论