因爲之前學過Java,在學習JS時,對JS的數據類型、function和Function的使用概念容易搞混淆,理解不怎麼深刻。遂將這些知識點和概念梳理一遍,以便自己和他人理解。如有錯誤,敬請諒解。
知識點會涉及到:JS數據類型、JS的類型判斷方式、函數創建方式、function與Function的區別、匿名函數的使用、函數特點以及函數重載問題。
Javascript數據類型
- 基本類型:undefined(未定義)、null(空)、number(數字)、string、boolean、symbol(新增的,沒用過)
- 引用類型:object function
- 基本類型即值類型,引用類型即對象類型,和Java概念差不多。
在許多JS學習資料中,都會看見數據類型是以Undefined、Null、String...這種形式,而不是我上面的那種小寫形式。關於這個問題,會進行解釋。
- 這是因爲在JS中,一切基於對象,所有事物都可以看作對象。這意味着在JS中使用的一切都是對象。給一個簡單實例:給一個變量賦值,這個過程沒用到new操作符創建相應對象,但卻可以使用它的包裝對象方法。如下:
<!-- 以字面量形式創建 -->
<script>
var a = 15;
var b = "abcde";
var len = b.length;
var c = b.charAt(0);
document.write("長度:" + len + ", b索引字符:" + c);
//結果:長度:5, b:a
</script>
<!--以對象形式來創建 -->
<script>
var a = new Number(15);
var b = new String("abcde");
document.write(a.length + " , " + b.charAt(0)); //結果和上面一樣,兩者使用方式相同
</script>
上面的實例驗證了:在JS中,直接使用值類型給變量賦值,JS也會把基本類型包裝成相應對象,這時變量就是一個對象,所以才能直接使用方法。和Java相比,Java只有在需要值類型和包裝類需要轉換時纔會自動進行裝箱拆箱,否則嚴格按照本身定義的類型來使用,不會全部轉成對象。所以搞明白JS中的一切都是對象這個概念後,包括變量、函數什麼的都可以看作是對象。
這時再來理解爲什麼數據類型都是大寫而不是小寫就很簡單了。關於上面的基本類型,因爲JS一切爲對象的原則,所以undefined、null、object等就沒有實際使用意義,上面寫出來也只是爲了搞明白它們之間的區別和相關概念。這些原始類型的作用僅僅是在使用 typeof 和 instanceof 用來判斷具體類型,或是作爲返回的字符串,用來表明該類型是什麼,是基本類型還是引用類型,其他地方就用不到了~
所以,數據類型用對象類型來會更符合JS的定義:
Undefined、Null、Number、String、Boolean、Symbol、Object、 Function
- Undefined表示的是一個變量被聲明但是沒有初始化,即沒有賦值。值爲undefined。
- Null則和Java中概念差不多,唯一值是null,表示對象的引用地址爲空。
- 關於Object和Function後面會說到,至於其他沒什麼可說的。
typeof 和 instanceof 的用法
typeof和instanceof都是運算符,它們都可以用來判斷變量類型,但是它們各自的用法和操作對象又有所不同。
typeof用法
typeof 的返回值 和用於判斷的值都是JS的原始類型,就是最上面寫的那些類型。
typeof(var):獲取指定變量的具體類型。
<!-- 使用typeof()來得到變量的原始類型,也可以直接 typeof var -->
<script>
var a = 15;
var b = "abcde";
var c = false;
var d = function(){} //這是一個函數
var e = new Function(); //這是一個函數對象
var f; //沒賦值
document.write("a:" + typeof(a) + "<br>");
document.write("b:" + typeof(b) + "<br>");
document.write("c:" + typeof(c) + "<br>");
document.write("d:" + typeof(d) + "<br>");
document.write("e:" + typeof(e) + "<br>");
document.write("f:" + typeof f +",undef:"+typeof(undef)+ "<br>");
</script>
<!--結果
a:number
b:string
c:boolean
d:function
e:function
f:undefined,undef:undefined -->
typeof var === 'type' 對變量類型進行判斷。
var a = 150;
var b = "abcde";
var c = false;
alert(typeof a === 'number'); //true
alert(typeof b === 'number'); //fasle
alert(typeof c === 'boolean'); //true
alert(typeof d === 'undefined'); //true
使用typeof判斷對象類型變量時,都是返回object。
<script>
//定義兩個構造函數
function cons1(){}
function cons2(){}
var a = new cons1();
var b = new cons2();
alert(typeof a === cons1);//false
alert(typeof b === cons2);//false
alert(typeof a);//object
alert(typeof b);//object
</script>
正因爲typeof對於判斷對象類型具有侷限性,所以判斷對象類型應使用insatanceof運算符。
instanceof用法
object instanceof Object2,instanceof運算符會判斷指定對象類型的prototype
//定義兩個構造函數
function cons1(){}
function cons2(){}
var a = new cons1();
var b = new cons2();
var aa = new Function(); //這是一個函數對象
var arr = []; //字面量創建數組
var arr2 = new Array();//數組對象創建數組
var obj = {name:"傲天", age:19}; //使用字面量創建對象
alert(a instanceof cons1); //返回true
alert(b instanceof cons2); //返回true
alert(aa instanceof Function); //返回true
alert(arr instanceof Object); //返回true
alert(arr instanceof Array); //返回true
alert(obj instanceof Object); //返回true
兩者區別:
typeof用於判斷基本類型,instanceof 用於判斷對象類型。
function和Function區別,創建函數、函數特點
Function與function的區別:
- Function是一個功能完整的對象,作爲JS的內置對象之一。而function只是一個關鍵字,用來創建一個普通函數或對象的構造函數。JS的普通函數都是Function對象的實例,所以函數本身也是一個對象,就像var一樣,只不過這個對象具有可調用特徵而已。
function創建對象和普通函數的區別:
- 如果用function創建構造函數,例如:var a = new function{}() 或者 var b = new Person(); 則a、b就不是普通函數,而是作爲一個真正對象,是Object類型。雖然普通函數也是對象,但一個是Function,作爲函數;一個是Object,作爲對象。 關於這個問題,可以使用instanceof運算符去驗證。
- 本篇不會涉及使用function創建對象的相關知識,這裏僅作了解。
注意:關鍵字function和原始類型的function名稱是相同的,但就像object和Object一樣,兩者沒半毛錢關係。一個是作爲創建函數的關鍵字,一個是用來判斷一個對象(函數)是不是Funtion類型。別搞混了。
創建函數的方式
// 函數聲明:使用function聲明一個函數,再爲其指定一個函數名。
function first(){
alert("函數聲明方式");
}
// 函數表達式:使用function聲明一個函數,但沒有函數名,而是將這個函數賦給一個變量。也叫作匿名函數
var second = function(arg1, arg2){
alert(arg1+"|" + arg2+"\n匿名函數方式")
}
// 使用Function對象構造函數創建函數
var third = new Function(
"a1", "a2", "alert('函數對象,' + a1 + '|' + a2)"
);
調用函數方式可以有如下幾種:
// 1.直接執行方式或採用事件來執行函數
first();
second("ABC", "XYZ");
third("火", "土");
// 2.函數也是對象,所以像變量一樣.把函數名賦給另一個變量,那個變量就指向該函數地址
// 相當於再進行一次封裝。動態添加對象方法時會用到。
var a = first;
var b = second;
a();
b("ABC", "XYZ");
//注意!這種方式是直接執行third()函數,而不是賦值函數名。
var c = third("火", "土");
三者的不同和優缺點:
- 函數聲明在使用之前,就會被加載到作用域中,隨時等待調用。而函數表達式(匿名函數)則是在代碼執行到那一行才被定義,提前使用則會出錯。
first(); //可以執行
function first(){
alert("函數聲明方式");
}
second(15,25); //執行出錯
var second = function(arg1, arg2){
alert(arg1+"|" + arg2+"\n匿名函數方式")
}
- 匿名函數(函數表達式)與Function()構造函數用法很相似,它們的不同點:匿名函數在使用時只被解析一次。而作爲字符串傳遞給Function()構造函數的JS代碼在每次調用構造函數時都要被解析和編譯一次。Function對象的就是通過構造函數動態地創建和編譯一個函數,但最好不要過多使用,因爲用Function對象構造函數定義函數比其他兩種慢多了。
匿名函數
- 匿名函數就是上面提到的函數表達式。匿名函數從字面意義來講,就是沒名字的函數,但它需要有一個依附體,要讓匿名函數能被找到,不然的話沒有意義。匿名函數看起來像Java的匿名內部類,但兩者的使用方式差別巨大。但可以去了解一下,對於使用匿名函數或許有不一樣的理解。
- 匿名函數除了可以賦值給一個var,用變量來調用匿名函數;還能把匿名函數賦給一個事件,作爲事件處理程序,但別的函數方式也能達到這種效果。但匿名函數最特殊的地方是它的調用方式,下面會一一列舉。
// 定義一個匿名函數,依附於一個變量
var first = function(a1, a2){
alert(a1 + "|" + a2 + "\n變量名調用匿名函數")
}
//通過var名調用
first(111, 222);
//依附一個事件,隨着事件觸發而執行
window.onload = function () {
alert("頁面加載完成後觸發事件!");
}
立即執行的匿名函數
//在匿名函數體後面加上(),表示立即執行該函數
var second = function(){
alert("代碼執行到此處,立即執行。無需var()");
}(); //加分號表示執行完成,可加可不加。
//匿名函數有參,則往裏面傳參數
var second = function(a1, a2){
alert(a1 + "|" + a2 + "\n用括號給匿名函數傳入參數,立即執行");
}(555,666);
因爲函數本身也是對象,所以能賦給變量、事件,自然也能賦給一個對象。
//匿名函數賦給anyobj對象,然後anyobj再賦給second變量。這種方式沒啥意義,瞭解原理即可。
var second = {
anyobj:function(msg){
alert(msg);
}("依附一個對象的匿名函數")
}
觀察上述調用方式,它們都需要一個依附體(變量、事件等),然後這些對象會通過()調用匿名函數,這就是立即執行的匿名函數。但能不能換成別的依附體呢?讓匿名函數看起來更像是匿名函數。如下:
//匿名函數放在括號體內
(
function(msg){
alert(msg + "\n匿名函數放在括號內,作爲函數表達式");
}("傳入參數")
);
這種調用方式是:外面括號包含匿名函數,然後調用()來立即執行函數。這種方式爲什麼能調用呢?這就要說到小括號的作用,外面的小括號相當於運算符,而此時匿名函數放在裏面就形成了函數表達式,然後通過()就能調用該匿名函數。
從這個角度來看,我們還能使用其他運算符來執行匿名函數。
//使用一元運算符和匿名函數形成函數表達式
+function(msg){
alert(msg);
}("+")
-function(msg){
alert(msg);
}("-")
!function(msg){
alert(msg);
}("!")
~function(msg){
alert(msg);
}("~")
*function(msg){
alert(msg);
}("*")
關於匿名函數的調用方式就說到這裏,當然還有許多細節沒說到,這裏也僅僅是作爲參考,至於還有沒有其他運算符來執行匿名函數就不過多瞭解,最常用的也就是以上幾種。最後想說,瞭解基本概念後,一定要多寫,寫得多自然也就理解了。
JS函數的特點
- 因爲JS的弱類型特性,函數形參無法指定具體類型,返回類型自然也沒有,return可以返回任何類型。
- JS函數不限制傳遞的參數數量。這表明就算函數的參數列表已確定,照樣可以傳入多個參數。
- JS函數沒有重載,如果有同名函數,後一個會覆蓋前一個。
<script>
// 定義函數,返回傳入的參數值
function getVal(val){
return val;
}
var a = getVal("傳入val值", 200); //傳入2個參數
//只定義了兩個參數a,b 可以是任何類型
function method1(arg1, arg2){
console.log("method1函數值:" + arg1 + "," + arg2);
console.log("a值:" + a);
}
</script>
<!-- 往method1函數中傳入4個參數 -->
<button onclick="method1(1, 'abc', 100, true)">點擊</button>
<!-- 控制檯輸出
method1函數值:1,abc
a值:傳入val值
-->
- 如果傳遞的參數數量少於函數本身定義的形參數量時,之後的參數值都是undefined,因爲沒對其傳值,就和定義了變量卻沒賦值一樣。
- 如果是在強類型語言中,這種問題是沒有的,所以要注意JS的特性。
關於arguments對象
- arguments是每個函數內部都有的內部對象。函數所有接收的實參都由它來存儲管理。取值方式和數組差不多。
<script>
//返回所有傳入的參數
function getArgs(){
var all = "";
for(var i = 0; i < arguments.length; i++){
all += arguments[i] + " ";
}
return all;
}
function show(){
var allArgs = getArgs("我", 150, new Date(), "ABC", 555);
console.log(allArgs);
// 我 150 Wed Sep 13 2017 21:19:53 GMT+0800 (中國標準時間) ABC 555
}
</script>
<button onclick="show()">點擊</button>
通過上面實例,可以看到arguments對象能拿到函數實參,無參函數通過arguments[0] 、argument[1]就能拿到傳入的第一個實參和第二個實參。但這種獲取實參方式不能亂用。如果規定了形參就使用形參,並且不要多傳無用參數。而無參函數就別傳入參數,一是無用,二是容易誤導,代碼寫規範點總是沒錯的。JS函數爲什麼沒重載
- 首先,先了解重載需要具備的條件:方法名相同,但只要形參類型不相同或者形參數量不相同,都會導致方法重載。至於有無返回值則和重載條件無關。這樣就會出現同名但方法不同。
- 在JS中,函數參數數量沒法確定,而且參數類型可以是任意的,如果函數同名,後面的會覆蓋前面的。所以JS函數沒法實現重載。反過來想,如果JS函數可以重載,那就需要確定參數類型或者參數個數,那這樣JS的動態類型就沒實際意義了。但可以通過arguments得到參數長度來實現另類重載。