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 } }
而在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
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
function
根據運行結果我們可以看出,在定義了引用之后,修改原始的那個對象會影響到其引用上,這一點也應該注意。
以上是最近發現的一些易錯的地方,也希望您能將您遇到的易錯問題分享出來,一起學習,共同進步!
