JavaScript是前端必備,而這其中的精髓也太多太多,最近在溫習的時候發現有些東西比較容易忽略,這裡記錄一下,一方面是希望自己在平時應用的時候能夠得心應手,另一方面也希望能給别人帶來一點點的收獲。
一、JavaScript的==和===,即相等運算符和等同運算符。
相等運算符,如果操作數有相同的類型,則判斷其等同性,如果兩個操作數的值相等,則傳回true(相等),否則傳回false(不相等);
如果類型不同,則按照這樣的情況來判斷:
null和undefined相等;數字與字元串字元比較,字元串轉化為數字再比較;其中一個為true轉化為1再做比較;如果一個值是對象,另一個是數字或者字元串,則将對象轉化為原始值(通過toString()或者valueOf()方法),其他傳回false。
等同運算符,如果操作數類型不一樣,直接傳回false,類型相同,如下判斷:
1、都是數字,若值相同,則兩者等同但是NAN除外,因為NAN與本身也不等,否則不相同;
2、都是字元串的情況:值不等則不等同,否則等同;
3、都是布爾值,均為true/false則等同否則不等同;
4、如果兩個操作數引用同一對象(數組或函數)則等同,否則不等;
5、均為null/undefined則等同。
二、函數作用域
作用域在所有語言中都有展現,隻是在Javascript腳本裡有其特殊性-->Javascript中的作用域為函數體内有效,而無塊兒作用域。在Java或者C#中,我們可以寫出下面的循環:

public void method(string obj1,string obj2){
for(int i=0;i<obj1.length;i++){
//do something
}
//此時的i為未定義
for(int i=0;i<obj2.length;i++){
//do another thing
}
}
View Code
而在Javascript中不同:

function func(){
for(var i = 0; i < array.length; i++){
//do something here.
}
//此時i仍然有值,及I == array.length
print(i);//i == array.length;
}
Javascript的函數是在局部作用域内運作的,在局部作用域内運作的函數體可以通路其外層的(可能是全局作用域)的變量和函數。JavaScript的作用域為詞法作用域,所謂詞法作用域是說,其作用域為在定義時(詞法分析時)就确定下來的,而并非在執行時确定,如下例:

var str = "global";
function scopeTest(){
print(str);
var str = "local";
print(str);
}
scopeTest();
您覺得運作結果是什麼呢?global local或者local local 再或者其他?而正确的結果卻是 undefined local,沒錯,undefined local!
因為在函數scopeTest的定義中,預先通路了未聲明的變量str,然後才對str變量進行初始化,是以第一個print(str)會傳回undifined錯誤。那為什麼函數這個時候不去通路外部的str變量呢?這是因為,在詞法分析結束後,構造作用域鍊的時候,會将函數内定義的var變量放入該鍊,是以str在整個函數scopeTest内都是可見的(從函數體的第一行到最後一行),由于str變量本身是未定義的,程式順序執行,到第一行就會傳回未定義,第二行為str指派,是以第三行的print(str)将傳回”local”。
三、數組操作
常用的對數組的操作:
contact() 連接配接兩個或更過的數組,并傳回結果
join() 把數組所有元素放入一個字元串,元素通過指定的分隔符進行分隔
pop() 删除并傳回最後一個元素與push()對應,向數組末尾添加一個或更多元素,并傳回新長度;類似于壓棧和彈棧
reverse() 颠倒元素的順序
shift() 删除并傳回第一個元素
slice() 從已有數組傳回制定數組
sort() 對數組元素排序,預設按字母排序,也可按數字大小排:array.sort(function(a,b){return a-b});
splice()删除元素,并添加新元素
unshift()向數組開頭添加一個或更多元素,并傳回新的長度
valueOf() 傳回數組對象的原始值
四、JavaScript閉包特性
我們來看一個例子,如果不了解JavaScript的特性,很難找到原因:

var outter = [];
function clouseTest () {
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(){
print(i);
}
outter.push(x);
}
}
//調用這個函數
clouseTest();
print(outter[0].invoke());
print(outter[1].invoke());
print(outter[2].invoke());
print(outter[3].invoke());
運作的結果如何呢?0 1 2 3?這是很多人期望的答案,可是事實真的是這樣嗎?馬上運作一下吧,是不是驚呆了?結果居然是4 4 4 4 !
其實,在每次疊代的時候,這樣的語句x.invoke = function(){print(i);}并沒有被執行,隻是建構了一個函數體為”print(i);”的函數對象,如此而已。而當i=4時,疊代停止,外部函數傳回,當再去調用outter[0].invoke()時,i的值依舊為4,是以outter數組中的每一個元素的invoke都傳回i的值:4。如何解決呢,我們可以聲明一個匿名函數,然後馬上執行它。

function clouseTest2(){
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(no){
return function(){
print(no);
}
}(i);
outter.push(x);
}
}
這個例子中,我們為x.invoke指派的時候,先運作一個可以傳回一個函數的函數,然後立即執行之,這樣,x.invoke的每一次疊代器時相當與執行這樣的語句:

//x == 0
x.invoke = function(){print(0);}
//x == 1
x.invoke = function(){print(1);}
//x == 2
x.invoke = function(){print(2);}
//x == 3
x.invoke = function(){print(3);}
這樣就可以得到正确的結果了。
可以根據Object.prototype.toString.Call(source)來判斷給定對象的類型。另外還有兩個地方需要注意:
1、如果變量作用域為函數内部則,則外部無法通路,如:

var person=function(){
var name="default";
return {
getName:function(){return name;},
setName:function(no){name=no}
}
}();
print(person.name)//undefined
2、引用
引用也是一個比較有意思的主題,JavaScript中的引用始終指向最終的對象,而并非引用本身,我們來看一個例子:

var obj = {};//空對象
var ref = obj;//引用
obj.name = "objectA";
print(ref.name);//ref跟着添加了name屬性
obj = ["one", "two", "three"];//obj指向了另一個對象(數組對象)
print(ref.name);//ref還指向原來的對象
print(obj.length);//3
print(ref.length);//undefined
運作結果:
objectA
3
undefined
obj隻是對一個匿名對象的引用,是以,ref并非指向它,當obj指向另一個數組對象時可以看到,引用ref并未改變,而始終指向那個後來添加了name屬性的"空"對象”{}”。了解這個之後,下面這個例子就不難了:

var obj = {};//建立一個對象,并被obj引用
var ref1 = obj;//ref1引用obj,事實上是引用obj引用的空對象
var ref2 = obj;
obj.func = "function";
print(ref1.func);
print(ref2.func);
聲明一個對象,然後用兩個引用來引用這個對象,然後修改原始的對象,注意這兩步的順序,運作之:
function
根據運作結果我們可以看出,在定義了引用之後,修改原始的那個對象會影響到其引用上,這一點也應該注意。
以上是最近發現的一些易錯的地方,也希望您能将您遇到的易錯問題分享出來,一起學習,共同進步!