那些年,咱們一塊兒踩過的坑(前端防翻車指南)

javascript作爲一門腳本語言,因爲缺少約束,以及各類自動容錯機制和隱式轉換,產生了不少容易誤解和容易引起問題的地方, 《javascript語言精髓》一書中,有很大一部分篇幅介紹了javascript語言的糟粕和毒瘤部分,相信大部分問題有些人遇到過,有些人則經過學習知曉其原理而完美的躲過,隨着ES規範的不斷完善與發展,其中的一些問題獲得瞭解決或完善, 本文總結了常見的坑點,前車可鑑,後車之覆,但願你們讀了以後能少踩坑,少翻車,也能瞭解相關問題的根源所在。javascript

1. replace只替換一次

這個是新人最常遇到的問題了,好比下面的例子,我想把空格所有替換掉java

"are you kidding me?".replace(" ","-")
複製代碼

結果只替換了第一個字符,必須得用正則治療

"are you kidding me?".replace(/\s/g,"-")
複製代碼

2. 數組不是基本類型,typeof 數組返回object

其實,數組是用對象模擬出來的,你能夠用數組的任意下標賦值,甚至用字符串作下標也不要緊,length返回的是值最大的那個索引,原來數組一直在欺騙咱們git

arr=[];
arr.a="hello";
console.log(arr.length)
arr["100"]="world";
console.log(arr.length)
複製代碼

3. Date 對象的月份從0開始

誰能告訴我,爲啥月份是從0開始的,日期倒是從1開始的。由於不統一,不符合直覺,一不當心就踩坑。github

4. new Date("2019-01-01") 不兼容

new Date("2019-01-01") 在chrome,firefox 下運行是沒有問題的,在safari和IE下都沒法返回正常的結果,在ie下將日期格式改寫爲2019-1-1 可以獲得正確的結果。大多數人只是憑直覺這麼寫了,而在chrome下恰好能返回正確的結果,誰知道在別的瀏覽器下就可能會翻車。web

根據mdn文檔, new Date建立日期對象 基於 Unix Time Stamp,即自1970年1月1日(UTC)起通過的毫秒數。面試

最好是老老實實的這樣寫:chrome

new Date(2019,0,1)
複製代碼

通過實際測試 new Date("2019/01/01") 這樣的日期格式也能夠正確解析gulp

5. 擁有ID屬性的元素,會自動成爲window範圍內的全局變量

全局變量的泄漏真的不是由於你沒用var聲明,而是全部具備id的元素都會自動變成一個全局變量。這可能會無心中形成變量衝突。數組

6. 使用保留字作變量名或對象屬性名

var class ={};              // 非法
obj={"class":".on"} ;       // 非法
object = {case: value};     // 非法
object.case = value;        // 非法
 
複製代碼

ES規範規定不能用保留字作變量名,使用點號引用屬性名時也會出現問題。ES5規範修正了保留 字作屬性名問題,因此如今點號引用屬性名在chrome中能正常執行了,在IE9如下會產生報錯。瀏覽器

7. 使用未聲明的變量會報錯

js變量不聲明就能夠賦值使用,使咱們常常忽略了用var或let定位變量名,一方面會形成泄露出來不少全局變量,另外一方面,有些是代碼是if條件賦值,若是由於邏輯問題繞過了賦值的語句,就會產生異常了,例如以下代碼,當條件賦值不成立時就會產生異常了

if(false){
    UserInfo={userName:"Hello"}
}

if(UserInfo){
    console.log("do something")
}
 
複製代碼

若是沒給UserInfo賦值的語句沒執行,則會報錯 Uncaught ReferenceError: UserInfo is not defined 判斷一個變量是否存在正確的姿式是用typeof 判斷是不是undefined,若是是全局變量可用加window.變量名

還有一種常見的變量檢測場景,如if(result.userInfo.userName) 這樣寫法,是很不安全的,一但result沒有返回,或返回了null值,就會報錯了,這絕對是js異常排行榜中穩居前幾位的錯了,安全的寫法 if(result && result.userInfo && result.userInfo.userName)

8. 用for in 遍歷會查找原型鏈引起的潛在問題

給數組或對象原型添加了一些方法以後,會被for in 遍歷出來。我曾遇到不當心使用for in遍歷數組,功能實現上也沒有什麼問題,某天以後,使用Array.prototype給數組添加了一 polyfill方法以後,功能出現問題

