JavaScript 誤區

謙行發表於2013-08-02

接觸JavaScript兩年多遇到過各種錯誤,其中有一些讓人防不勝防,原來對JavaScript的誤會如此之深,僅以此文總結一下常見的各種想當然的誤區

String replace

string的replace方法我們經常用,替換string中的某些字元,語法像這樣子

string.replace(subStr/reg,replaceStr/function)

第一個引數是要查詢的字串或者一個正規表示式,第二個引數是想替換成的字串或一個方法,我們可以這麼使用

"I'm Byron".replace("B","b") // I'm byron

記過和我們想得一樣,但是

"I'm a student, and you?".replace("n","N"); // I'm a studeNt, and you?

和我們預期的不一樣,第二個‘n’沒有被替換。字串的 replace 方法如果第一個引數傳入字串,那麼只有第一個匹配項會被替換。如果要替換全部匹配項,需要傳入一個 RegExp 物件並指定其 global 屬性。

"I'm a student, and you?".replace(/n/g,"N"); // I'm a studeNt, aNd you?

這樣才可以達到我們目的,關於string replace方法詳細使用可以看看JavaScript string 的replace

typeof

關於typeof最容易讓人誤會的就是typeof是操作符,並不是函式,也就是說我們在判斷一變數型別時沒必要加括號,直接使用即可

typeof 1; //number
typeof(1); //number, 沒必要這麼寫,但是也沒錯,等於是給變數或常量包裝了一下

typeof 的返回值是字串,在大多數情況下返回的和我們預期結果是一樣的,但是有幾點注意的地方,我們知道JavaScript有幾種基本型別,number、string、boolean、null、undefined,再就是物件object了,我們看幾個例子

typeof 123; //number
typeof NaN //number
typeof "123" //string
typeof false; //boolean
typeof undefined ; //undefined
typeof null //object
typeof new Array(); //object
typeof (new Array()); //覺得不夠清晰可以這麼用,結果是一樣的
typeof (function(){}); //function
typeof a; //undefined

1. 123是個數字返回number

2. 雖然NaN表示不是數字,但是typeof仍然會返回number

3. “123” 變成了字串,所以返回string

4. false 型別就是boolean

5. undefined的型別就是undefined,這個型別的變數只能有一個字面值”undefined”

6. null的型別並不是null,而是object,所以我們不要寄希望與typeof幫我們判斷null

7. typeof如果判斷是物件只會返回object,而不會返回Array、Date的具體型別

9. function也是object的一種,按說也應該直接返回object。但是typeof對它區別對待了,返回了function,我們可以用此判斷變數是否為函式

10. 沒定義的變數同樣返回undefined

if和==

在javascript中if並不僅僅判斷boolean決定真假,0、NaN、””、undefined、null、false都會被認為是假

if (!false) {
            console.log(1);
        };
        if (!0) {
            console.log(2);
        };
        if (!"") {
            console.log(3);
        };
        if (!undefined) {
            console.log(4);
        };
        if (!null) {
            console.log(5);
        };
    if (!NaN) { 
        console.log(6); 
    };

在console中123456都會被列印出來

但是這並不意味著這些值就==false

0==false; //true
"0"==false; //true,這個也是true
undefined==false //false
null==false //false
null==null //true
NaN==NaN //false
null==undefined //true

納尼!!!

相等運算子 (==、!=)
如果兩表示式的型別不同,則試圖將它們轉換為字串、數字或 Boolean 量
NaN 與包括其本身在內的任何值都不相等。
負零等於正零。
null 與 null 和 undefined 相等。
相同的字串、數值上相等的數字、相同的物件、相同的 Boolean 值或者(當型別不同時)能被強制轉化為上述情況之一,均被認為是相等的其他比較均被認為是不相等的。

所以說 0==”0”; 返回的也會是true。

那麼應該則麼判斷兩個變數究竟是不是一回事兒呢

恆等運算子 (===、!==)
除了不進行型別轉換,並且型別必須相同以外,這些運算子與相等運算子的作用是一樣的,這下就搞定了。

Date 物件

我們可以這樣構造一個Date物件

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)

前三種方式沒有什麼問題,但第四種得到的結果回合我們預期的不一致

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方法時尤其要注意

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物件。

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用來遍歷一個物件中的成員(屬性,方法),如果用來遍歷陣列的到的結果並不是預期中陣列每項的值,方法神馬的會被遍歷出來

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。

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() 何時被呼叫執行

預解析

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”(這句是順序執行的,在第二句之後執行)。這也是為什麼我們可以在方法宣告語句之前就呼叫方法的原因。

showMsg(); // This is message
function showMsg()
{
    alert('This is message');
}

塊級作用域

JavaScript沒有塊級作用域,只有函式級作用域,這就意味著{}在JavaScript中只能起到語法塊的作用,而不能起到作用域塊作用

if(true){//語法塊,保證{}內程式碼if條件成立執行
    //...
    var a=3;
}
console.log(a); //3

上面例子可以清楚看到屬於window的console.log方法依然可以訪問貌似是區域性變數的a

閉包

首先從一個經典錯誤談起,頁面上有若干個div, 我們想給它們繫結一個onclick方法,於是有了下面的程式碼

<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>
$(document).ready(function() {
            var spans = $("#divTest span");
            for (var i = 0; i < spans.length; i++) {
                spans[i].onclick = function() {
                    alert(i);
                }
            }
        });

很簡單的功能可是卻偏偏出錯了,每次alert出的值都是4,簡單的修改就好使了

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的坑坑窪窪真不少,一邊學習一邊總結吧。

相關文章