javascript作爲一門腳本語言,因爲缺少約束,以及各類自動容錯機制和隱式轉換,產生了不少容易誤解和容易引起問題的地方, 《javascript語言精髓》一書中,有很大一部分篇幅介紹了javascript語言的糟粕和毒瘤部分,相信大部分問題有些人遇到過,有些人則經過學習知曉其原理而完美的躲過,隨着ES規範的不斷完善與發展,其中的一些問題獲得瞭解決或完善, 本文總結了常見的坑點,前車可鑑,後車之覆,但願你們讀了以後能少踩坑,少翻車,也能瞭解相關問題的根源所在。javascript
這個是新人最常遇到的問題了,好比下面的例子,我想把空格所有替換掉java
"are you kidding me?".replace(" ","-")
複製代碼
"are you kidding me?".replace(/\s/g,"-")
複製代碼
其實,數組是用對象模擬出來的,你能夠用數組的任意下標賦值,甚至用字符串作下標也不要緊,length返回的是值最大的那個索引,原來數組一直在欺騙咱們git
arr=[];
arr.a="hello";
console.log(arr.length)
arr["100"]="world";
console.log(arr.length)
複製代碼
誰能告訴我,爲啥月份是從0開始的,日期倒是從1開始的。由於不統一,不符合直覺,一不當心就踩坑。github
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
全局變量的泄漏真的不是由於你沒用var聲明,而是全部具備id的元素都會自動變成一個全局變量。這可能會無心中形成變量衝突。數組
var class ={}; // 非法
obj={"class":".on"} ; // 非法
object = {case: value}; // 非法
object.case = value; // 非法
複製代碼
ES規範規定不能用保留字作變量名,使用點號引用屬性名時也會出現問題。ES5規範修正了保留 字作屬性名問題,因此如今點號引用屬性名在chrome中能正常執行了,在IE9如下會產生報錯。瀏覽器
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)
給數組或對象原型添加了一些方法以後,會被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]);
}
複製代碼
第一次踩這個坑的時候真是排查了好久,屬於萬萬沒想到的問題, 一直認爲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)
}
複製代碼
arguments只是和數組長的有點像,你想直接調用數組的那些方法是不行的,要先轉化一下成爲數組 [].slice.call(arguments);
遇到這種狀況,乖乖的寫普通函數吧,什麼?你既想享受 this的便利,又想用arguments,箭頭函數:臣妾作不到啊。
若是你的數組很大,想找到符合的項就退出循環,報歉,仍是作不到,本身選擇的forEach ,含着淚也要執行完。
解決方法
result=[1,2,3,4,5].some(function(item){
console.log(item)
if(item==3){
return true
}
})
複製代碼
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同時輸出了
以下代碼
function test(){
return
{
a: "hello"
}
}
console.log(test()) //輸出undefined
複製代碼
return 語句後面自動插分號了,至關於return ; 返回了undefined,這不是我想要的結果
//文件1:
(function test1(){
console.log("test1");
})()
//文件2:
(function test2(){
console.log("test2");
})()
複製代碼
兩個文件單獨執行都沒問題,可是用gulp打包合併在一塊兒應會報錯了,天啊,誰能想到呢,
Uncaught TypeError: (intermediate value)(...) is not a function
複製代碼
我在第一次遇到的時候真的是很抓狂,查了一成天,明明代碼都沒有錯啊。尤爲是打包還會壓縮代碼,根本看不出是哪裏的問題,一開始都覺得是編譯器壓縮代碼出問題了,方向都搞錯了,沒想到濃眉大眼的傢伙也會叛變革命了。
緣由是js自動插分號,因此記得寫分號是個好習慣。有些同窗喜歡在文件頭加個分號,不要以爲奇葩,就是爲了不這個問題。
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種情景:
a [ "b" ]()
這種形式調用,this指向對象變量不須要var聲明就能夠賦值(在嚴格模式下已經禁止) 在控制檯輸入,name="hello"
刷新頁面再執行console.log(name)
值在頁面刷新後竟然還在,是否是很震驚。緣由是直接給name賦值至關於給全局變量window.name賦值,window.name是進程級的,不會隨窗口刷新消失。這個坑形成的最大問題就是泄露出來不少全局變量。
面試題常常會出這些,就是由於很常見,一不當心就會踩坑,因此記一下經常使用的是頗有必要的,加號操做符的兩個操做數若是都是值類型,第一優先級是轉換爲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
複製代碼
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
}
複製代碼
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須要執行相同的邏輯,那就封裝一個閉包函數來調用。
若是你還要兼容IE,這個問題絕對是揮之不去的夢魘
var obj={
a:1,
b:2,
}
複製代碼
屢次由於這個問題出現上線後IE9如下瀏覽器整個掛掉。好在ES5標準已經要求兼容了,IE9以上不會再出現。
本文總結了javascript常見的21個坑,他們有些是語言設計的缺陷,有些是特性原本如此,只是違反咱們的直覺,人們老是在踩坑,錯誤,失敗中不斷吸收經驗教訓不斷成長,聰明的人會從別人的問題中吸收經驗教訓,有了前車可鑑,咱們能夠少走不少彎路,加速成長。
最後,推薦一下個人github開源項目:github.com/windyfancy/…
但願你們多多支持。
參考資料:<javascript語言精髓> Nine Javascript Gotchas