var obj ={"a":1,"b":2,"c":3}; 
 
Object.prototype.method1= function (){} ;  
for(var key in obj){ 
console.log(obj[key]); 
} 
複製代碼

9. instanceof 在跨iframe頁面調用時不起做用

第一次踩這個坑的時候真是排查了好久,屬於萬萬沒想到的問題, 一直認爲instanceof 是很靠譜的,沒想到它在跨iframe 使用時徹底失效了。舉例來講你在A頁面定義了一個公共函數addConfig,若是傳入的是對象就添加到配置列表中,若是傳入是數組,就把多個配置都添加到配置列表中,很常見的功能場景

var configList=[];
function addConfig(config){
    if(config instanceof Array){
        configList=configList.concat(config)
    }else{
        configList.push(config)
    }
}
複製代碼

在單個頁面中使用是沒問題,一旦在A頁面中嵌入一個iframe子頁面B,B頁面使用parent.addConfig調用,就會出現問題,由於不一樣窗口(window)中的Array構造器函數是不相等的,因此instanceof 老是會返回false。

因此最安全的判斷數組方法是用Array.isArray方法,若是是ES3兼容就用Array.prototype.toString判斷。

其實上面那個例子有更簡便的寫法,用ES6延展操做符,連類型判斷都省了,這姿式真帥。

function addConfig(config){
    configList.push(...config)
}
複製代碼

10. arguments 不是數組

arguments只是和數組長的有點像,你想直接調用數組的那些方法是不行的,要先轉化一下成爲數組 [].slice.call(arguments);

11. 箭頭函數沒有arguments

遇到這種狀況,乖乖的寫普通函數吧,什麼?你既想享受 this的便利,又想用arguments,箭頭函數:臣妾作不到啊。

12. forEach 沒法break和return

若是你的數組很大,想找到符合的項就退出循環,報歉,仍是作不到,本身選擇的forEach ,含着淚也要執行完。

解決方法

  1. 用try catch,在要退出循環的地方throw一個Error,不推薦,爲了實現功能連異常都用上了,太喪心病狂了。
  2. 不用forEach,改用every和some 遍歷數組 示例,找到3以後退出循環:
result=[1,2,3,4,5].some(function(item){
   console.log(item)
   if(item==3){
   	return true
   }

})
複製代碼

13. forEach 不支持async

forEach 回調函數是獨立的上下文,沒法用async/await來實現阻塞外層函數執行的效果,示例代碼

function getResponse(url,time){
    return new Promise(function (resolve){
        setTimeout(function (){
            resolve(url);
        },time)
    });
}
var urlList=["/url1","url2"]
urlList.forEach(async (url)=>{
	let res=await getResponse(url,2000);
	console.log(res);
});
複製代碼

以上代碼,若是用普通的for 循環是串行的,應該是2秒後輸出url1 ,4秒後輸出url2,但用forEach倒是並行的,url1和url2同時輸出了

14. 自動插分號,引發函數返回值不正確

以下代碼

function test(){
    return
    {
        a: "hello"
    }
}

console.log(test()) //輸出undefined
複製代碼

return 語句後面自動插分號了,至關於return ; 返回了undefined,這不是我想要的結果

15. 自動插分號,引發含有自執行函數的文件合併後執行不了

//文件1:
(function test1(){
console.log("test1");
})()

//文件2:
(function test2(){
console.log("test2");
})()
複製代碼

兩個文件單獨執行都沒問題,可是用gulp打包合併在一塊兒應會報錯了,天啊,誰能想到呢,

Uncaught TypeError: (intermediate value)(...) is not a function
複製代碼

我在第一次遇到的時候真的是很抓狂,查了一成天,明明代碼都沒有錯啊。尤爲是打包還會壓縮代碼,根本看不出是哪裏的問題,一開始都覺得是編譯器壓縮代碼出問題了,方向都搞錯了,沒想到濃眉大眼的傢伙也會叛變革命了。

緣由是js自動插分號,因此記得寫分號是個好習慣。有些同窗喜歡在文件頭加個分號,不要以爲奇葩,就是爲了不這個問題。

16. this 是個任人打扮的小姑娘

obj.getName() 和 fun= obj.getName; fun(); 效果是不同的, 老生常談的問題了,當你不瞭解this的原理時,常常會犯以下的錯誤

