接觸JavaScript兩年多遇到過各種錯誤,其中有一些讓人防不勝防,原來對JavaScript的誤會如此之深,僅以此文總結一下常見的各種想當然的誤區。
String replace
string的replace方法我們經常用,替換string中的某些字元,語法像這樣子
1 |
string.replace(subStr/reg,replaceStr/function) |
第一個引數是要查詢的字串或者一個正規表示式,第二個引數是想替換成的字串或一個方法,我們可以這麼使用
1 |
"I'm Byron".replace("B","b") // I'm byron |
記過和我們想得一樣,但是
1 |
"I'm a student, and you?".replace("n","N"); // I'm a studeNt, and you? |
和我們預期的不一樣,第二個‘n’沒有被替換。字串的 replace 方法如果第一個引數傳入字串,那麼只有第一個匹配項會被替換。如果要替換全部匹配項,需要傳入一個 RegExp 物件並指定其 global 屬性。
1 |
"I'm a student, and you?".replace(/n/g,"N"); // I'm a studeNt, aNd you? |
這樣才可以達到我們目的,關於string replace方法詳細使用可以看看JavaScript string 的replace
Date 物件
我們可以這樣構造一個Date物件
1 2 3 4 |
new Date() //Date {Fri Aug 02 2013 16:50:33 GMT+0800 (China Standard Time)} new Date(milliseconds) //Date {Fri Aug 02 2013 16:53:26 GMT+0800 (China Standard Time)} new Date("2013/08/02") //Date {Fri Aug 02 2013 00:00:00 GMT+0800 (China Standard Time)} new Date(year,month,day,hours,minutes,seconds,ms) |
前三種方式沒有什麼問題,但第四種得到的結果回合我們預期的不一致
1 |
new Date(2013,08,02) //Date {Mon Sep 02 2013 00:00:00 GMT+0800 (China Standard Time)} |
我們可以看到,傳入的月份是08,返回的結果卻是九月。這是因為Date物件的月份是從0開始計數的(天卻不是),即0代表一月,1代表二月…11代表12月。在呼叫Date例項的getMonth方法時尤其要注意
1 2 |
var d = new Date(2012, 4, 15); // 2012年5月15日 alert(d.getMonth()); // 結果為4 |
Date.parse
Date.parse方法可以識別兩種格式的字串引數(標準的長日期格式,比如帶星期的那種,也可以識別,不過不常用):
1. “M/d/yyyy”: 美國的日期顯示格式。如果年傳入2位則作為 19xx 處理
2.”yyyy-MM-dd” 或 “yyyy/MM/dd”: 注意月和日都必須是兩位
Date.parse 的返回結果不是一個Date物件,而是從1970-01-01午夜(GMT)到給定日期之間的毫秒數。可以用Date的建構函式將其轉換為Date物件。
1 2 3 |
new Date(Date.parse("8/2/2012")); // 正確識別為2012年8月2日 new Date(Date.parse("2012-08-02")); // 正確識別為2012年8月2日 new Date(Date.parse("2012-8-2")); // 不能識別 |
for…in 遍歷陣列
for…in用來遍歷一個物件中的成員(屬性,方法),如果用來遍歷陣列的到的結果並不是預期中陣列每項的值,方法神馬的會被遍歷出來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Array.prototype.contains = function(item) { for (var i = 0; i <= this.length - 1; i++) { if(this[i] == item) return this[i]; } } var staff = [ "Staff A", "Staff B" ]; // Normal Enumeration: Only the 2 items are enumerated for (var i = 0; i <= staff.length - 1; i++) { var singleStaff = staff[i]; alert(singleStaff); } // for...in Enumeration: the method "contains" are enumerated, too for (var singleStaff in staff) { alert(singleStaff); } |
事實上很多時候我們都會給陣列加上其他屬性。比如 jQuery 物件就是一個陣列物件加上一些擴充套件方法;再比如 String.prototype.match 方法返回值就是一個陣列(正規表示式及其子表示式的匹配項)加上 index 和 input 兩個屬性。
parseInt
語法: parseInt(string, radix)
引數 | 描述 |
string | 必需。要被解析的字串。 |
radix | 可選。表示要解析的數字的基數。該值介於 2 ~ 36 之間。如果省略該引數或其值為 0,則數字將以 10 為基礎來解析。如果它以 “0x” 或 “0X” 開頭,將以 16 為基數。如果該引數小於 2 或者大於 36,則 parseInt() 將返回 NaN。 |
當引數 radix 的值為 0,或沒有設定該引數時,parseInt() 會根據 string 來判斷數字的基數。
舉例,如果 string(開頭結尾空格自動省略) 以 “0x” 開頭,parseInt() 會把 string 的其餘部分解析為十六進位制的整數。如果 string 以 0 開頭,那麼 ECMAScript v3 允許 parseInt() 的一個實現把其後的字元解析為八進位制或十六進位制的數字。如果 string 以 1 ~ 9 的數字開頭,parseInt() 將把它解析為十進位制的整數。如果字串的第一個字元不能被轉換為數字,那麼 parseFloat() 會返回 NaN。
1 2 3 4 5 6 |
parseInt("10"); //返回 10 parseInt("19",10); //返回 19 (10+9) parseInt("11",2); //返回 3 (2+1) parseInt("17",8); //返回 15 (8+7) parseInt("1f",16); //返回 31 (16+15) parseInt("010"); //未定:返回 10 或 8 |
setTimeout/setInterval執行時機
setTimeout()和setInterval()經常被用來處理延時和定時任務。setTimeout() 方法用於在指定的毫秒數後呼叫函式或計算表示式,而setInterval()則可以在每隔指定的毫秒數迴圈呼叫函式或表示式,直到clearInterval把它清除。
JavaScript其實是執行在單執行緒的環境中的,這就意味著定時器僅僅是計劃程式碼在未來的某個時間執行,而具體執行時機是不能保證的,因為頁面的生命週期中,不同時間可能有其他程式碼在控制JavaScript程式。在頁面下載完成後程式碼的執行、事件處理程式、Ajax回撥函式都是使用同樣的執行緒,實際上瀏覽器負責進行排序,指派某段程式在某個時間點執行的優先順序。
我們可以可以把JavaScript想象成在時間線上執行。當頁面載入的時候首先執行的是頁面生命週期後面要用的方法和變數宣告和資料處理,在這之後JavaScript程式將等待更多程式碼執行。當程式空閒的時候,下一段程式碼會被觸發
除了主JavaScript程式外,還需要一個在程式下一次空閒時執行的程式碼佇列。隨著頁面生命週期推移,程式碼會按照執行順序新增入佇列,例如當按 鈕被按下的時候他的事件處理程式會被新增到佇列中,並在下一個可能時間內執行。在接到某個Ajax響應時,回撥函式的程式碼會被新增到佇列。JavaScript中沒有任何程式碼是立即執行的,但一旦程式空閒則儘快執行。定時器對佇列的工作方式是當特定時間過去後將程式碼插入,這並不意味著它會馬上執行,只能表示它儘快執行。
關於setTimeout/setInterval執行時機詳細說明可以看看 setTimeout()和setInterval() 何時被呼叫執行
預解析
1 2 3 4 |
console.log(a); //Error:a is not defined ,直接報錯,下面語句沒法執行,一下結果為註釋該句後結果 console.log(b) //undefined var b="Test"; console.log(b);//Test |
很奇怪前兩句變數a,b都沒有宣告,第一句卻報錯,第二句能夠輸出undefined?這是因為JavaScript 是解釋型語言,但它並不是直接逐步執行的,JavaScript解析過程分為先後兩個階段,一個是預處理階段,另外一個就是執行階段。在預處理階段 JavaScript直譯器將完成把JavaScript指令碼程式碼轉換到位元組碼,然後第二階段JavaScript直譯器藉助執行環境把位元組碼生成機械 碼,並順序執行。
也就說JavaScript值執行第一句語句之前就已經將函式/變數宣告預處理了,var b=”Test” 相當於兩個語句,var b;(undefined結果的來源,在執行第一句語句之前已經解析),b=”Test”(這句是順序執行的,在第二句之後執行)。這也是為什麼我們可以 在方法宣告語句之前就呼叫方法的原因。
1 2 3 4 5 |
showMsg(); // This is message function showMsg() { alert('This is message'); } |
塊級作用域
JavaScript沒有塊級作用域,只有函式級作用域,這就意味著{}在JavaScript中只能起到語法塊的作用,而不能起到作用域塊作用
1 2 3 4 5 |
if(true){//語法塊,保證{}內程式碼if條件成立執行 //... var a=3; } console.log(a); //3 |
上面例子可以清楚看到屬於window的console.log方法依然可以訪問貌似是區域性變數的a
閉包
首先從一個經典錯誤談起,頁面上有若干個div, 我們想給它們繫結一個onclick方法,於是有了下面的程式碼
1 2 3 4 5 6 |
<div id="divTest"> <span>0</span> <span>1</span> <span>2</span> <span>3</span> </div> <div id="divTest2"> <span>0</span> <span>1</span> <span>2</span> <span>3</span> </div> |
1 2 3 4 5 6 7 8 |
$(document).ready(function() { var spans = $("#divTest span"); for (var i = 0; i < spans.length; i++) { spans[i].onclick = function() { alert(i); } } }); |
很簡單的功能可是卻偏偏出錯了,每次alert出的值都是4,簡單的修改就好使了
1 2 3 4 5 6 7 8 9 10 |
var spans2 = $("#divTest2 span"); $(document).ready(function() { for (var i = 0; i < spans2.length; i++) { (function(num) { spans2[i].onclick = function() { alert(num); } })(i); } }); |
閉包是指有許可權訪問另一個函式作用域的變數的函式,建立閉包的常見方式就是在一個函式內部建立另一個函式,只要存在呼叫內部函式的可 能,JavaScript就需要保留被引用的函式。而且JavaScript執行時需要跟蹤引用這個內部函式的所有變數,直到最後一個變數廢 棄,JavaScript的垃圾收集器才能釋放相應的記憶體空間。
這筆書的很膚淺,想簡單瞭解閉包可以看看JavaScript 閉包究竟是什麼
結束
關於JavaScript的坑坑窪窪真不少,一邊學習一邊總結吧。