第1章 JavaScript 簡介
JavaScript 具備與瀏覽器窗口及其內容等幾乎所有方面交互的能力。
歐洲計算機制造商協會 ECMA European Computer Manufactures Association
ECMAScript ek-ma-script
一個完整的JavaScript實現應該由下列三個不同的部分組成:
- 核心(ECMAScript),提供核心語言功能
- 文檔對象模型(DOM),提供訪問和操作網頁內容的方法和接口
- 瀏覽器對象模型(BOM),提供與瀏覽器交互的方法和接口
ECMAScript
我們常見的 Web 瀏覽器知識 ECMAScript 實現可能的宿主環境之一,其他宿主環境包括Node(一種服務端JavaScript平臺)和 Adobe Flash。
ECMAScript規定了這門語言的下列組成部分:
- 語法
- 類型
- 語句
- 關鍵字
- 保留字
- 操作符
- 對象
ECMAScript 3.1 成為 ECMA-262 第5版,在2009年發布。
瀏覽器對象模型(BOM)
- 彈出新瀏覽器窗口的功能
- 移動、縮放和關閉瀏覽器窗口的功能
- 提供瀏覽器詳細信息的 navigator 對象
- 提供瀏覽器所加載頁面的詳細信息的 location 對象
- 提供用戶顯示器分辨率詳細信息的 screen 對象
- 對 cookies 的支持
- 像 XMLHttpRequest 和 IE 的 ActiveXObject 這樣的自定義對象
HTML5 致力于把很多 BOM 功能寫入正式規范。
第2章 在 HTML 中使用 JavaScript
向 HTML 頁面中插入 JavaScript 的主要方法,就是使用<script>
元素。HTML 4.01 為<script>
定義了下列6個屬性:
-
async
:可選,異步腳本。表示應該立即下載腳本,但不應妨礙頁面中的其他操作,比如下載其他資源或等待加載其他腳本。只對外部腳本文件有效。 -
charset
:可選。表示通過src
屬性指定的代碼的字符集。由于大多數瀏覽器會忽略它的值,因此這個屬性很少有人用。 -
defer
:可選,延遲腳本。表示腳本可以延遲到文檔被解析和顯示之后再執行。只對外部腳本文件有效。IE7 及更早版本對嵌入腳本也支持這個屬性。也就是說,腳本會被延遲到整個頁面都解析完畢后再運行。 -
language
:已廢棄。大多數瀏覽器會忽略這個屬性,因此也沒有必要再用了。 -
src
:可選。表示包含要執行代碼的外部文件。 -
type
:可選。可以看成是language
的替代屬性;表示編寫代碼使用的腳本語言的內容類型(也成為 MIME 類型)。雖然text/javascript
和text/ecmascript
都已經不被推薦使用,但人們一直以來使用的都還是type/javascript
。實際上,服務器在傳送JavaScript 文件時使用的 MIME 類型通常是application/x-javascript
,但在type
中設置這個值卻可能導致腳本被忽略。這個屬性不是必須的,如果沒有指定這個屬性,其默認值仍為text/javascript
。
轉義字符“\”
使用<script>
元素的方式有兩種:直接在頁面中嵌入 JavaScript 代碼和包含外部 JavaScript 文件。
第一種:
<script type="text/javascript">
function sayHi(){
alert("Hi");
}
</script>
第二種:
<script type="text/javascript" src="example.js"></script>
外部文件example.js
只需包含通常要放在開始的<script>
和結束的</script>
之間的那些 JavaScript 代碼即可。
與解析嵌入式 JavaScript 代碼一樣,在解析外部 JavaScript 文件(包括下載該文件)時,頁面的處理也會暫時停止。
需要注意的是,帶有src
屬性的<script>
元素不應該在其<script>
和</script>
標簽之間再包含額外的JavaScript 代碼。如果包含了嵌入的代碼,則只會下載并執行外部腳本文件,嵌入的代碼會被忽略。
另外,通過<script>
元素的src
屬性還可以包含來自外部域的JavaScript 文件。
引用外部JavaScript 文件的優點
并不存在必須使用外部文件的硬性規定,但支持使用外部文件的人多會強調如下優點:
- 可維護性:遍及不同HTML 頁面的JavaScript 會造成維護問題。但把所有JavaScript 文件都放在一個文件夾中,維護起來就輕松多了。而且開發人員因此能夠在不觸及HTML 標記的情況下,集中精力編輯JavaScript 代碼。
- 可緩存:瀏覽器能夠根據具體的設置緩存鏈接的所有外部JavaScript 文件。也就是說,如果有兩個頁面都使用同一個文件,那么這個文件只需下載一次。因此,最終結果就是能夠加快頁面加載的速度。
- 適應未來
使用<noscript>
元素可以指定在不支持腳本的瀏覽器中顯示的替代內容。但在啟用了腳本的情況下,瀏覽器不會顯示<noscript>
元素中的任何內容。
第3章 基本概念
任何語言的核心都必然會描述這門語言的最基本的工作原理。而描述的內容通常都要涉及這門語言的語法、操作符、數據類型、內置功能等用于構建復雜解決方案的基本概念。
本章將主要按照第3版定義的ECMAScript 介紹這門語言的基本概念,并就第5版的變化給出說明。
ECMAScript 中的一切(變量、函數名和操作符)都區分大小寫。
標識符
所謂標識符,就是指變量、函數、屬性的名字,或者函數的參數。標識符可以是按照下列格式規則組合起來的一或多個字符:
- 第一個字符必須是一個字母、下劃線或一個美元符號
- 其他字符可以是字母、下劃線、美元符號或數字
按照慣例,ECMAScript 標識符采用駝峰大小寫格式,也就是第一個字母小寫,剩下的每個單詞的首字母大寫。
關鍵字和保留字
以下就是ECMAScript 的全部關鍵字:
以下是 ECMA-262 第3版定義的全部保留字:
在實現 ECMAScript 3 的JavaScript 引擎中使用關鍵字做標識符,會導致“Identifier Expected”錯誤。
變量
ECMAScript 的變量是松散類型的,所謂松散類型就是可以用來保存任何類型數據。換句話說,每個變量僅僅是一個用于保存值的占位符而已。定義變量時要使用var
操作符(注意var
是一個關鍵字),后跟變量名(即一個標識符)。
用var
操作符定義的變量將成為定義該變量的作用域中的局部變量。也就是說,如果在函數中使用var
定義一個變量,那么這個變量在函數退出后就會被銷毀。可以省略var
操作符,從而創建一個全局變量。
雖然省略
var
操作符可以定義全局變量,但不推薦這種做法。因為在局部作用域中定義的全局變量很難維護,而且如果有意地忽略了var
操作符,也會由于相應變量不會馬上就有定義而導致不必要的混亂。如未經聲明的變量賦值在嚴格模式下會導致拋出ReferenceError
錯誤。
數據類型
ECMAScript 中有5種簡單數據類型(基本數據類型):Undefinded、Null、Boolean、Number 和String。還有1種復雜數據類型——Object,Object 本質上是由一組無序的名值對組成的。
typeof 操作符
鑒于ECMAScript 是松散類型的,因此需要有一種手段來檢測給定變量的數據類型——typeof 就是負責提供這方面信息的操作符。對一個值使用 typeof 操作符可能返回下列某個字符串:
- ”undefined“——未定義
- ”boolean“——布爾值
- ”string“——字符串
- ”number“——數值
- ”object“——對象或null
- ”function“——函數
注意,typeof 是一個操作符而不是函數,因此可以在其后使用圓括號,但不是必須的。
從技術角度講,函數在ECMAScript 中是對象,不是一種數據類型。然而,函數也確實有一些特殊的屬性,因此通過typeof 操作符來區分函數和其他對象是有必要的。
Undefined 類型
Undefined 類型只有一個值,即特殊的 undefined。在使用var
聲明變量但未對其加以初始化時,這個變量的值就是 undefined
Null 類型
Null 類型只有一個值,即特殊的 null。從邏輯角度來看,null 值表示一個空對象指針,而這也正是使用 typeof 操作符檢測 null 值時會返回”object“的原因。
如果定義的變量準備在將來用于保存對象,那么最好將該變量初始化為null 而不是其他值。
Boolean 類型
Boolean 類型是ECMAScript 中使用最多的一種類型,該類型只有兩個字面值:true 和 false。(區分大小寫)
雖然Boolean 類型的字面值只有兩個,但ECMAScript 中所有類型的值都有與這兩個Boolean 值等價的值。要將一個值轉換為其對應的Boolean 值,可以調用轉型函數Boolean() 。下表給出了各種數據類型及其對應的轉換規則:
這些轉換規則對理解流控制語句自動執行相應的Boolean 轉換非常重要。
Number 類型
最基本的數值字面量格式是十進制整數。
八進制字面值的第一位必須是0
十六進制字面量的前兩位必須是0x
在進行算數運算時,所有以八進制和十六進制表示的數值最終都將被轉換成十進制數值。
浮點數值
所謂浮點數值,就是該數值中必須包含一個小數點,并且小數點后面必須至少有一位數字。由于保存浮點數值需要的內存空間是保存整數值的兩倍,因此ECMAScript 會不失時機地將浮點數值轉換為整數值。
對于那些極大或極小的數值,可以用e 表示法(即科學計數法)表示的浮點數值表示。
浮點數值的最高精讀是17位小數,但在進行算術計算時其精確度遠遠不如整數。例如,0.1加0.2的結果不是0.3,而是0.30000000000000004。因此,永遠不要測試某個特定的浮點數值。
數值范圍
最小數值:Number.MIN_VALUE
最大數值:Number.MAX_VALUE
如果某次計算的結果得到了一個超出JavaScript 數值范圍的值,那么這個數值將會被自動轉換成特殊的Infinity 數值。要想確定一個數值是不是位于最大和最小的數值之間,可以使用isFinite()
函數。這個函數在參數位于最大與最小數值之間時會返回true
NaN
NaN,即非數值(Not a Number)是一個特殊的數值,這個數值用于表示一個本來要返回數值的操作數未返回數值的情況(這樣就不會拋出錯誤了)。例如,在其他編程語言中,任何數值除以非數值都會導致錯誤,從而停止執行代碼。但在ECMAScript 中,任何數值除以非數值會返回NaN ,因此不會影響其他代碼的執行。
NaN 本身有兩個非同尋常的特點:
- 任何涉及NaN的操作(例如NaN/10)都會返回NaN
- NaN與任何值都不相等,包括NaN本身
針對NaN的這兩個特點,ECMAScript 定義了isNaN()
函數。這個函數接受一個參數,該參數可以是任何類型,而函數會幫我們確定這個參數是否”不是數值“。任何不能被轉換為數值的值都會導致這個函數返回true。
isNaN()
也適用于對象。
數值轉換
有三個函數可以把非數值轉換為數值:
- Number()
- parseInt()
- parseFloat()
Number() 函數的轉換規則如下:
- 如果是Boolean 值,true 和 false 將分別被轉換為1和0
- 如果是數字值,只是簡單的傳入和返回
- 如果是null 值,返回0
- 如果是undefined,返回NaN
- 字符串轉換規則如下
- 如果是對象,則調用對象的
valueOf()
方法,然后依照前面的規則轉換返回的值。如果轉換的結果是NaN,則調用對象的toString()
方法,然后再次依照前面的規則轉換返回的字符串值
字符串的Number() 轉換規則:
- 如果字符串中只包含數字,則將其轉換為十進制數值
- 如果字符串中包含有效的浮點格式,將其轉換為對應的浮點數值
- 如果字符串中包含有效的十六進制格式,將其轉換為相同大小的十進制整數格式
- 如果字符串是空的,將其轉換為0
- 如果字符串中包含除上述格式之外的字符,將其轉換為NaN
示例:
一元加操作符的操作與Number() 函數相同
處理整數時更常用的是parseInt()
函數。轉換時,它會忽略字符串前面的空格,直至找到第一個非空格字符。如果第一個字符不是數字字符或者符號,parseInt()
就會返回NaN。也許考慮到八進制和十六進制的問題。示例:
建議提供此函數第二個參數,表示轉換時使用的基數(即多少進制)。多數情況下,我們要解析的都是十進制數值,因此始終將10作為第二個參數是非常有必要的。
與parseInt()
函數類似,parseFloat()
也是從第一個字符開始解析每個字符。不同的是,parseFloat()
只解析十進制數,因此不需要第二個參數。示例:
String 類型
String 類型用于表示由零或多個16位Unicode 字符組成的字符序列,即字符串。可用雙引號或單引號表示。
與PHP中的雙引號和單引號會影響對字符串的解釋方式不同,ECMAScript 中的這兩種方式并沒有什么區別。
字符字面量
String 數據類型包含一些特殊的字符字面量,也叫轉義序列,用于表示非打印字符,或者具有其他用途的字符。
這些字符字面量可以出現在字符串的任意位置,將被作為一個字符來解析。
字符串的特點
ECMAScript 中的字符串是不可變的,也就是說,字符串一旦創建,它們的值就不能改變。要改變某個變量保存的字符串,首先要銷毀原來的字符串,然后再用另一個包含新值的字符串填充該變量。
轉換為字符串
要把一個值轉換為字符串有兩種方式:
- toString()——例如a.toString()
- String()——例如String(a)
區別在于轉換的數值為null 或undefined 的情況下,后者可以轉換為相應的字面量。
要把某個值轉換為字符串,可以使用加號操作符把它與一個字符串加在一起。
Object 類型
ECMAScript 中的對象其實就是一組數據和功能的集合。
在ECMAScript 中,Object 類型是所有它的實例的基礎。換句話說,Object 類型所具有的任何屬性和方法也同樣存在于更具體的對象中。
Object 的每個實例都具有下列屬性和方法:
- constructor:保存著用于創建當前對象的函數。
- hasOwnProperty(propertyName):用于檢查給定的屬性在當前對象實例中(而不是在實例的原型中)是否存在。其中,作為參數的屬性名(propertyName)必須以字符串的形式指定。
- isPrototypeof(object):用于檢查傳入的對象是否是當前對象的原型
- propertyIsEnumerable(propertyName):用于檢查給定的屬性是否能夠使用for-in 語句來枚舉。作為參數的屬性名必須以字符串形式指定。
- toLocaleString():返回對象的字符串表示,該字符串與執行環境的地區對應。
- toString():返回對象的字符串表示。
- valueOf():返回對象的字符串、數值或布爾值表示。通常與toString() 方法的返回值相同。
由于在ECMAScript 中Object 是所有對象的基礎,因此所有對象都具有這些基本的屬性和方法。
操作符
一元操作符
只能操作一個值的操作符叫做一元操作符。一元操作符是ECMAScript 中最簡單的操作符。
遞增和遞減操作符
分為前置型和后置型。
執行前置遞增和遞減操作時,變量的值都是在語句被求值以前改變的。而后置的是在包含它們的語句被求值之后才執行的。
所有這4個操作符對任何值都適用,規則如下:
- 在應用于一個包含有效數字字符的字符串時,先將其轉換為數字值,再執行加減1的操作。字符串變量變成數值變量。
- 在應用于一個不包含有效數字字符的字符串時,將變量的值設置為NaN。字符串變量變成數值變量。
- 在應用于布爾值時,false轉換為0,true轉換為1,之后再執行加減1的操作。布爾值變量變成數值變量。
- 在應用于浮點數值時,執行加減1操作。
- 在應用于對象時,先調用對象的valueOf() 方法以取得一個可供操作的值。然后對該值應用前述規則。如果結果是NaN,則再調用toString() 方法后再應用前述規則。對象變量變為數值變量。
示例:
一元加和減操作符
一元加和減操作符主要用于基本的算術運算。
對非數值應用一元加或減操作符時,該操作符會像Number() 轉型函數一樣對這個值執行轉換。
位操作符
位操作符用在最基本的層次上,即按內存中表示數值的位來操作數值。速度更快
在ECMAScript 中,當對數值應用位操作符時,后臺會發生如下轉換過程:64位的數值被轉換為32位數值,然后執行位操作,最后再將32位的結果轉換回64位數值。
如果對非數值應用位操作符,會先使用Number() 函數將該值轉換為一個數值(自動完成),然后再應用位操作。得到的將是一個數值。
按位非(NOT)
按位非操作符由一個波浪線(~)表示,執行按位非的結果就是返回數值的反碼。示例:
按位非操作的本質:操作數的負值減1
按位與(AND)
按位與操作符由一個和號字符(&)表示,它有兩個操作符數。按位與操作只在兩個數值對應位都是1時才返回1,任何一位是0,結果都是0。
按位或(OR)
按位或操作符由一條豎線符號(|)表示,有兩個操作數。按位或操作在有一個位是1的情況下就返回1,而只有在兩個位都是0的情況下才返回0。
按位異或(XOR)
按位異或操作符由一個插入符號(^)表示,有兩個操作數。這個操作在兩個數值對應位上只有一個1是才返回1,如果對應的兩位都是1或都是0,則返回0。
左移
左移操作符由兩個小于號(<<)表示,這個操作符會將數值的所有位向左移動指定的位數。
注意,左移不會影響操作數的符號位。
有符號的右移
有符號的右移操作符由兩個大于號(>>)表示,這個操作符會將數值向右移動,但保留符號位(即正負號標記)。
在移位的過程中,原數值會出現空位。只不過這次的空位出現在原數值的左側、符號位的右側。而此時ECMAScript 會用符號位的值來填充所有空位,以便得到一個完整的值。
無符號右移
無符號右移操作符由3個大于號(>>>)表示,這個操作符會將數值的所有32位都向右移動,然后以0來填充空位。所以,對正數的無符號右移與有符號右移結果相同,但對負數的結果就不一樣了。
無符號右移操作符會把負數的二進制碼當成正數的二進制碼。而且,由于負數以其絕對值的二進制補碼形式表示,因此就會導致無符號右移后的結果非常之大。
布爾操作符
布爾操作符一共有3個:
- 非(NOT)
- 與(AND)
- 或(OR)
邏輯非
邏輯非操作符由一個嘆號(!)表示。同時使用兩個邏輯非操作符,實際上就會模擬Boolean() 轉型函數的行為。
邏輯與
邏輯與操作符由兩個和號(&&)表示,有兩個操作數。邏輯與操作屬于短路操作,即如果第一個操作數能夠決定結果,那么就不會再對第二個操作數求值。
邏輯或
邏輯或操作符由兩個豎線符號(||)表示,有兩個操作數。也屬于短路操作符。
我們可以利用邏輯或的屬性為變量賦值,以避免null 或 undefined 值。
var myObject = preferredObject || backupObject;
ECMAScript 程序的賦值語句經常會使用這種模式。
乘性操作符
ECMAScript 定義了3個乘性操作符:乘法*、除法/和求模%。如果參與乘性計算的某個操作數不是數值,后臺會先使用Number() 轉型函數將其轉換為數值。也就是說,空字符串會被當作0,布爾值true 將被當作1。
關系操作符
- 小于<
- 大于>
- 小于等于<=
- 大于等于>=
一些規則:
- 如果兩個操作數都是數值,則執行數值比較
- 如果兩個操作數都是字符串,則比較兩個字符串對應的字符編碼值
- 如果一個操作數是數值,則將另一個操作數轉換為一個數值,然后執行數值比較
- 如果一個操作數是對象,則調用這個對象的valueof() 方法,用得到的結果按照前面的規則執行比較。如果對象沒有valueof() 方法,則調用toString() 方法,并用得到的結果根據前面的規則執行比較。
- 如果一個操作數是布爾值,則先將其轉換為數值,然后再執行比較。
相等操作符
相等和不相等
- 相等==
- 不相等!=
這兩個操作符都會先轉換操作數(強制轉型),然后再比較它們的相等性。
在轉換不同的數據類型時,相等和不相等操作符遵循下列基本規則:
- 如果有一個操作數是布爾值,則在比較相等性之前先將其轉換為數值
- 如果一個操作數是字符串,另一個操作數是數值,在比較相等性之前先將字符串轉換為數值
- 如果一個操作數是對象,另一個操作數不是,則調用對象的valueOf() 方法,用得到的基本類型值按照前面的規則進行比較
進行比較時遵循下列規則:
- null 和undefined 是相等的
- 要比較相等性之前,不能將null 和undefined 轉換成其他任何值
- 如果有一個操作數是NaN,則相等操作符返回false,而不相等操作符返回true。重要提示:即使兩個操作數都是NaN,相等操作符也返回false;因為按照規則,NaN不等于NaN。
- 如果兩個操作數都是對象,則比較它們是不是同一個對象。如果兩個操作數都指向同一個對象,則相等操作符返回true;否則,返回false。
下表列出一些特殊情況及比較結果:
全等和不全等
除了在比較之前不轉換操作數之外,全等和不全等操作符沒有什么區別。全等操作符由3個等于號(===)表示,它只在兩個操作數未經轉換就相等的情況下返回true。
不全等操作符由一個嘆號后跟兩個等于號(!==)表示,它在兩個操作數未經轉換就不相等的情況下返回true。
記住:null == undefined 會返回true,因為它們是類似的值;但null === undefined會返回false,因為它們是不同類型的值。
由于相等和不相等操作符存在類型轉換問題,而為了保持代碼中數據類型的完整性,我們推薦使用全等和不全等操作符。
條件操作符
variable = boolean_expression ? true_value : false_value;
例如:
var max = (num1 > num2) ? num1 : num2;
賦值操作符
簡單的賦值操作符由等于號(=)表示,其作用就是把右側的值賦給左側的變量。
如果在等于號(=)前面再添加乘性操作符、加性操作符或位操作符,就可以完成復合賦值操作。
設計復合賦值操作符的主要目的是簡化賦值操作,并不會帶來任何性能的提升。
逗號操作符
使用逗號操作符可以在一條語句中執行多個操作。
語句
if 語句
語法:
if (condition) statement1 else statement2
其中的condition (條件)可以是任意表達式;而且對這個表達式求值的結果不一定是布爾值。ECMAScript 會自動調用Boolean() 轉換函數將這個表達式的結果轉換為一個布爾值。
do-while 語句
do-while 語句是一種后測試循環語句,即只有在循環體中的代碼執行之后,才會測試出口條件。換句話說,在對條件表達式求值之前,循環體內的代碼至少會被執行一次。語法:
do {
statement
} while (expression)
while 語句
while 語句屬于前測試循環語句,也就是說,在循環體內的代碼被執行之前,就會對出口條件求值。因此,循環體內的代碼有可能永遠不會被執行。語法:
while (expression) statement
for 語句
for 語句也是一種前測試循環語句,但它具有在執行循環之前初始化變量和定義循環后要執行的代碼的能力。
for (initialization; expression; post-loop-expression) statement
使用while 循環做不到的,使用 for 循環同樣也做不到。也就是說,for 循環知識把與循環有關的代碼集中在了一個位置。
由于ECMAScript 中不存在塊級作用域,因此在循環內部定義的變量也可以在外部訪問到。
for 語句中的初始化表達式、控制表達式和循環后表達式都是可選的。將這三個表達式全部省略,就會創建一個無限循環。而只給出控制表達式實際上就把for 循環換成了while 循環。
for-in 語句
for-in 語句是一種精準的迭代語句,可以用來枚舉對象的屬性。枚舉的是屬性而非值。
for (property in expression) statement
示例:
for (var propName in window){
document.write(propName);
}
在這個例子中,使用for-in 循環來顯示BOM 中window 對象的所有屬性。
ECMAScript 對象的屬性沒有順序。因此,通過for-in 循環輸出的屬性名的順序是不可預測的。具體來講,所有屬性都會被返回一次,但返回的先后次序可能會因瀏覽器而異。
為了保證最大限度的兼容性,建議在使用for-in 循環之前,先檢測確認該對象的值不是null 或 undefined。
label 語句
使用label 語句可以在代碼中添加標簽,以便將來使用。語法:
label: statement
定義的標簽由break 或 continue 語句引用。加標簽的語句一般都要與for 語句等循環語句配合使用。
break 和 continue 語句
break 和 continue 語句用于在循環中精確地控制代碼的執行。其中,break 語句會立即退出循環,強制繼續執行循環后面的語句。而 continue 語句雖然也是立即退出循環,但退出循環后會從循環的頂部繼續執行。
break 和 continue 語句都可以與 label 語句聯合使用,從而返回代碼中特定的位置。這種聯合使用的情況多發生在循環嵌套的情況下。
with 語句
with 語句的作用是將代碼的作用域設置到一個特定的對象中。語法:
with (expression) statement;
嚴格模式下不允許使用with 語句,否則將視為語法錯誤。
由于大量使用with 語句會導致性能下降,同時也會給調試代碼造成困難,因此在開發大型應用程序時,不建議使用with 語句。
switch 語句
switch (expression){
case value: statement
break;
case value: statement
break;
default: statement
}
ECMAScript 中switch 語句中可以使用任何數據類型,無論是字符串,還是對象都沒有問題。且每個case 的值不一定是常量,可以使變量,甚至是表達式。
switch 語句在比較值時使用的是全等操作符,因此不會發生類型轉換。
函數
ECMAScript 中的函數使用function 關鍵字來聲明,后跟一組參數以及函數體。語法:
function functionName(arg0, arg1,...,argN){
statements
}
函數中,可通過return 語句后跟要返回的值來實現返回值。位于return 語句之后的任何代碼永遠都不會執行。
理解參數
ECMAScript 函數的參數與大多數其他語言中函數的參數有所不同。ECMAScript 函數不介意傳遞進來多少個參數,也不在乎傳進來參數是什么數據類型。也就是說,即便你定義的函數只接收兩個參數,在調用這個函數時也未必一定要傳遞兩個參數。可以傳遞一個、三個甚至不傳遞參數,而解析器永遠不會有什么怨言。之所以會這樣,原因是ECMAScript 中的參數在內部是用一個數組來表示的。函數接收到的始終是這個數組,而不關心數組中包含哪些參數。實際上,在函數體內可以通過arguments 對象來訪問這個參數數組,從而獲得傳遞給函數的每一個參數。
ECMAScript 函數的一個重要特點:命名的參數只提供便利,但不是必需的。
通過訪問arguments 對象的length 屬性可以獲知有多少個參數傳遞給了函數。
arguments 對象可以與命名參數一起使用,且它的值永遠與對應命名參數的值保持同步。
沒有傳遞值的命名參數將自動被賦予undefined 值。
沒有重載
ECMAScript 中定義了兩個名字相同的函數,則該名字只屬于后定義的函數。
通過檢查傳入參數中函數的類型和數量并作出不同的反應,可以模仿方法的重載。
例如:
function doAdd(){
if(arguments.length == 1){
alert(arguments[0] + 10);
} else if(arguments.length == 2){
alert(arguments[0] + arguments[1])
}
}
第4章 變量、作用域和內存問題
基本類型和引用類型的值
ECMAScript 變量可能包含兩種不同數據類型的值:基本類型值和引用類型值。基本類型值指的是簡單的數據段,而引用類型值指那些可能由多個值構成的對象。
動態的屬性
定義基本類型值和引用類型值的方式是類似的:創建一個變量并為該變量賦值。對于引用類型的值,我們可以為其添加屬性和方法,也可以改變和刪除其屬性和方法。而基本類型值不能被添加。
復制變量值
除了保存的方式不同之外,在從一個變量向另一個變量復制基本類型值和引用類型值時,也存在不同。
如果從一個變量向另一個變量復制基本類型的值,會在變量對象上創建一個新值,然后把該值復制到為新變量分配的位置上。示例:
var num1 = 5;
var num2 = num1;
圖示:
當從一個變量向另一個變量復制引用類型的值時,同樣也會將存儲在變量對象中的值復制一份放到為新變量分配的空間中。不同的是,這個值的副本實際上是一個指針,而這個指針指向存儲在堆中的另一個對象。復制操作結束后,兩個變量實際上將引用同一個對象。因此,改變其中一個變量,就會影響另一個變量,示例如下:
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"
圖示:
傳遞參數
ECMAScript 中所有函數的參數都是按值傳遞的。也就是說,把函數外部的值復制給函數內部的參數,就和把值從一個變量復制到另一個變量一樣。基本類型值的傳遞如同基本類型變量的復制一樣,而引用類型值的傳遞,則如同引用類型變量的復制一樣。
在向參數傳遞基本類型的值時,被傳遞的值會被復制給一個局部變量(即命名參數,或者用ECMAScript 的概念來說,就是arguments 對象的一個元素)。在向參數傳遞引用類型的值時,會把這個值在內存中的地址復制給一個局部變量,因此這個局部變量的變化會反映在函數的外部。
檢測類型
typeof 操作符是確定一個變量是字符串、數值、布爾值,還是undefined 的最佳工具。如果變量的值是一個對象或null,則typeof 操作符會返回”object“。
檢測是什么類型的對象,可以使用instanceof 操作符,語法如下:
result = variable instanceof constructor
如果變量是給定引用類型的實例,那么instanceof 操作符就會返回true。
根據規定,所有引用類型的值都是Object 的實例。因此,在檢測一個引用類型值和Object 構造函數時,instanceof 操作符始終會返回true。當然,如果使用instanceof 操作符檢測基本類型的值,則該操作符會返回false,因為基本類型不是對象。
小結
- 基本類型值在內存中占據固定大小的空間,因此被保存在棧內存中
- 從一個變量向另一個變量復制基本類型的值,會創建這個值的一個副本
- 引用類型的值是對象,保存在堆內存中
- 包含引用類型值的變量實際上包含的并不是對象本身,而是一個指向該對象的指針
- 從一個變量向另一個變量復制引用類型的值,復制的其實是指針,因此兩個變量最終都指向同一個對象
- 確定一個值是哪種基本類型可以使用typeof 操作符,而確定一個值是哪種引用類型可以使用instanceof 操作符。
執行環境及作用域
執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在后臺使用它。
全局執行環境是最外圍的一個執行環境。在Web 瀏覽器中,全局執行環境被認為是window 對象,因此所有全局變量和函數都是作為window 對象的屬性和方法創建的。
每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之后,棧將其環境彈出,把控制權返回給之前的執行環境。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。作用域鏈的用途,是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。如果這個環境是函數,則將其活動對象作為變量對象。活動對象在最開始時只包含一個變量,即arguments 對象(這個對象在全局環境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是作用域鏈中的最后一個對象。
標識符解析是沿著作用域鏈一級一級地搜索標識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標識符為止(如果找不到標識符,通常會導致錯誤發生)。
內部環境可以通過作用域鏈訪問所有的外部環境,但外部環境不能訪問內部環境中的任何變量和函數。這些環境之間的聯系是線性、有次序的。每個環境都可以向上搜索作用域鏈,以查詢變量和函數名;但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。
函數參數也被當做變量來對待,因此其訪問規則與執行環境中的其他變量相同。
延長作用域鏈
當執行流進入下列任何一個語句時,作用域鏈就會得到加長:
- try-catch 語句的catch 塊
- with 語句
這兩個語句都會在作用域鏈的前端添加一個變量對象。對with 語句來說,會將指定的對象添加到作用域鏈中。對catch 語句來說,會創建一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
沒有塊級作用域
使用var 聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境;在with 語句中,最接近的環境是函數環境。如果初始化變量時沒有使用var 聲明,該變量會自動被添加到全局環境。
當在某個環境中為了讀取或寫入而引用一個標識符時,必須通過搜索來確定該標識符實際代表什么。搜索過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。如果在局部環境中找到了該標識符,搜索過程停止,變量就緒。如果在局部環境中沒有找到該變量名,則繼續沿作用域鏈向上搜索。搜索過程將一直追溯到全局環境的變量對象。如果在全局環境中也沒有找到這個標識符,則意味著該變量尚未聲明。
在這個搜索過程中,如果存在一個局部的變量的定義,則搜索會自動停止,不再進入另一個變量對象。換句話說,如果局部環境中存在著同名標識符,就不會使用位于父環境中的標識符。
如果在局部環境中需要訪問同名的全局變量,需添加window
。
小結
- 執行環境有全局執行環境(也稱為全局環境)和函數執行環境之分
- 每次進入一個新執行環境,都會創建一個用于搜索變量和函數的作用域鏈
- 函數的局部環境不僅有權訪問函數作用域中的變量,而且有權訪問其包含(父)環境,乃至全局環境
- 全局環境只能訪問在全局環境中定義的變量和函數,而不能直接訪問局部環境中的任何數據
- 變量的執行環境有助于確定應該何時釋放內存
垃圾收集
JavaScript 具有自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程中使用的內存。
- 離開作用域的值將被自動標記為可以回收,因此將在垃圾收集期間被刪除。
- ”標記清除“是目前主流的垃圾收集算法,這種算法的思想是給當前不使用的值加上標記,然后再回收其內存。
- 另一種垃圾收集算法是”引用計數“,這種算法的思想是跟蹤記錄所有值被引用的次數。JavaScript 引擎目前都不再使用這種算法。
- 在代碼中存在循環引用現象時,”引用計數“算法就會導致問題。
- 解除變量的引用不僅有助于消除循環引用現象,而且對垃圾收集也有好處。為了確保有效地回收內存,應該及時解除不再使用的全局對象、全局對象屬性以及循環應用變量的引用。
確保占用最少的內存可以讓頁面獲得更好的性能。而優化內存占用的最佳方式,就是為執行中的代碼只保存必要的數據。一旦數據不再有用,最好通過將其設置為null 來釋放其引用——這個做法叫做解除引用。這一做法適用于大多數全局變量和全局對象的屬性。局部變量會在它們離開執行環境時自動被解除引用。
不過,解除一個值的引用并不意味著自動回收該值所占用的內存。解除引用的真正作用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。