Function 類型
每個函數都是 Function 類型的執行個體,而且都與其他引用類型一樣具有屬性和方法。由于函數是對象,是以函數名實際上也是一個指向函數對象的指針。
由于函數名僅僅是指向函數的指針,是以函數名與包含對象指針的其他變量沒有什麼不同。換句話說,一個函數可能會有多個名字,如下面的例子所示:
function sum(num1, num2){
return num1 + num2;
}
alert(sum(,)); //20
var anotherSum = sum;
alert(anotherSum(,)); //20
sum = null;
alert(anotherSum(,)); //20
函數聲明與函數表達式
函數聲明和函數表達式雖然都可以随一個函數進行聲明,但是解析器在加載資料時還是有着差別的。解析器會率先讀取函數聲明,并使其在執行任何代碼之前可用(可以通路);至于函數表達式,則必須等到解析器執行到它所在的代碼行,才會真
正被解釋執行。請看下面的例子:
alert(sum(,));
function sum(num1, num2){
return num1 + num2;
}
以上代碼完全可以正常運作。因為在代碼開始執行之前,解析器就已經通過一個名為函數聲明提升(function declaration hoisting)的過程,讀取并将函數聲明添加到執行環境中。對代碼求值時,JavaScript引擎在第一遍會聲明函數并将它們放到源代碼樹的頂部。是以,即使聲明函數的代碼在調用它的代碼後面,JavaScript 引擎也能把函數聲明提升到頂部。如果像下面例子所示的,把上面的函數聲明改為等價的函數表達式,就會在執行期間導緻錯誤。
alert(sum(,));
var sum = function(num1, num2){
return num1 + num2;
};
除了什麼時候可以通過變量通路函數這一點差別之外,函數聲明與函數表達式的文法其實是等價的。
函數内部屬性
在函數内部,有兩個特殊的對象:arguments 和 this。arguments 的主要用途是儲存函數參數,但這個對象還有一個名叫 callee 的屬性,該屬性是一個指針,指向擁有這個 arguments 對象的函數。請看下面這個非常經典的階乘函數:
function factorial(num){
if (num <=) {
return ;
} else {
return num * factorial(num-)
}
}
定義階乘函數一般都要用到遞歸算法;如上面的代碼所示,在函數有名字,而且名字以後也不會變的情況下,這樣定義沒有問題。但問題是這個函數的執行與函數名 factorial 緊緊耦合在了一起。為了消除這種緊密耦合的現象,可以像下面這樣使用 arguments.callee。
function factorial(num){
if (num <=) {
return ;
} else {
return num * arguments.callee(num-)
}
}
this引用的是函資料以執行的環境對象——或者也可以說是 this 值(當在網頁的全局作用域中調用函數時,this 對象引用的就是 window)。來看下面的例子:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"
函數的名字僅僅是一個包含指針的變量而已。是以,即使是在不同的環境中執行,全局的 sayColor()函數與 o.sayColor()指向的仍然是同一個函數
ECMAScript 5 也規範化了另一個函數對象的屬性:caller。這個屬性中儲存着調用目前函數的函數的引用,如果是在全局作用域中調用目前函數,它的值為 null。例如:
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
以上代碼會導緻警告框中顯示 outer()函數的源代碼。因為 outer()調用了 inter(),是以inner.caller 就指向 outer()。為了實作更松散的耦合,也可以通過 arguments.callee.caller來通路相同的資訊:
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
函數屬性和方法
每個函數都有兩個非繼承來而來的方法,
apply() 和 call()
。這兩個方法在初學js的時候被繞的迷迷糊糊,而且在面試中也是經常會被問到,下面就詳細說一下這兩個函數的作用以及差別。
用途:這兩個方法的用途都是在特定的作用域中調用函數,實際上等于設定函數體内this對象的值,說白了就是改變函數體内部this的指向
差別:差別僅僅就在于接收參數的方式不同
具體的差別請參考這兩篇博文:
https://blog.csdn.net/ganyingxie123456/article/details/70855586
https://www.cnblogs.com/faithZZZ/p/6999327.html
這兩位大神的講解通俗易懂,要比紅寶書上的講解更加明了。
基本包裝類型
為了便于操作基本類型值,ECMAScript 還提供了 3 個特殊的引用類型:Boolean、Number 和String。這些類型與本章介紹的其他引用類型相似,但同時也具有與各自的基本類型相應的特殊行為。實際上,每當讀取一個基本類型值的時候,背景就會建立一個對應的基本包裝類型的對象,進而讓我們能夠調用一些方法來操作這些資料。
var s1 = "some text";
var s2 = s1.substring();
如上例子,s1是字元串,字元串當然是基本類型值,基本類型值不是對象,因而從邏輯上講它們不應該有方法(盡管如我們所願,它們确實有方法)。其實,為了讓我們實作這種直覺的操作,背景已經自動完成了一系列的處理。當第二行代碼通路 s1 時,通路過程處于一種讀取模式,也就是要從記憶體中讀取這個字元串的值。而在讀取模式中通路字元串時,背景都會自動完成下列處理:
(1) 建立 String 類型的一個執行個體;
(2) 在執行個體上調用指定的方法;
(3) 銷毀這個執行個體。
可以将以上三個步驟想象成是執行了下列 ECMAScript 代碼。
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
引用類型與基本包裝類型的主要差別就是對象的生存期。使用 new 操作符建立的引用類型的執行個體,在執行流離開目前作用域之前都一直儲存在記憶體中。而自動建立的基本包裝類型的對象,則隻存在于一行代碼的執行瞬間,然後立即被銷毀。這意味着我們不能在運作時為基本類型值添加屬性和方法。來看下面的例子:
var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined
對基本包裝類型的執行個體調用 typeof 會傳回"object",而且所有基本包裝類型的對象都會被轉換為布爾值 true
var obj = new Object("some text");
alert(obj instanceof String); //true
要注意的是,使用 new 調用基本包裝類型的構造函數,與直接調用同名的轉型函數是不一樣的。例如:
var value = "25";
var number = Number(value); //轉型函數
alert(typeof number); //"number"
var obj = new Number(value); //構造函數
alert(typeof obj); //"object"
注:部落格中所引用的例子或者語句有的來自書中原文(JavaScript進階程式設計 第3版),在此做補充說明。