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 (
- <ul>
- {this.state.items.map(item =>
- <li key={item.id}>{item.name}</li>
- )}
- </ul>
- );
- }
- }
这里要注意两件重要的事情:
组件的 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 (
- <ul>
- {this.state.items.map(item =>
- <li key={item.id}>{item.name}</li>
- )}
- </ul>
- );
- }
- }
这和你的项目中的代码不一定完全相同, 但是我们希望给你提供一个解决或是避免该问题的思路
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 元素
- <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 Doesnt 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://segmentfault.com/a/1190000013307889