var MyObject = function () {
   var self = this;
   this.alertMessage = "Javascript rules";
   this.OnClick = function() {
         alert(self.value);
     }
}();
document.getElementById("theText").onclick =  MyObject.OnClick

複製代碼

複習一下,this 與函數自己以及上下文都沒有關係,只取決於函數是如何被調用的。記住這句話永遠不會被this搞暈。

this只有4種情景:

    1. 純粹的函數調用,this指向window
    1. 對象方法調用,必須是a.b()或 a [ "b" ]()這種形式調用,this指向對象
    1. 構造函數調用,new Foo() 時,會自動生成一個空對象並返回,this指向這個新生成的空對象
    1. call,apply,bind,this能夠隨意指定。

17. 未聲明的變量賦值

變量不須要var聲明就能夠賦值(在嚴格模式下已經禁止) 在控制檯輸入,name="hello"

刷新頁面再執行console.log(name)

值在頁面刷新後竟然還在,是否是很震驚。緣由是直接給name賦值至關於給全局變量window.name賦值,window.name是進程級的,不會隨窗口刷新消失。這個坑形成的最大問題就是泄露出來不少全局變量。

18. 使人疑惑的+操做符,隱式類型轉換

面試題常常會出這些,就是由於很常見,一不當心就會踩坑,因此記一下經常使用的是頗有必要的,加號操做符的兩個操做數若是都是值類型,第一優先級是轉換爲string(若是其中有一個string),第二優先級是轉換爲number (若是其中有一個number)

// 1
console.log(   1 + 2   ); // 3
console.log(   1 + 2 +"3"  );  //33
console.log( "3" + "4" ); // "34"
// 2
console.log(   1 + "3" ); // "13"
console.log( "3" + 1   ); // "31"
// 3
console.log( 1 + null      ); //1
console.log( 1 + undefined ); //NaN
console.log( 1 + NaN       );//NaN
// 4
console.log( "3" + null      ); //3null
console.log( "3" + undefined ); //3undefined
console.log( "3" + NaN       );//3NaN
// 5
console.log( 1 + {} ); //1[object Object]
console.log( 1 + [] ); //1

複製代碼

19. if判斷檢測變量的雷區

if 檢測條件會被隱式轉換爲boolean類型,以下

if(something){
    console.log(something)
}
複製代碼

真的要去背一下真值表嗎,否則真的很難保證不踩到雷, 總結一下來講,當值爲false,null,undefined,"",0,NaN時if條件不成立,其它條件都成立

如下是常見類型的測試:

if(false){
    alert('false'); // false
}
if(undefined){
    alert('false'); // false
}
if(null){
    alert('null'); // false
}
if(0){
    alert('0'); // false
}
if(''){
    alert(''); // false
}
if(NaN){ 
    alert('NaN'); // false
}
if(' '){
    alert(' '); // true
}
if([]){
    alert('[]'); // true
}
if({}){
    alert('[]'); // true
}
if(new Object()){
    alert('object'); // true
}

複製代碼

20. switch 穿透

switch case 若是不寫break,會一直往下一個case執行,雖然會帶來一些便利,可是會形成邏輯混亂難以讀懂,其危害就像臭名昭著的goto語句同樣,以下代碼:

var type="a";
switch(type){
case "a":
console.log("aaa")
case "b":
console.log("bbb");
case "c":
console.log("cccc")
}
複製代碼

代碼的執行結果是aaa,bbb,cccc都會輸出, 所以,每寫一個case都要先寫上一個break,避免忘記。若是真的是有幾個case須要執行相同的邏輯,那就封裝一個閉包函數來調用。

21. 對象結尾多餘的逗號問題

若是你還要兼容IE,這個問題絕對是揮之不去的夢魘

var obj={
a:1,
b:2,
}

複製代碼

屢次由於這個問題出現上線後IE9如下瀏覽器整個掛掉。好在ES5標準已經要求兼容了,IE9以上不會再出現。

總結

本文總結了javascript常見的21個坑,他們有些是語言設計的缺陷,有些是特性原本如此,只是違反咱們的直覺,人們老是在踩坑,錯誤,失敗中不斷吸收經驗教訓不斷成長,聰明的人會從別人的問題中吸收經驗教訓,有了前車可鑑,咱們能夠少走不少彎路,加速成長。

最後,推薦一下個人github開源項目:github.com/windyfancy/…

但願你們多多支持。

參考資料:<javascript語言精髓> Nine Javascript Gotchas

相關文章
相關標籤/搜索