貓頭鷹的深夜翻譯:從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
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~

相關文章
相關標籤/搜索