天天看點

js進階程式設計(紅寶書)學習筆記五

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版),在此做補充說明。