JavaScript權威指南(第六版) 初讀筆記

JavaScript的5種原始類型:undefined、null、布爾值、數字和字符串。node

JavaScript中兩個很是重要的數據類型是對象和數組。程序員

經過方括號定義數組元素和經過花括號定義對象屬性名和屬性值之間的映射關係。正則表達式

 

3.1 數字

JavaScript不區分整數值和浮點數值,JavaScript中的全部數字均用浮點數值表示。編程

JavaScript中的算術運算在溢出(overflow)、下溢(underflow)或被零整除時不會報錯。當數字運算結果超過了JavaScript所能表示的數字上限(溢出),結果爲一個特殊的無窮大(infinity)值,在JavaScript中以Infinity表示。一樣地,當負數的值超過了JavaScript所能表示的負數範圍,結果爲負無窮大,在JavaScript中以-Infinity表示。無窮大值的行爲特性和咱們所指望的是一致的:基於它們的加、減、乘和除結果仍是無窮大值(固然還保留它們的正負號)。數組

下溢(underflow)是當運算結果無限接近於零並比JavaScript能表示的最小值還小的時候發生的一種情形。這種狀況下,JavaScript將會返回0。當一個負數發生下溢時,JavaScript返回一個特殊的值「負零」。這個值(負零)幾乎和正常的零徹底同樣,JavaScript程序員不多用到負零。瀏覽器

被零整除在JavaScript並不報錯:它只是簡單的返回無窮大(Infinity)或負無窮大(-Infinity)。緩存

但有一個例外,零除以零是沒有意義的,這種整除運算結果也是一個非數字(non-a-number)值,用NaN表示。無窮大除以無窮大,給任意負數做開方運算或者算術運算符與不是數字或沒法轉換爲數字的操做數一塊兒使用時都將返回NaN。安全

 

3.1.4 二進制浮點數和四捨五入錯誤

JavaScript採用IEEE-745浮點數表示法(幾乎全部現代編程語言所採用),這是一種二進制表示法,能夠精確地表示分數,好比 1/2 、 1/8 和 1/1024 。 遺憾的是,咱們經常使用的分數(特別是在金融計算方面)都是十進制分數 1/10 、 1/100 等。二進制浮點數表示法並不能精確表示相似0.1這樣簡單的數字。服務器

JavaScript中的數字具備足夠的精度,並能夠及其近似於0.1。但事實是,數字不能精確表述的確帶來了一些問題。看下這段代碼:數據結構

var x = .3 - .2;
var y = .2 - .1;

x == y;          //  ==> false: 兩值不相等!
x == .1;         //  ==> false; 
y == .1;         //  ==> true;

因爲舍入偏差,0.3和0.2之間的近似差值實際上並不等於0.2和0.1之間的近似差值。這個問題並不僅在JavaScript中才會出現,理解這一點很是重要:在任何使用二進制浮點數的編程語言中都會有這個問題。

 

3.1.5 日期和時間

var date = new Date("2015/4/12");

date.getDay();        // ==> 0, (2015/4/12 是星期天);

書中出現錯誤,0表示星期日,1表示星期一。

 

3.2 文本

字符串(string)是一組由16位值組成的不可變的有序序列。JavaScript中並無表示單個字符的「字符型」。要表示一個16位值,只需將其賦值給字符串變量便可,這個字符串長度爲1。

相似replace()和toUpperCase()方法都返回新字符串,原字符串自己並無發生改變。

在 ECMAScript 5 中,字符串能夠當作只讀數組,除了使用charAt()方法,也可使用方括號來訪問字符串中的單個字符(16位值):

s = "hello, world";
s[0];     // ==> "h"

基於Mozilla的Web瀏覽器(好比Firefox)好久以前就支持這種方式的字符串索引,多數現代瀏覽器(IE除外)也緊跟Mozilla的腳步,在 ECMAScript 5 成型以前就支持了這一特性。

 

3.2.4 模式匹配

JavaScript定義了RegExp()構造函數,用來建立表示文本匹配模式的對象。

儘管RegExp並非語言中的基本數據類型,可是它們依然具備直接量寫法,能夠直接在JavaScript程序中使用。在兩條斜線之間的文本構成了一個正則表達式。第二條斜線以後也能夠跟隨一個或多個字母,用來修飾匹配模式的含義。

 

3.3 布爾值

下面這些值會被轉換成false:

  • undefined
  • null
  • 0
  • -0
  • NaN
  • "" 

全部其餘值,包括全部對象(數組)都會轉換成true。

 

3.6 包裝對象

咱們看到字符串也一樣具備屬性和方法:

var s = "hello world!";
var word = s.substring(s.indexOf(" ")+1, s.length);

字符串既然不是對象,爲何它會有屬性呢? 只要引用了字符串s的屬性,JavaScript就會將字符串值經過調用new String(s)的方式轉換成對象,這個對象繼承了字符串的方法,並被用來處理屬性的引用。一旦屬性引用結束,這個新建立的對象就會銷燬(其實在表現上並不必定建立或銷燬這個臨時對象,然而整個過程看起來是這樣)。

同字符串同樣,數字和布爾值也具備各自的方法:經過Number()和Boolean()構造函數建立一個臨時對象,這些方法的調用均是來自於這個臨時對象。null和undefined沒有包裝對象:訪問它們的屬性會形成一個類型錯誤。

var s = "test";
s.len = 4;
var t = s.len;

當運行這段代碼時,t的值是undefined。第二行代碼建立一個臨時字符串對象,並給其len屬性賦值爲4,隨即銷燬這個對象。第三行經過原始的(沒有被修改過的)字符串值建立一個新字符串對象,嘗試讀取其len屬性,這個屬性天然不存在,表達式求值結果是undefined。這段代碼說明了在讀取字符串、數字和布爾值的屬性值(或方法)的時候,表現得像對象同樣。但若是你試圖給其屬性賦值,則會忽略這個操做:修改只是發生在臨時對象身上,而這個臨時對象並未繼續保留下來。

存取字符串、數字或布爾值的屬性時建立的臨時對象稱作包裝對象,它只是偶爾用來區分字符串值和字符串對象、數字和數值對象以及布爾值和布爾對象。你須要明白字符串、數字和布爾值是有別於對象的。

須要注意的是,可經過String(),Number()或Boolean()構造函數來顯式建立包裝對象:

var s = "test";
var S = new String(s);
typeof(s);        // ==> "string"
typeof(S);       // ==> "object"

typeof能夠檢測給定變量的數據類型,可能的返回值有:

1. 'undefined'     --- 這個值未定義;

2. 'boolean'        --- 這個值是布爾值;

3. 'string'            --- 這個值是字符串;

4. 'number'         --- 這個值是數值;

5. 'object'           --- 這個值是對象或null;

6. 'function'        --- 這個值是函數;

 

3.7 不可變的原始值和可變的對象引用

JavaScript中的原始值(undefined、null、布爾值、數字和字符串)與對象有着根本區別。

原始值是不可更改的:任何方法都沒法更改一個原始值。

對象和原始值不一樣,首先,對象是可變的,它們的值是可修改的;其次,對象的比較並不是值的比較:即便兩個對象包含相同的屬性及相同的值,他們也是不相等的。

咱們一般將對象稱爲引用類型(reference type),以此來和JavaScript的基本類型區分開來。依照術語的叫法,對象值都是引用(reference),對象的比較均是引用的比較:當且僅當它們引用同一基對象時,它們才相等。

 

3.8 類型轉換

var n = 1 - "x";       // ==> NaN:字符串"x"沒法轉換爲數字
n + " objects"          // ==> "NaN objects":NaN轉換爲字符串"NaN"

"=="等於運算符在判斷兩個值是否相等時會作類型轉換,"==="恆等運算符在判斷相等時並未作任何類型轉換。

 

3.8.2 顯式類型轉換

作顯式類型轉換最簡單的方法就是使用Boolean()、Number()、String()或Object()函數。當不經過new運算符調用這些函數時,它們會做爲類型轉換函數進行類型轉換。

Number類定義的toString()方法能夠接收表示轉換基數(radix)的可選參數,若是不指定此參數,轉換規則將是基於十進制。一樣,亦能夠將數字轉換爲其餘進制數,例如

var n = 17;
binary_string = n.toString(2);                // 轉換爲 「10001」
octal_string = "0" + n.toString(8);         // 轉換爲 「021」
hex_string = "0x" + n.toString(16);       // 轉換爲 「0x11」

Number類爲數字到字符串的類型轉換場景定義了三個方法:

  • toFixed()根據小數點後的指定位數將數字轉換爲字符串,它從不使用指數計數法;
  • toExponential()使用指數計數法將數字轉換爲指數形式的字符串;
  • toPrecision()根據指定的有效數字位數將數字轉換爲字符串。若是有效數字的位數少於數字整數部分的位數,則轉換成指數形式。

若是經過Number()轉換函數傳入一個字符串,它會試圖將其轉換爲一個整數或浮點直接量,這個方法只能基於十進制進行轉換,而且不能出現非法的尾隨字符。ParseInt()只解析整數,而ParseFloat()則能夠解析整數和浮點數。若是字符串前綴是"0x"或者"0X",parseInt()將其解釋爲十六進制數,parseInt()和parseFloat()都會跳過任意數量的前導空格,儘量解析更多數值字符,並忽略後面的內容。若是第一個非空格字符是非法的數字直接量,將最終返回NaN;

 

parseInt("3 blind mice");          // ==> 3
parseInt("0xFF");                  // ==> 255
parseIntFloat(".1");               // ==> NaN:整數不能以"."開始

 parseInt()能夠接收第二個可選參數,這個參數指定數字轉換的基數,合法的取值範圍是2~36:

parseInt("11", 2);           // ==> 3
parseInt("077", 10);       // ==> 77

 

3.9 變量聲明

若是未在var聲明語句中給變量指定初始值,那麼雖然聲明瞭這個變量,但在給它存入一個值以前,它的初始值就是undefined;

應當始終使用var來聲明變量。

 

3.10.1 函數做用域和聲明提早

在一些相似C語言的編程語言中,花括號內的每一段代碼都具備各自的做用域,並且變量在聲明它們的代碼段以外是不可見的,咱們稱之爲塊級做用域(block scope),而JavaScript中沒有塊級做用域。JavaScript取而代之地使用了函數做用域(function scope):變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。

JavaScript的函數做用域是指在函數內聲明的全部變量在函數體內始終是可見的。有意思的是,這意味着變量在聲明以前甚至已經可用。JavaScript的這個特性被非正式地稱爲聲明提早(hoisting),即JavaScript函數裏聲明的全部變量(但不涉及到賦值)都被「提早」至函數體的頂部,看一下以下代碼:

var scope = "global";
function f() {
  console.log(scope);        // 輸出 "undefined", 而不是"global"
  var scope = "local";       // 變量在這裏賦初始值,但變量自己在函數體內任何地方均是有定義的
  console.log(scope);        // 輸出 "local"
}

你可能會誤覺得函數中的第一行會輸出"global",由於代碼尚未執行到var語句聲明局部變量的地方。其實否則,因爲函數做用域的特性,局部變量在整個函數體始終是有定義的,也就是說,在函數體內局部變量覆蓋了同名全局變量。儘管如此,只有在程序執行到var語句的時候,局部變量纔會被真正賦值。所以,上述過程等價於:將函數內的變量聲明「提早」至函數體頂部,同時變量初始化留在原來的位置:

function f() {
  var scope;                    // 在函數頂部聲明瞭局部變量
  console.log(scope);       // 變量存在,但其值是"undefined"
  scope = "local";            // 這裏將其初始化並賦值
  console.log(scope);       // 這裏它具備了咱們所指望的值
}

因爲JavaScript沒有塊級做用域,所以一些程序員特地將變量聲明放在函數體頂部。這種作法使得他們的源代碼很是清晰地反映了真實的變量做用域。

 

3.10.2 做爲屬性的變量

當聲明一個JavaScript全局變量時,其實是定義了全局對象的一個屬性。當使用var聲明一個變量時,建立的這個屬性是不可配置的,也就是說這個變量沒法經過delete運算符刪除。若是你沒有使用嚴格模式並給一個未聲明的變量賦值的話,JavaScript會自動建立一個全局變量。以這種方式建立的變量是全局對象的正常的可配置屬性,並能夠刪除它們:

var truevar = 1;               // 聲明一個不可刪除的全局變量
fakevar = 2;                   // 建立全局對象的一個可刪除的屬性
this.fakevar2 = 3;             // 同上
delete truevar;                //  ==> false:變量並無被刪除
delete fakevar;                // ==> true: 變量被刪除
delete this.fakevar2;          // ==> true: 變量被刪除

JavaScript能夠容許使用this關鍵字來引用全局對象,卻沒有方法能夠引用局部變量中存放的對象。這種存放局部變量的對象的特有性質,是一種對咱們不可見的內部實現。

 

3.10.3 做用域鏈

JavaScript是基於詞法做用域的語言:全局變量在程序中始終都是有定義的。局部變量在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。

若是將一個局部變量看作是自定義實現的對象的類型的話,那麼能夠換個角度來解讀變量做用域。每一段JavaScript代碼(全局代碼或函數)都有一個與之相關的做用域鏈(scope chain)。這個做用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼「做用域中」的變量。當JavaScript須要查找變量x的值的時候(這個過程稱做「變量解析」(variable resolution)),它會從鏈中的第一個對象開始查找,若是這個對象有一個名爲x的屬性,則會直接使用這個屬性的值,若是第一個對象中不存在名爲x的屬性,JavaScript會繼續查找鏈上的下一個對象。若是第二個對象依然沒有名爲x的屬性,則會繼續查找下一個對象,以此類推。若是做用域鏈上沒有任何一個對象含有屬性x,那麼認爲這段代碼的做用域鏈上不存在x,並最終拋出一個引用錯誤(ReferenceError)異常。

在JavaScript的最頂層代碼中(也就是不包含在任何函數定義內的代碼),做用域鏈由一個全局對象組成。在不包含嵌套的函數體內,做用域鏈由兩個對象,第一個是定義函數參數和局部變量的對象,第二個是全局對象。在一個嵌套的函數體內,做用域鏈上至少有三個對象。理解對象鏈的建立規則是十分重要的。當定義一個函數時,它實際上保存一個做用域鏈。當調用這個函數時,它建立一個新的對象來存儲它的局部變量,並將這個對象添加至保存的那個做用域鏈上,同時建立一個新的更長的表示函數調用做用域的「鏈」。對於嵌套函數來說,事情變得更加有趣,每次調用外部函數時,內部函數又會從新定義一遍。由於每次調用外部函數的時候,做用域鏈都是不一樣的。內部函數在每次定義的時候都有微妙的差異——在每次調用外部函數時,內部函數的代碼都是相同的,並且關聯這段代碼的做用域鏈也不相同。

做用域鏈的概念對於理解with語句是很是有幫助的,一樣對理解閉包的概念也相當重要。

 

4.8 算術表達式

在JavaScript中,全部的數字都是浮點型的,除法運算的結果也是浮點型的,好比5/2的結果是2.5,而不是2.

對於數字和字符串操做符來講,加號運算符和比較運算符的行爲都有所不一樣,前者更偏心字符串,若是它的其中一個操做數是字符串的話,則進行字符串鏈接操做。而比較運算符則更偏心數字,只有在兩個操做數都是字符串的狀況下,纔會進行字符串的比較。

 

4.9.3 in運算符

in運算符但願它的左操做數是一個字符串或能夠轉換爲字符串,但願它的右操做數是一個對象。若是右側的對象擁有一個名爲左操做數值的屬性名,那麼表達式返回true:

var point = { x:1, y:1 };
"x" in point;              // ==> true
"toString" in point;    // ==> true

var data = [7, 8, 9];
"0" in data;               // ==> true:數組包含元素"0"
3 in data;                  // ==> false: 沒有索引爲3的元素

 

4.9.4 instanceof 運算符

instanceof運算符判斷一個對象是不是一個類的實例.

instanceof運算符但願左操做數是一個對象,右操做數標識對象的類. 若是左側的對象是右側類的實例,則表達式返回true; 不然返回false; 第9章將會講到, JavaScript中對象的類是經過初始化他們的構造函數來定義的. 這樣的話, instanceof的右操做數應當是一個函數.

須要注意的是, 全部的對象都是Object的實例. 若是instanceof的左操做數不是對象的話, instanceof返回false. 若是右操做數不是函數, 則拋出一個類型錯誤異常.

爲了理解instanceof運算符是如何工做的, 必須首先理解"原型鏈"(prototype chain). 爲了計算表達式 o instance of f , JavaScript首先計算f.prototype, 而後在原型鏈中查找o, 若是找到, 那麼o是f(或者f的父類)的一個實例, 表達式返回true. 若是f.prototype不在o的原型鏈中的話, 那麼o就不是f的實例, instanceof返回false;

對象o中存在一個隱藏的成員, 這個成員指向其父類的原型, 若是父類的原型是另一個類的實例的話, 則這個原型對象中也存在一個隱藏對象指向另一個類的原型, 這種鏈條將許多對象或類串接起來, 構成原型鏈. 

 

4.10.1 邏輯與(&&)

在JavaScript中任何但願使用布爾值的地方,表達式和語句都會將其當作真值或假值來對待,所以實際上"&&"並不老是返回true和false,但也並沒有大礙。

&&運算符首先計算左操做數的值,即首先計算「&&」左側的表達式。若是計算結果是假值,那麼整個表達式的結果必定也是假值,所以「&&」這時簡單地返回左操做數的值,而並不會對右操做數進行計算。

反過來說,若是左操做數是真值,那麼整個表達式的結果則依賴於右操做數的值。若是右操做數是真值,那麼整個表達式的值必定是真值;若是右操做數是假值,那麼整個表達式的值必定是假值。所以,當左操做數是真值時,「&&」運算符將計算右操做數的值並將其返回做爲整個表達式的計算結果:

var o = { x:2 };
var p = null;
o && o.x;                // ==> 2
p && p.x;                // ==> null

儘管「&&」能夠按照第二層和第三層的理解進行一些複雜表達式運算,但大多數狀況下,「&&」僅用來對真值和假值作布爾運算。

這個運算符最經常使用的方式是用來從一組備選表達式中選出一個真值表達式:

// 若是max_width已經定義了,直接使用它;不然在preferences對象中查找max_width
// 若是沒有定義它,則使用一個寫死的常量
var max = max_width || preferences.max_width || 500;

這種慣用法一般用在函數體內, 用來給參數提供默認值:

// 將o的成員屬性複製到p中,並返回p 
function copy(o, p) {
  p = p || {};    // 若是向參數p沒有傳入任何對象,則使用一個新建立的對象
}

 

4.10.3 邏輯非(!)

和「&&」與「||」運算符不一樣,「!」運算符首先將其操做數轉換爲布爾值,而後再對布爾值取反。也就是說「!」老是返回true或者false,而且,能夠經過使用兩次邏輯非運算來獲得一個值的等價布爾值:!!x

 

4.12 表達式計算

eval()只有一個參數。若是傳入的參數不是字符串,它直接返回這個參數。若是參數是字符串,它會把字符串當成JavaScript代碼進行編譯(parse),若是編譯失敗則拋出一個語法錯誤(SyntaxError)異常。若是編譯成功,則開始執行這段代碼,並返回字符串中的最後一個表達式或語句的值,若是最後一個表達式或語句沒有值,則最終返回undefined。若是字符串拋出一個異常,這個異常將把該調用傳遞給eval()。

現代JavaScript解釋器進行了大量的代碼分析和優化,而eval()的問題在於,用於動態執行的代碼一般來說是不能分析。通常來說,若是一個函數調用了eval(),那麼解釋器將沒法對這個函數作進一步優化。

 

 

4.13.2 typeof運算符

typeof是一元運算符,放在其單個操做數的前面,操做數能夠是任意類型。返回值爲表示操做數類型的一個字符串。

須要注意的是,typeof運算符能夠帶上圓括號,這讓typeof看起來像一個函數名,而不是一個運算符關鍵字。

 

4.13.3 delete運算符

delete是一元操做符,它用來刪除對象屬性或者數組元素。

var o = { x:1, y:2 };        
delete o.x;
"x" in o;      // ==> false:這個屬性在對象中再也不存在

var a = [1, 2, 3];
delete a[2];        
2 in a;        // ==> false:元素2在數組中已經不存在了
a.length      // ==>3:注意,數組長度並無改變。a[2]="undefined"

 

5.3.2 function

函數聲明語句一般出如今JavaScript代碼的最頂層,也能夠嵌套在其餘函數體內。但在嵌套時,函數聲明只能出如今所嵌套函數的頂部。也就是說,函數定義不能出如今if語句、while循環或其餘任何語句中。

 

5.6.6 try/catch/finally語句

try/catch/finally語句是JavaScript的異常處理機制。其中try從句定義了須要處理的異常所在的代碼塊。catch從句跟隨在try從句以後,當try塊內某處發生了異常時,調用catch內的代碼邏輯。catch從句後跟隨finally塊,後者中放置清理代碼,無論try塊中是否產生異常,finally塊內的邏輯老是會執行。

和普通的變量不一樣,catch子句中的標識符具備塊級做用域,它只在catch語句塊內有定義。

 

5.7.3  "use strict"

"use strict"是ECMAScript 5引入的一條指令。

使用"use strict"指令的目的是說明(腳本或函數中)後續的代碼將會解析爲嚴格代碼(strict code)。若是頂層(不在任何函數內的)代碼使用了"use strict"指令,那麼它們就是嚴格代碼。若是函數體定義所處的代碼是嚴格代碼或者函數體使用了"use strict"指令,那麼函數體的代碼也是嚴格代碼。

嚴格代碼以嚴格模式執行。ECMAScript 5中的嚴格模式是該語言的一個受限制的子集,它修正了語言的重要缺陷,並提供健壯的差錯功能和加強的安全機制。嚴格模式和非嚴格模式之間的區別以下:

  • 在嚴格模式中嚴禁使用with語句。
  • 在嚴格模式中,全部的變量都要先聲明,若是給一個未聲明的變量、函數、函數參數、catch從句參數或全局對象的屬性賦值,將會拋出一個引用錯誤(在非嚴格模式中,這種隱式聲明的全局變量的方法是給全局對象新添加一個新屬性)。
  • 在嚴格模式中,函數(不是方法)中的this值是undefined。(在非嚴格模式中,調用函數的this值老是全局對象)。能夠利用這個特性來判斷JavaScript實現是否支持嚴格模式:

    var hasStrictMode = (function(){ "use strict"; return this==undefined}());

 

for/in語句遍歷一個對象的屬性。

 

第六章 對象

JavaScript對象能夠從一個稱爲原型的對象繼承屬性。對象的方法一般是繼承的屬性。這種"原型式繼承"(prototypal inheritance)是JavaScript的核心特徵。

JavaScript對象是動態的,但他們經常使用來模擬靜態對象以及靜態類型語言中的「結構體」(struct)

對象最多見的用法是建立(create)、設置(set)、查找(query)、刪除(delete)、檢索(test)和枚舉(enumerate)它的屬性。

除了名字和值以外,每一個屬性還有一些與之相關的值,稱爲「屬性特性」(property attribute);

  • 可寫(writable attribute),代表是否能夠設置該屬性的值。
  • 可枚舉(enumerable attribute),代表是否能夠經過for/in循環返回該屬性。
  • 可配置(configurable attribute),代表是否能夠刪除或修改該屬性。

除了包含屬性以外,每一個對象還擁有三個相關的對象特性(object attribute):

  • 對象的原型(prototype)指向另一個對象,本對象的屬性繼承自它的原型對象;
  • 對象的類(class)是一個標識對象類型的字符串。
  • 對象的擴展標記(extensible flag)指明瞭(在ECMScript 5中)是否能夠像該對象添加新屬性。

最後,咱們用下面這些術語來對三類JavaScript對象和兩類屬性做劃分:

  • 內置對象(native object)是由ECMAScript規範定義的對象或類。例如,數組、函數、日期和正則表達式都是內置對象。
  • 數組對象(host object)是由JavaScript解釋器所嵌入的宿主環境(好比Web瀏覽器)定義的。客戶端JavaScript中表示網頁結構的HTMLElement對象均是宿主對象。既然宿主環境定義的方法能夠當成普通的JavaScript函數對象,那麼宿主對象也能夠當成內置對象。
  • 自定義對象(user-defined object)是由運行中的JavaScript代碼建立的對象。
  • 自有屬性(own property)是直接在對象中定義的屬性。
  • 繼承屬性(inherited property)是在對象的原型對象中定義的屬性。

 

6.1 建立對象

能夠經過對象直接量、關鍵字new和(ECMAScript 5中的)Object.create()函數來建立對象。

 

6.1.1 對象直接量

建立對象最簡單的方法就是在JavaScript代碼中使用對象直接量。對象直接量是由若干名/值對組成的映射表,名/值對中間用冒號分隔,名/值對之間用逗號分隔,整個映射表用花括號括起來。屬性名能夠是JavaScript標識符也能夠是字符串直接量。

var book = {
    "main title": "JavaScript",              // 屬性名字裏有空格,必須用字符串表示
    "sub-title": "The Definitive Guide",     // 屬性名字裏有連字符,必須用字符串表示
    "for": "all audiences",                  // "for"是保留字,所以必須用引號
    author: {                                // 注意,這裏的屬性名都沒有引號   
        firstname: "David",
        surname: "Flanagan"
    }
};

 

6.1.2 經過new建立對象

new + 構造函數

 

6.1.3 原型

全部經過對象直接量建立的對象都具備同一個原型對象,並能夠經過JavaScript代碼Object.prototype得到對原型對象的引用。經過關鍵字new和構造函數建立的對象的原型就是構造函數的prototype屬性的值。所以,同使用{}建立對象同樣,經過new Object()建立的對象也繼承自Object.prototype。一樣,經過new Array()建立的對象的原型就是Array.prototype,經過new Date()建立的對象的原型就是Date.prototype。

沒有原型的對象爲數很少,Object.prototype就是其中一個。它不繼承任何屬性。其餘原型對象都是普通對象,普通對象都具備原型。全部的內置構造函數(以及大部分自定義的構造函數)都具備一個繼承自Object.prototype的原型。例如,Date.prototype的屬性繼承自Object.prototype,所以由new Date()建立的Date對象的屬性同時繼承自Date.prototype和Object.prototype。這一系列連接的原型對象就是所謂的「原型鏈」(prototype chain)。

 

6.1.4  Object.create()

ECMAScript 5 定義了一個名爲Object.create()的方法,它建立一個新對象,其中第一個參數是這個對象的原型。Object.create()提供第二個參數,用以對對象的屬性進行進一步的描述。

Object.create()是一個靜態函數,而不是提供給某個對象調用的方法。使用它的方法很簡單,只須傳入所需的原型對象便可:

var o1 = Object.create( { x:1 , y:2} );

能夠經過傳入參數null來建立一個沒有原型的新對象,但經過這種方式建立的對象不會繼承任何東西,甚至不包括基礎方法,好比toString(),也就是說,它將不能和"+"運算符一塊兒正常工做:

var o2 = Object.create(null);       // o2不繼承任何屬性和方法

若是想建立一個普通的空對象,須要傳入Object.prototype:

var o3 = Object.create(Object.prototype);

 

例6-1:經過原型繼承建立一個新對象

// inherit() 返回了一個繼承自原型對象p的屬性的新對象
// 這裏使用ECMAScript 5 中的Object.create()函數(若是存在的話)
// 若是不存在Object.create(),則退化使用其餘方法
function inherit(p) {
    if (p == null) throw TypeError();     // p是一個對象,但不能是null
    if (Object.create)                    // 若是Object.create()存在
        return Object.create(p);          // 直接使用它

    var t = typeof p;                     // 不然進行進一步檢測
    if (t !== "object" && t !== "function")  throw TypeError();
    function f() {};                      // 定義一個空構造函數
    f.prototype = p;                      // 將其原型屬性設置爲p
    return new f();                       // 使用f()建立p的繼承對象
}

 

6.2.2 繼承

屬性賦值操做首先檢查原型鏈,以此斷定是否容許賦值操做。例如,若是o繼承自一個只讀屬性x,那麼賦值操做是不容許的。若是容許屬性賦值操做,它也老是在原始對象上建立屬性或對已有的屬性賦值,而不會去修改原型鏈。在JavaScript中,只有在查詢屬性時纔會體會到繼承的存在,而設置屬性和繼承無關,這是JavaScript的一個重要特性,該特性可讓程序員能夠有選擇的覆蓋(override)繼承的屬性。

 

6.3 刪除屬性

delete運算符只能刪除自有屬性,不能刪除繼承屬性(要刪除繼承屬性必須從定義這個屬性的原型對象上去刪除它,並且這會影響到全部繼承自這個原型的對象)。

delete不能刪除那些可配置性爲false的屬性(儘管能夠刪除不可擴展對象的可配置屬性)。某些內置對象的屬性是不可配置的,好比經過變量聲明和函數聲明建立的全局對象的屬性。

 

6.4 檢測屬性

JavaScript對象能夠看作屬性的集合,咱們常常會檢測集合中成員的所屬關係——判斷某個屬性是否存在於某個對象中。能夠經過in運算符、hasOwnPreperty()和propertyIsEnumerable()方法來完成這個工做,甚至僅經過屬性查詢也能夠作到這一點。

 in運算符的左側是屬性名(字符串),右側是對象。若是對象的自有屬性或繼承屬性中包含這個屬性則返回true;

對象的hasOwnProperty()方法用來檢測給定的名字是不是對象的自有屬性。對於繼承屬性它將返回false;

propertyIsEnumerable()是hasOwnProperty()的加強版,只有檢測到是自有屬性且這個屬性的可枚舉性(enumerable attribute)爲true時它才返回true。某些內置屬性是不可枚舉的。一般由JavaScript代碼建立的屬性都是可枚舉的,除非在EMCAScript 5中使用一個特殊的方法來改變屬性的可枚舉性。

除了使用in運算符以外,另外一種更簡便的方法是使用"!=="判斷一個屬性是不是undefined:

var o = { x:1 };
o.x !== undefined;               // true: o中有屬性x
o.y !== undefined;               // false: o中沒有屬性y
o.toString !== undefined;        // true: o繼承了toString屬性

然而有一種場景只能使用in運算符而不能使用上述屬性訪問的方式。in能夠區分不存在的屬性和存在但值爲undefined的屬性。例以下面的代碼:

var o = { x: undefined };         // 屬性被顯式賦值爲undefined
o.x !== undefined;                // false:屬性存在,但值爲undefined
o.y !== undefined;                // false:屬性不存在
"x" in o;                         // true:屬性存在
"y" in o;                         // false:屬性不存在
delete o.x;                       // 刪除了屬性x
"x" in o;                         // false:屬性再也不存在 

 

6.5 枚舉屬性

除了for/in循環外,ECMAScript 5 定義了兩個用以枚舉屬性名稱的函數。

第一個是Object.keys(),它返回一個數組,這個數組由對象中可枚舉的自有屬性的名稱組成。

ECMAScript 5 中第二個枚舉屬性的函數是Object.getOwnPropertyNames(),它和Object.keys()相似,只是它返回對象的全部自有屬性的名稱,而不只僅是可枚舉的屬性。

 

6.6 屬性getter和setter

咱們知道,對象屬性是由名字、值和一組特性(attribute)構成的。在ECMAScript 5 中,屬性值能夠用一個或兩個方法替代,這兩個方法就是getter和setter。由getter和setter定義的屬性稱作「存取器屬性」(accessor property)。

和數據屬性不一樣,若是屬性同時具備getter和setter方法,那麼它是一個讀/寫屬性。若是它只有getter方法,那麼它是一個只讀屬性。若是它只有setter方法,那麼它是一個只寫屬性(數據屬性中有一些例外),讀取只寫屬性老是返回undefined。

存取器屬性定義爲一個或兩個和屬性同名的函數,這個函數定義沒有使用function關鍵字,而是使用get和(或)set。注意,這裏沒有使用冒號將屬性名和函數體分隔開,但在函數體的結束和下一個方法和數據屬性之間有逗號分隔。

var p = {
    // x和y是普通的可讀寫的數據屬性
    x: 1.0,
    y: 1.0,
  
    // r是可讀寫的存取器屬性,它有getter和setter.
    // 函數體結束後不要忘記帶上逗號
    get r() {
        return Math.sqrt(this.x*this.x + this.y*this.y);
    },
    set r(newvalue) {
        var oldvalue = Math.sqrt(this.x*this.x + this.y*this.y);
        var ratio = newvalue/oldvalue;
        this.x *= ratio;
        this.y *= ratio;
    },
    // theta是隻讀存取器屬性,它只用getter方法
    get theta() {
        return Math.atan2(this.y, this.x);
    }
};

注意在這段代碼中getter和setter裏this關鍵字的用法。JavaScript把這些函數當作對象的方法來調用。也就是說,在函數體內的this指向表示這個點的對象,所以,r屬性的getter方法能夠經過this.x和this.y引用x和y的屬性。

和數據屬性同樣,存取器屬性是能夠繼承的。

// 這個對象產生嚴格自增的序列號
var serialnum = {
    // 這個數據屬性包含下一個序列號
    // $符號暗示這個屬性是一個私有屬性
    $n: 0, // 返回當前值,而後自增
    get next() { return this.$n++; }

    // 給n設置新的值,但只有當它比當前值大時才設置成功
    set next(n) {
        if (n >= this.$n)  this.$n = n;
        else throw "序列號的值不能比當前值小";
    }
};

 

6.7 屬性的特性

除了包含名字和值以外,屬性還包含一些標識它們可寫、可枚舉和可配置的特性。在ECMAScript 3 中沒法設置這些特性,全部經過ECMAScript 3 的程序建立的屬性都是可寫、可枚舉和可配置的,且沒法對這些特性作修改。本節將講述ECMAScript 5 中查詢和設置這些屬性特性的API。這些API對於庫的開發者來講很是重要,由於:

  • 能夠經過這些API給原型對象添加方法,並將它們設置成不可枚舉的,這讓它們看起來更像內置方法。
  • 能夠經過這些API給對象定義不能修改或刪除的屬性,藉此「鎖定」這個對象。

在本節裏,咱們將存取器屬性的getter和setter方法當作是屬性的特性。按照這個邏輯,咱們也能夠把數據屬性的值一樣看作屬性的特性。所以,能夠認爲一個屬性包含一個名字和4個特性。數據屬性的4個特性分別是它的值(value)、可寫性(writable)、可枚舉性(enumerable)和可配置性(configurable)。存取器屬性不具備值(value)特性和可寫性,他們的可寫性是由setter方法存在與否決定的。所以存取器屬性的4個特性是讀取(get)、寫入(set)、可枚舉性和可配置性。

爲了實現屬性特性的查詢和設置操做,ECMAScript 5 中定義了一個名爲「屬性描述符」(property descriptor)的對象,這個對象表明那4個特性。描述符對象的屬性和它們所描述的屬性特性是同名的。所以,數據屬性的描述符對象的屬性有value、writable、enumerable和configurable。存取器屬性的描述符對象則用get屬性和set屬性代替value和writable。其中writable、enumerable和configurable都是布爾值,固然,get屬性和set屬性是函數值。

經過調用Object.getOwnPropertyDescriptor()能夠得到某個對象特定屬性的屬性描述符:

// 返回 { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor( {x:1}, "x" );

// 對於繼承屬性或不存在的屬性,返回undefined
Object.getOwnPropertyDescriptor( {}, "x" );     // undefined

要想得到繼承屬性的特性,須要遍歷原型鏈(Object.getPrototypeOf())。

想要設置屬性的特性,或者想讓新建屬性具備某種特性,則須要調用Object.defineProperty(),傳入要修改的對象、要建立或修改的屬性的名稱以及屬性描述符對象:

var o = {};
// 添加一個不可枚舉的數據屬性x,並賦值爲1
Object.defineProperty(o, "x", { value: 1,
                                writtable: true,
enumerable:
false, configurable: true}) ; // 對屬性x作修改,讓它變爲只讀 Object.defineProperty(o, "x", { writable: false }); // 試圖更改這個屬性的值 o.x = 2; // 操做失敗但不報錯,而在嚴格模式中拋出類型錯誤異常 // 如今將x從數據屬性修改成存取器屬性 Object.defineProperty(o, "x", { get: function() {return 0;} });

若是要同時修改或建立多個屬性,則須要使用Object.defineProperties()。第一個參數是要修改的對象,第二個參數是一個映射表,它包含要新建或修改的屬性的名稱,以及它們的屬性描述符。

完整的規則,任何對Object.defineProperty()或Object.defineProperties()違反規則的使用都會拋出類型錯誤異常:

  • 若是對象是不可擴展的,則能夠編輯已有的自有屬性,但不能給它添加新屬性。
  • 若是屬性是不可配置的,則不能修改它的可配置性和可枚舉性。
  • 若是存取器屬性是不可配置的,則不能修改其getter和setter方法,也不能將它轉換爲數據類型。
  • 若是數據屬性是不可配置的,則不能將它的可寫性從false修改成true,但能夠從true修改成false。
  • 若是屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是能夠修改的(其實是先將它標記爲可寫的,而後修改它的值,最後轉換爲不可寫的)。

 

6.8 對象的三個屬性

每個對象都有與之相關的原型(prototype)、類(class)和可擴展性(extensible attribute)。

 

6.8.1 原型屬性

在ECMAScript 5中,將對象做爲參數傳入Object.getPrototypeOf()能夠查詢它的原型。

isPrototypeOf()方法能夠檢測一個對象是不是另外一個對象的原型。

 

6.8.2 類屬性

對象的類屬性(class attribute)是一個字符串,用以表示對象的類型信息。ECMAScript 3和ECMAScript 5 都未提供設置這個屬性的方法,並只有一種間接的方法能夠查詢它。默認的toString()方法(繼承自Object.prototype)返回了以下這種格式的字符串:

  [ object class ] 

經過內置構造函數(好比Array和Date)建立的對象包含「類屬性」(class attribute),它與構造函數的名稱相匹配。經過對象直接量和Object.create建立的對象的類屬性是「Object」,那些自定義構造函數建立的對象也是同樣,類屬性也是「Object」。

 

6.8.3 可擴展性

對象的可擴展性用來表示是否能夠給對象添加新屬性。全部內置對象和自定義對象都是顯式可擴展的,宿主對象的可擴展性由JavaScript引擎定義。

ECMAScript 5 定義了用來查詢和設置對象可擴展性的函數。經過將對象傳入Object.esExtensible(),來判斷該對象是不是可擴展的。若是想將對象轉換爲不可擴展的,須要調用Object.preventExtensions(),將待轉換的對象做爲參數傳進去。注意,一旦將對象轉換爲不可擴展的,就沒法再將其轉換回可擴展的了。一樣須要注意的是,preventExtensions()隻影響到對象自己的可擴展性。若是給一個不可擴展的對象的原型添加屬性,這個不可擴展的對象一樣會繼承這些新屬性。

可擴展屬性的目的是將對象「鎖定」,以免外界的干擾。對象的可擴展性一般和屬性的可配置性與可寫性配合使用,ECMAScript 5定義的一些函數能夠更方便地設置多種屬性。

Object.seal()和Object.preventExtensions()相似,除了可以將對象設置爲不可擴展的,還能夠將對象的全部自有屬性都設置爲不可配置的。也就是說,不能給這個對象添加新屬性,並且它已有的屬性也不能刪除或配置,不過它已有的可寫屬性依然能夠設置。對於那些已經封閉(sealed)起來的對象是不能解封的。可使用Object.isSealed()來檢測對象是否封閉。

Object.freeze()將更嚴格地鎖定對象——「凍結」(frozen)。除了將對象設置爲不可擴展的和將其屬性設置爲不可配置的以外,還能夠將它自有的全部數據屬性設置爲只讀(writable: false),若是對象的存取器屬性具備setter方法,存取器屬性將不受影響,仍能夠經過給屬性賦值調用他們。使用Object.isFrozen()來檢測對象是否凍結。

Object.preventExtensions()、Object.seal()和Object.freeze()都返回傳入的對象,也就是說,能夠經過函數嵌套的方式調用它們:

// 建立一個封閉對象,包括一個凍結的原型和一個不可枚舉的類型
var o = Object.seal(Object.create(Object.freeze({x:1}),  // 返回的是一個對象
                                  {y:{value:2, writable: true}}));  

 

6.9 序列化對象

對象序列化(serialization)是指將對象的狀態轉換爲字符串,也可將字符串還原爲對象。ECMAScript 5 提供了內置函數JSON.stringify()和JSON.parse()用來序列化和還原JavaScript對象。這些方法都使用了JSON做爲數據交換格式,JSON的全稱是「JavaScript Object Notation」 —— JavaScript對象表示法。

JSON的語法是JavaScript語法的子集,它並不能表示JavaScript裏的全部值。支持對象、數組、字符串、無窮大數字、true、false和null,而且它們能夠序列化和還原。NaN、Infinity和-Infinity序列化的結果是null,日期對象序列化的結果是ISO格式的日期字符串,但JSON.parse()依然保留它們的字符串形態,而不會將它們還原爲原始日期對象。函數、RegExp、Error對象和undefined值不能序列化和還原。JSON.stringify()只能序列化對象可枚舉的自有屬性。對於一個不能序列化的屬性來講,在序列化後的輸出字符串中會將這個屬性省略掉。JSON.stringify()和JSON.parse()均可以接收第二個可選參數,經過傳入須要序列化或還原的屬性列表來定製自定義的序列化或還原操做。

 

6.10 對象方法

本節將對定義在Object.prototype裏的對象方法展開講解。

 

  • toString()方法,返回一個表示調用這個方法的對象值的字符串。
  • toLocaleString()方法,返回一個表示這個對象的本地化字符串。(Date、Number)

 

第7章  數組

JavaScript數組是無類型的:數組元素能夠是任意類型,而且同一個數組中的不一樣元素也可能有不一樣的類型。JavaScript數組是動態的:根據須要他們會增加或縮減,而且在建立數組時無須聲明一個固定的大小或者在數組大小變化時無須從新分配空間。

一般,數組的實現是通過優化的,用數字索引來訪問數組元素通常來講比訪問常規的對象屬性要快不少。數組繼承自Array.prototype中的屬性,它定義了一套豐富的數組操做方法。

 

7.1 建立數組

  • 數組直接量,若是省略數組直接量中的某個值,省略的元素將被賦予undefined值;
  • 使用Array()構造函數

 

7.2 數組元素的讀和寫

使用[]操做符來訪問數組中的一個元素。

請記住,數組是對象的特殊形式。使用方括號訪問數組就像用方括號訪問對象屬性同樣。JavaScript將指定的數字索引值轉換成字符串,而後將其做爲屬性名來使用

事實上數組索引僅僅是對象屬性名的一種特殊類型,這意味着JavaScript數組沒有「越界」錯誤的概念。當試圖查詢任何對象中不存在的屬性時,不會報錯,只會獲得undefined值。

 

7.3 稀疏數組

稀疏數組就是包含從0開始的不連續索引的數組。一般,數組的length屬性值表明數組中元素的個數。若是數組是稀疏的,length屬性值大於元素的個數。

var a = [];          // 建立一個空數組,length = 0;
a[1000] = 0;         // length = 1001; 

 

7.4 數組長度

數組有兩個特殊的行爲。

  • 若是爲一個數組元素賦值,它的索引 i 大於或等於現有數組的長度時,length屬性的值將設置爲 i+1 ;
  • 設置length屬性爲一個小於當前長度的非負整數n時,當前數組中那些索引值大於或等於n的元素將從中刪除;
var a = [1, 2, 3, 4, 5];
console.log(a[4]);         // ==>5
a.length = 0;              // 刪除全部元素,a爲[]
a.length = 5;
console.log(a[4]);         // ==> "undefined"

在ECMAScript 5 中,可使用Object.defineProperty()讓數組的length屬性變爲只讀。

相似地,若是讓一個數組元素不能配置,就不能刪除它。若是不能刪除它,length屬性就不能設置爲小於不可配置元素的索引值。

 

7.5 數組元素的添加和刪除

可使用unshift()方法在數組的首部插入一個元素,而且將其餘元素依次移到更高的索引處。

能夠像刪除對象屬性同樣使用delete運算符來刪除數組元素。刪除數組元素與爲其賦undefined值是相似的(但有一些微妙的區別)。

shift()方法從數組頭部刪除一個元素。同時將全部元素下移到比當前索引低1的地方。

splice()是一個通用的方法來插入、刪除和替換數組元素。他會根據須要修改length屬性並移動元素到更高或更低的索引處

 

7.6 數組遍歷

在嵌套循環或其餘性能很是重要的上下文中,數組的長度應該只查詢一次而非每次循環都要查詢:

for (var i=0, len=keys.length; i<len; i++) {
  // 循環體仍然不變
}

forEach()方法;

 

7.8  數組方法

 

7.8.1  join()

Array.join()方法將數組中全部元素都轉化爲字符串並鏈接在一塊兒,返回最後生成的字符串。能夠指定一個可選的字符串在生成的字符串中來分隔數組中的各個元素。不過不指定分隔符,默認使用逗號。

Array.join()方法是String.split()方法的逆向操做,後者是將字符串分隔成若干塊來建立一個數組。

 

7.8.2  reverse()

Array.reverse()方法將數組中的元素顛倒順序,返回逆序的數組。它採用了替換。

 

7.8.3  sort()

Array.sort()方法將數組中的元素排序並返回排序後的數組。當不帶參數調用sort()時,數組元素以字母表順序排序(若有必要將臨時轉換爲字符串進行比較):

var a = [33, 4, 1111, 222];
a.sort();           // 字母表順序: 111, 222, 33, 4
a.sort( function(a, b) {return a-b;} );   // 數值順序:4, 33, 222, 1111   

注意,這裏使用匿名函數表達式很是方便。既然比較函數只使用一次,就沒有必要給它們命名了。

 

7.8.4 concat()

Array.concat()方法建立並返回一個新數組,它的元素包括調用concat()的原始數組的元素和concat()的每一個參數。

 

7.8.5  slice()

Array.slice()方法返回指定數組的一個片斷或子數組。它的兩個參數分別指定了片斷的開始和結束的位置。返回的數組包含第一個參數指定的位置和全部到但不包含第二個參數指定的位置之間的全部數組元素。若是隻指定一個參數,返回的數組將包含從開始位置到數組結尾的全部元素。如參數中出現負數,它表示相對於數組中最後一個元素的位置。例如,參數-1指定了最後一個元素,而-3指定了倒數第三個元素。注意,slice()不會修改調用的數組。

 

7.8.6  splice()

Array.splice()方法是在數組中插入或刪除元素的通用方法,splice()會修改調用的函數。

splice()的第一個參數指定了插入和(或)刪除的起始位置,第二個參數指定了應該從數組中刪除的元素的個數。若是省略第二個參數,從起始點開始到數組結尾的全部元素都將被刪除。splice()返回一個由刪除元素組成的數組,或者若是沒有刪除元素就返回一個空數組。例如:

var a = [1, 2, 3, 4, 5, 6, 7, 8];
a.splice(4);         // 返回[5, 6, 7, 8];  a是[1, 2, 3, 4];
a.splice(1, 2);     // 返回[2, 3];  a是[1, 4];
a.splice(1, 1);     // 返回[4];  a是[1];

splice()的前兩個參數指定了須要刪除的數組元素。緊隨其後的任意個數的參數指定了須要插入到數組中的元素,從第一個參數指定的位置開始插入。例如:

var a = [1, 2, 3, 4, 5];
a.splice(2, 0, 'a', 'b');            // 返回[];  a是[1, 2, 'a', 'b', 3, 4, 5]
a.splice(2, 2, [1, 2], 3);        // 返回['a', 'b'];  a是[1, 2, [1, 2], 3, 3, 4, 5]

 

 

7.9 ECMAScript 5 中的數組方法

ECMAScript5 定義了9個新的數組方法來遍歷、映射、過濾、檢測、簡化和搜索數組。

大多數方法的第一個參數接收一個函數,而且對數組的每一個元素(或一些)元素調用一次該函數。若是是稀疏數組,對不存在的元素不調用傳遞的函數。在多數狀況下,調用提供的函數使用三個參數:數組元素、元素的索引和數組自己。一般,只須要第一個參數值,能夠忽略後兩個參數。

 

7.9.1  forEach()

forEach()方法從頭至尾遍歷數組,爲每一個元素調用指定的函數。

var data = [1, 2, 3, 4, 5];
var sum = 0;
data.forEach(function(value){ sum+= value;});
console.log(sum);

 

7.9.2  map()

map()方法將調用的數組的每一個元素傳遞給指定的函數,並返回一個數組,它包含該函數的返回值。例如:

a = [1, 2, 3];
b = a.map( function(x){return x*x;} );    // b是[1, 4, 9]

傳遞給map()的函數的調用方式和傳遞給forEach()的函數的調用方式同樣。但傳遞給map()的函數應該有返回值。注意,map()返回的是新數組:它不修改調用的數組。若是是稀疏數組,返回的也是相同方式的稀疏數組:它具備相同的長度,相同的缺失元素;

 

7.9.3 filter()

filter()方法返回的數組元素是調用的數組的一個子集。傳遞的函數是用來邏輯斷定的:該函數返回true或false。若是返回值爲true或能轉化爲true的值,那麼傳遞給斷定函數的元素就是這個子集的成員,它將被添加到一個做爲返回值的數組中。例如:

a = [5, 4, 3, 2, 1];
smallvalues = a.filter( function(x){return x<3;} );    // [2, 1]
everyother = a.filter( function(x, i){ return i%2==0;} );     // [5, 3, 1]

注意,filter()會跳過稀疏數組中缺乏的元素,它的返回數組老是稠密的。爲了壓縮稀疏數組的空缺,代碼以下:

var dense = sparse.filter( function(){return true;} );

甚至,壓縮空缺並刪除undefined和null元素,能夠這樣使用filter():

a = a.filter( function(x){return x!==undefined && x!=null; } );

 

7.9.4  every()和some()

every()方法就像數學中的「全稱量詞」:當前僅當針對數組中的全部元素調用斷定函數都返回true,它才返回true:

a = [1, 2, 3, 4, 5];
a.every( function(x){return x<10;} );      // ==> true;
a.every( function(x){return x%2===0;} );     // ==>false;

some()方法就像數學中的「存在量詞」:當數組中至少有一個元素調用斷定函數返回true,它就返回true;而且當且僅當數值中的全部元素調用斷定函數都返回false,他才返回false:

a = [1, 2, 3, 4, 5];
a.some( function(x){return x%2===0;} );     // ==>true;
a.some(isNaN);       // ==>false;

注意,一旦every()和some()確認該返回什麼值他們就會中止遍歷數組元素。some()在斷定函數第一次返回true後就返回true,但若是斷定函數一直返回false,它將會遍歷整個數組。every()剛好相反:它在斷定函數第一次返回false後就返回false,但若是斷定函數一直返回true,它將會遍歷整個數組。注意,根據數學上的慣例,在空數組上調用時,every()返回true,some()返回false;

 

7.9.5  reduce()和reduceRight()

reduce()和reduceRight()方法使用指定的函數將數組元素進行組合,生成單個值,也能夠稱爲「注入」和「摺疊」。舉例說明它是如何工做的:

var a = [1, 2, 3, 4, 5];
var sum = a.reduce( function(x,y){return x+y}, 0 );         // 數組求和
var product = a.reduce( function(x,y){return x*y}, 1 );     // 數組求積
var max = a.reduce( function(x,y){return (x>y)?x:y; } );    //求最大值

reduce()須要兩個參數。第一個是執行化簡操做的函數。化簡函數的任務就是用某種方法把兩個值組合或化簡成一個值,並返回化簡後的值。當不指定初始值調用reduce()時,它將使用數組的第一個元素做爲其初始值。

reduceRight()的工做原理和reduce()同樣,不停的是他按照數組索引從高到低處理數組。例如:

var a = [2, 3, 4];
// 計算2^(3^4)。
var big = a.reduceRight(function(accumulator, value){
                           return Math.pow(value, accumulator);      
});

 

7.9.6  indexOf()和lastIndexOf()

indexOf()和lastIndexOf()搜索整個數組中具備給定值的元素,返回找到的第一個元素的索引或者若是沒有找到就返回-1。indexOf()從頭到尾搜索,而lastIndexOf()則反向搜索。

第一個參數是須要搜索的值,第二個參數是可選的:它指定數組中的一個索引,從那裏開始搜索。若是省略該參數,indexOf()從頭開始搜索,而lastIndexOf()從末尾開始搜索。

以下函數在一個數組中搜索指定的值並返回包含全部匹配的數組的索引的一個數組:

function findall(a, x) {
    var results = [],               // 將會返回的數組
          len = a.length,           // 待搜索數組的長度
          pos = 0;                    // 開始搜索的位置
    while (pos < len) {
        pos = a.indexOf(x, pos);
        if (pos == -1)  break;
        results.push(pos);
        pos = pos + 1; 
    }
    return results;
}

 

7.10  數組類型

在ECMAScript 5 中, 可使用Array.isArray()函數來斷定一個未知的對象是否爲數組。

ECMAScript 3 中isArray()函數的代碼能夠這樣書寫:

var isArray = Function.isArray || function(o) {
    return typeof o === "object" && 
           Object.prototype.toString.call(o) === "[object Array]";
};

 

7.12  做爲數組的字符串

在ECMAScript 5 中,字符串的行爲相似於只讀的數組。除了使用charAt()方法來訪問單個的字符之外,還可使用方括號。

字符串的行爲相似於數組的事實使得通用的數組方法能夠應用到字符串上。  

請記住,字符串是不可變值,故當把它們當作數組看待時,它們是隻讀的。如push()、sort()、reverse()和splice等數組方法會修改數組,它們在字符串上是無效的。

 

第8章  函數

JavaScript的函數能夠嵌套在其餘函數中定義,這樣它們就能夠訪問它們被定義時所處的做用域的任何變量。這意味着JavaScript函數構成了一個閉包(closure),它給JavaScript帶來了很是強勁的編程能力。

 

8.1  函數定義

函數聲明語句:

// 計算階乘的遞歸函數
function factorial(x) {
    if (x <= 1) return 1;
    return x * factorial(x-1);
}

函數定義表達式:

data.sort(function(a,b){return a-b;});

注意,以表達式方式定義的函數,函數的名稱是可選的。函數定義表達式特別適合用來定義那些只會用到一次的函數。

函數聲明語句「被提早」到外部腳本或外部函數做用域的頂部。

 

嵌套函數

在JavaScript裏,函數能夠嵌套在其它函數裏。例如:

function hypotenuse(a,b) {
    function square(x) { return x*x; }
    return Math.sqrt(square(a)+square(b));
}

嵌套函數的有趣之處在於它的變量做用域規則:它們能夠訪問嵌套他們的函數的參量和變量。例如,在上面的代碼裏,內部函數square()能夠讀寫外部函數hypotenuse()定義的參數a和b,這些做用域規則對內嵌函數很是重要。

函數聲明語句並不是真正的語句,ECMAScript規範只是容許他們做爲頂級語句。他們能夠出如今全局代碼裏,或者內嵌在其餘函數中,但它們不能出如今循環,條件判斷,或者try/catch/finally以及with語句中。注意,此限制僅適用於以語句聲明形式定義的函數。函數定義表達式能夠出如今JavaScript代碼的任何地方。

 

8.2  函數調用

構成函數主題的JavaScript代碼在定義之時並不會執行,只有調用該函數時,它們纔會執行。有4中方式來調用JavaScript函數:

  • 做爲函數
  • 做爲方法
  • 做爲構造函數
  • 經過它們的call()和apply()方法間接調用

 

8.2.1  函數形式調用

根據ECMAScript 3 和非嚴格的 ECMAScript 5 對函數調用的規定,調用上下文(this的值)是全局對象。然而,在嚴格模式下,調用上下文則是undefined。

以函數形式調用的函數一般不使用this關鍵字。不過,「this」能夠用來判斷當前是不是嚴格模式。

// 定義並調用一個函數來肯定當前腳本運行時是否爲嚴格模式
var strict = (function(){return !this;}());

 

8.2.2 方法形式調用

方法無非就是保存在對象的屬性裏的JavaScript函數。

方法調用中,調用上下文(this的值)爲調用方法的對象。

方法調用可能包含更復雜的屬性訪問表達式:

customer.surname.toUpperCase();      // 調用customer.surname的方法
f().m();                             // 在f()調用結束後繼續調用返回值中的方法m()

方法和this關鍵字是面向對象編程範例的核心。任何函數只要做爲方法調用,實際上都會傳入一個隱式的實參——這個實參就是調用方法的對象。

 

方法鏈

當方法的返回值是一個對象,這個對象還能夠再調用它的方法。這種方法調用序列中每次的調用結果都是另一個表達式的組成部分。

當方法並不須要返回值時,最好直接返回this。若是在設計的API中一直採用這種方式(每一個方法都返回this),使用API就能夠進行「鏈式調用」風格的編程,在這種編程風格中,只要指定一次要調用的對象便可,餘下的方法均可以基於此進行調用:

shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();

 

和變量不一樣,關鍵字this沒有做用域的限制,嵌套的函數不會從調用它的函數中this。若是嵌套函數做爲方法調用,其this的值指向調用它的對象。若是嵌套函數做爲函數調用,其this值不是全局對象(非嚴格模式下)就是undefined(嚴格模式下)。不少人誤覺得調用嵌套函數時this會指向調用外層函數的上下文。若是你想訪問這個外部函數的this值,須要將this的值保存在一個變量裏,這個變量和內部函數都同在一個做用域內。一般使用變量self來保存this,好比:

var o = {                                // 對象o
    m: function() {                      // 對象中的方法m()
        var self = this;                 // 將this的值保存至一個變量中
        console.log(this === o);         // 輸出true,this就是這個對象o
        f();                                    
        
        function f() {                        // 定義一個嵌套函數f()
            console.log(this === o);          // "false":this的值是全局變量或undefined
            console.log(self === o);          // "true":self指外部函數的this值
        }    
    }
};
o.m(); // 調用對象o的方法m()

 

8.2.3  構造函數調用

若是構造函數沒有形參,JavaScript構造函數調用的語法是容許省略實參列表和圓括號的。好比,下面這兩行代碼就是等價的:

var o = new Object();
var o = new Object;

構造函數調用建立一個新的對象,這個對象繼承自構造函數的prototype屬性。構造函數試圖初始化這個新建立的對象,並將這個對象用做其調用上下文,所以構造函數可使用this關鍵字來引用這個新建立的對象。注意,儘管構造函數看起來像一個方法調用,它依然會使用這個新對象做爲調用上下文。

 

8.2.4  間接調用

call()和apply()能夠用來間接地調用函數。

 

8.3.1 可選形參

當調用函數的時候傳入的實參比函數聲明時指定的形參個數要少,剩下的形參都將設置爲undefined值。

// 將對象o中可枚舉的屬性名追加至數組a中,並返回這個數組a
// 若是省略a,則建立一個新數組並返回這個新數組
function getPropertyNames(o, /* optional */ a) {
    if (a === undefined) a = [];    // 若是未定義,則使用新數組
    for (var property in o)  a.push(property);
    return a;
}

var a = getPropertyNames(0);       // 將o的屬性存儲到一個新數組中
getPropertyNames(p, a);            // 將p的屬性追加至數組a中

須要注意的是,當用這種可選實參來實現函數時,須要將可選實參放在實參列表的最後。一樣注意在函數定義中使用註釋 /*optional*/來強調形參是可選的。

 

8.3.2  可變長的實參列表:實參對象

在函數體內,標識符arguments是指向實參對象的引用,實參對象是一個類數組對象,這樣經過數組下標就能訪問傳入函數的實參值。arguments[]對象最適合的應用場景是在這樣一類函數中,這類函數包含固定個數的命名和必須參數,以及隨後個數不定的可選參數。

 

callee和caller屬性

除了數組元素,實參對象還定義了callee和caller屬性。在ECMAScript 5 嚴格模式中,對這兩個屬性的讀寫操做都會產生一個類型錯誤。而在非嚴格模式下,ECMAScript標準規定callee屬性指代當前正在執行的函數。caller是非標準的,但大多數瀏覽器都實現了這個屬性。

callee屬性在某些時候會很是有用,好比在匿名函數中經過callee來遞歸地調用自身。

var factorial = function(x){
    if (x <= 1)  return 1;
    return x*arguments.callee(x-1);
}

 

8.3.3  將對象屬性用做實參

能夠經過名/值對的形式來傳入參數,這樣參數的形式就可有可無了。

爲了實現這種風格的方法調用,定義函數的時候,傳入的實參都寫入一個單獨的對象之中,在調用的時候傳入一個對象,對象中的名/值對是真正須要的實參。

這種寫法容許在函數中設置省略參數的默認值。

// 將原始數組的length元素複製至目標數組
function arraycopy(/*array*/ from, /*index*/ from_start, 
                             /*array*/ to, /*index*/ to_start,
                             /*integer*/ length ){
    // 邏輯代碼
}

// from_start和to_start都默認爲0
function easycopy(args) {
    arraycopy(args.from,
                    args.from_start || 0,    // 注意這裏設置了默認值
                    args.to,
                    args.to_start || 0, args.length );
}

// 來看若是調用easycopy()
var a = [1, 2, 3, 4], b=[];
easycopy( { from:a, to:b, length:4} );

 

自定義函數屬性

JavaScript中的函數並非原始值,而是一種特殊的對象,也就是說,函數能夠擁有屬性。

來看一個例子,下面這個函數使用了自身的屬性(將自身當作數組來對待)來緩存上一次的計算結果:

// 計算階乘,並將結果緩存至函數的屬性中
function factorial(n) {
    if (isFinite(n) && n>0 && n==Math.round(n)) {
        if(!(n in factorial))                          // 若是沒有緩存結果
            factorial[n] = n * factorial(n-1);         // 計算結果並緩存之
        return factorial[n];                           // 返回緩存結果   
    }
    else  return NaN;         // 若是輸入有誤                                
}
factorial[1] = 1;       // 初始化緩存以保存這種基本狀況

 

8.5 做爲命名空間的函數

JavaScript中的函數做用域:在函數中聲明的變量在整個函數體內都是可見的(包括在嵌套的函數中),在函數的外部是不可見的。不在任何函數內聲明的變量時全局變量,在整個JavaScript程序中都是可見的。在JavaScript中是沒法聲明只在一個代碼塊內可見的變量的,基於這個緣由,咱們經常簡單地定義一個函數用做臨時的命名空間,在這個命名空間內定義的變量都不會污染到全局命名空間。

( function() {
    // 模塊代碼
  }() );              // 結束函數定義並當即調用它

注意,function以前的左圓括號是必須的,由於若是不寫這個左圓括號,JavaScript解釋器會試圖將關鍵字function解析爲函數聲明語句。使用圓括號JavaScript解釋器纔會正確地將其解析爲函數表達式。

 

8.6   閉包

和其餘大多數現代編程語言同樣,JavaScript也採用詞法做用域(lexical scoping),也就是說,函數的執行依賴於變量做用域,這個做用域是在函數定義時決定的,而不是函數調用時決定的。爲了實現這種詞法做用域,JavaScript函數對象的內部狀態不只包含函數的代碼邏輯,還必須引用當前的做用域鏈。函數對象能夠經過做用域鏈相互關聯起來,函數體內部的變量均可以保存子函數做用域內,這個特性在計算機科學文獻中稱爲「閉包」。

理解壁報首先要了解嵌套函數的詞法做用域規則。看一下這段代碼:

var scope = "global scope";              // 全局變量
function checkscope() {
    var scope = "local scope";           // 局部變量
    function f() { return scope; }       // 在做用鏈中返回這個值
    return f();
}
checkscope()                             // ==> "local scope"

checkscope()函數聲明瞭一個局部變量,並定義了一個函數f(),函數f()返回了這個變量的值,最後將函數f()的執行結果返回。如今咱們對這段代碼作一點改動:

var scope = "global scope";              // 全局變量
function checkscope() {
    var scope = "local scope";           // 局部變量
    function f() { return scope; }       // 在做用鏈中返回這個值
    return f;
}
checkscope()()                           // 返回值是什麼?

JavaScript函數的執行用到了做用域鏈,這個做用域鏈是函數定義的時候建立的。嵌套的函數f()定義在這個做用域裏,其中的scope必定是局部變量,無論在什麼時候何地執行函數f(),這種綁定在執行f()時依然有效。所以最後一行代碼返回「local scope」,而不是「global scope」。簡言之,閉包的這個特性強大到讓人吃驚:他們能夠捕捉到局部變量(和參數),並一直保存下來,看起來像這些變量綁定到了在其中定義他們的外部函數。

閉包能夠捕捉到單個函數調用的局部變量,並將這些局部變量用做私有狀態。

像counter同樣的私有變量不是隻能用在一個單獨的閉包內,在同一個外部函數內定義的多個嵌套函數也能夠訪問它,這多個嵌套函數都共享一個做用域鏈,看一下這段代碼:

function counter() {
    var n = 0;
    return {
        count: function() { return ++n; },
        reset: function() { n=0; }
    };
}

var c = counter(), d = counter();          // 建立兩個計數器對象
c.count();
console.log(c.count());          // ==> 2
console.log(d.count());          // ==> 1
c.reset();                       // reset()和count()方法共享狀態
console.log(c.count());          // ==> 1:由於咱們重置了c
console.log(d.count());          // ==> 2:d不受影響               

注意,c = counter();  調用count()函數,並把返回值賦給c,c的類型爲使用{}建立的對象,這個對象包含兩個方法屬性——count()和reset();

此外,每次調用counter()都會建立一個新的做用域鏈和一個新的私有變量。

當寫相似這種代碼的時候每每會犯一個錯誤:那就是試圖將循環代碼移入定義這個閉包的函數以內,看一些這段代碼:

// 返回一個函數組成的數組,它們的返回值是0-9
function constfuncs() {
    var funcs = [];
    for (var i=0; i<10; i++)
        funcs[i] = function() { return i; };
    return funcs;
}

var funcs = constfuncs();
funcs[5]()      // 返回值是什麼 ?

上面這段代碼建立了10個閉包,並將他們存儲到一個數組中。這些閉包都是在同一個函數調用中定義的,所以它們能夠共享變量i。當constfuncs()返回時,變量i的值是10,全部的閉包都共享這一個值,所以,數組中的函數的返回值都是同一個值,這不是咱們想要的結果。關聯到閉包的做用域鏈都是「活動的」,記住這一點很是重要。嵌套的函數不會將做用域內的私有成員複製一份,也不會對所綁定的變量生成靜態快照。

書寫閉包的時候還需注意一件事情,this是JavaScript的關鍵字,而不是變量。正如以前討論的,每一個函數調用都包含一個this值,閉包是沒法訪問外部函數中的this的,除非在外部函數中將this轉存爲一個變量:var self = this;

綁定arguments的問題相似。arguments並非一個關鍵字,但在調用每一個函數時都會自動聲明它,因爲閉包具備本身所綁定的arguments,所以閉包內沒法直接訪問外部函數的參數數組,除非外部函數將參數數組保存到另一個變量中:var outerArguments  = arguments;

 

8.7  函數屬性、方法和構造函數

函數是JavaScript中特殊的對象,他們能夠擁有屬性和方法,甚至能夠用Function()構造函數來建立新的函數對象。

 

8.7.1  length屬性

在函數體裏,arguments.length表示傳入函數的實參的個數,而函數自己的length屬性則有不一樣的含義。函數的length屬性是隻讀屬性,它是函數定義時給出的形參的個數。

// 這個函數使用arguments.callee,所以它不能在嚴格模式下工做
function check(args) {
    var actual = args.length;                // 實參的個數
    var expected = args.callee.length;       // 形參的個數
    if (actual !== expected) 
        throw Error("Expected " + expected + " args; got " + actual);
}

function f(x, y, z) {
    check(arguments);      // 檢查實參個數和指望的實參個數是否一致
    return x + y +z;       // 再執行函數的後續邏輯
}

 

8.7.2  prototype屬性

每一個函數都包含一個prototype屬性,這個屬性是指向一個對象的引用。

 

8.7.3  call()方法和apply()方法

咱們能夠將call()和apply()看作是某個對象的方法,經過調用方法的形式來間接調用函數。call()和apply()的第一個實參是要調用函數的母對象,它是調用上下文,在函數體內經過this來得到對它的引用。

call()方法和apply()方法的返回值就是調用函數的返回值。

對於call()來講,第一個調用上下文實參以後的全部參數就是要傳入待調用函數的值(用逗號隔開)。apply()的實參都放入一個數組當中。

 

 

8.7.4  bind()方法

bind()是在ECMAScript 5 中新增的方法,這個方法的主要做用就是將函數綁定至某個對象。當在函數f()上調用bind()方法並傳入一個對象o做爲參數,這個方法將返回一個新的函數。(以函數調用的方式)調用新的函數將會把原始的函數f()當作o的方法來調用。傳入新函數的任何實參都將傳入原始函數,好比:

function f(y) { return this.x+y; }           // 這個是待綁定的函數
var o = { x:1 };                             // 將要綁定的對象
var g = f.bind(o);
g(2)           // ==>3:經過調用g(x)來調用o.f(x)

能夠經過以下代碼輕易地實現這種綁定:

// 返回一個函數,經過調用它來調用o中的方法f(),傳遞它全部的實參
function bind(f, o) {
    if (f.bind)  return f.bind(o);
    else return function() {
        return f.apply(o, arguments);
    }
}

ECMAScript 5 中的bind()方法不只僅將函數綁定至一個對象,它還附帶一些其餘應用,除了第一個實參(要綁定函數的對象)以外,傳入bind()的實參也會綁定至函數的實參,這種附帶的應用是一種常見的函數式編程技術,有時也被稱爲「柯里化」(currying)。

var sum = function(x, y){ return x+y; };
// 把sum綁定到null中,創建一個新函數
// 新函數的第一個參數被綁定爲1, 這個新函數指望只傳入一個實參
var succ = sum.bind(null, 1);
succ(2)                           // ==>3:x綁定到1,並傳入2做爲實參y

function f(y, z) { return this.x+y+z; };
var g = f.bind( {x:1}, 2 );       // 綁定f()到對象{x:1}並綁定y爲2
g(3)          // ==> 6:這裏x=1,y=2,z=3
             
// 例8-5: ECMAScript 3版本的Function.bind()方法
if (!Function.prototype.bind) {
    Function.prototype.bind = function(o /*, args*/ ) {
        // 將this和arguments的值保存至變量中
        var self = this, boundArgs = arguments;
        
        // bind()方法的返回值是一個函數
        return function() {
            // 建立一個實參列表,將傳入bind()的第二個及後續的實參都傳入這個函數
            var args = [], i;
            for (i=1; i<boundArgs.length; i++)  args.push(boundArgs[i]);
            for (i=0; i<arguments.length; i++)  args.push(arguments[i]);
            // 如今將self做爲o的方法調用,傳入這些實參
            return self.apply(o, args);
        };
    };
}

 

8.7.5  toString()方法

ECMAScript規範規定這個方法返回一個字符串,這個字符串和函數聲明語句的語法相關。實際上,大多數(非所有)的toString()方法的實現都返回函數的完整源碼。內置函數每每返回一個相似「[native code]」的字符串做爲函數體。

 

8.8  函數式編程

 

8.8.1  使用函數處理數組

假設有一個數組,數組元素都是數字,咱們想要計算這些元素的平均值和標準差,使用數組方法map()和reduce()來實現極其簡潔。

// 首先定義兩個簡單的函數
var sum = function(x, y) { return x+y; };
var square = function(x) { return x*x; };

// 而後將這些函數和數組方法配合使用計算出平均數和標準差
var data = [1, 1, 3, 5, 5];
var mean = data.reduce(sum)/data.length;
var deviations = data.map(function(x){ return x-mean; });
var stddev = Math.sqrt(deviations.map(square).reduce(sum))/(data.length-1));

 

8.8.2  高階函數

所謂高階函數(high-order function)就是操做函數的函數,它接收一個或多個函數做爲參數,並返回一個新函數,來看這個例子:

function not(f) {
    return function() {                                // 返回一個新的函數
        var result = f.apply(this, argument);          // 調用f()
        return !result;                                // 對結果取反
    }
}

 這裏是一個更常見的例子,它接收兩個參數f()和g(),並返回一個新的函數用以計算f(g()):

// 返回一個新的能夠計算f(g(...))的函數
function compose(f, g) {
    return function() {
        // 須要給f()傳入一個參數,因此使用f()的call()方法
        // 須要給g()傳入不少參數,因此使用g()的apply()方法
        return f.call(this, g.apply(this, arguments)); 
   };
}

var square = function(x) { return x*x; };
var sum = function(x) { return x+y; };
var squareofsum = compose(square, sum);
squareofsum(2, 3)      // ==> 25

 

8.8.3  不徹底函數

function array(a, n) { return Array.prototype.slice.call(a, n||0); }

// 這個函數的實參傳遞至左側
function partialLeft(f /*, ... */ ) {
    var args = arguments;                    // 保存外部的實參數組
    return function() {           
        var a = array(args, 1);              // 開始處理外部的第一個args(第0個args是函數f)
        a = a.concat(array(arguments));      // 而後增長全部的內部參數
        return f.apply(this, a);
    }
}  

// 這個參數的實參傳遞至右側
function partialRight(f /*, ... */ ) {
    var args = arguments;                     // 保存這個外部數組
    return function() {
        var a = array(arguments);             // 從內部參數開始 
        a = a.concat(array(args, 1));         // 而後從外部第1個args開始添加
        return f.apply(this, a);
    };
}

// 這個函數的實參被用做模板
// 實參列表中的undefined值都被填充
function partical(f /*, ... */ ) {
    var args = arguments;
    return function() {
        var a = array(args, 1);
        var i=0, j=0;
        // 遍歷args,從內部實參填充undefined值
        for (; i<a.length; i++) {
            if (a[i]===undefined) {
                a[i] = arguments[j++];
            }
        }
        // 如今將剩下的內部實參都追加進去
        a = a.concat(array(arguments, j));
        return f.apply(this, a);
    };
}

// 這個函數帶有三個參數
var f = function(x, y, z) { return x*(y-z); };
// 注意這三個不徹底調用之間的區別
partialLeft(f, 2)(3, 4)            // 2*(3-4)
partialRight(f, 2)(3, 4)           // 3*(4-2)
partial(f, undefine, 2)(3, 4)      // 3*(2-4)

 

9.1  類和原型

在JavaScript中,類的全部實例對象都從同一個原型對象上繼承屬性。所以,原型對象是類的核心。

 

9.2  類和構造函數

調用構造函數的一個重要特徵是,構造函數的prototype屬性被用做新對象的原型。這意味着經過同一個構造函數建立的全部對象都繼承自一個相同的對象,所以它們都是同一個類的成員。

 

9.5  類和類型

 

9.5.1 instanceof運算符

instanceof運算符,左操做數是待檢測其類的對象,右操做數是定義類的構造函數。若是o繼承自c.prototype,則表達式o instanceof c值爲true。

不經過構造函數做爲中介,可使用isPrototypeOf()方法檢測對象r是否爲範圍類的成員: range.methods.isPrototypeOf(r);          // range.method是原型對象

instanceof運算符和isPrototypeOf()方法的缺點是,咱們沒法經過對象來獲取類名,只能檢測對象是否屬於指定的類名。

 

9.6.1  一個例子:集合類

集合(set)是一種數據結構,用以表示非重複值的無序集合。例子9-6用JavaScript實現了一個更加通用的set類,它實現了從JavaScript值到惟一字符串的映射,而後將字符串用做屬性名。對象和函數都不具有如此簡單可靠的惟一字符串表示,所以集合類必須給集合中的每個對象或函數定義一個惟一的屬性標識。

例9-6:值的任意集合
function Set() {                            // 這是一個構造函數 
    this.values = {};                       // 集合數據保存在對象的屬性裏
    this.add.apply(this, arguments);        // 把全部參數都添加進這個集合
}

// 將每一個參數都添加至集合中
Set.prototype.add = function() {
    for (var i=0; i<arguments.length; i++) {
        var val = arguments[i];                      // 待添加到集合中的值
        var str = Set._v2s(val);                      // 把它轉換爲字符串
        if (!this.value.hasOwnProperty(str)) {    
            this.values[str] = val;
            this.n++;
        }
    }
    return this;                         // 支持鏈式方法調用
};

// 從集合刪除元素,這些元素由參數指定
Set.prototype.remove = function() {
    for (var i=0; i<arguments.length; i++) {
        var str = Set._v2s(arguments[i]);
        if (this.values.hasOwnProperty(str)) {
            delete this.values[str];
            this.n--;
        }
    }
    return this;                         // 支持鏈式方法調用
};

// 若是集合包含這個值,則返回true;不然,返回false;
Set.prototype.contains = function(value) {
    return this.values.hasOwnProperty(Set._v2s(value));
};

// 返回集合的大小
Set.prototype.size = function() {
    return this.n;
};

// 遍歷調用集合中的全部元素,在指定的上下文中調用f
Set.prototype.foreach = function(f, context) {
    for (var s in this.values) {
        if (this.values.hasOwnProperty(s)) {
            f.call(context, this.values[s]);
        }
    }
};

// 這是一個內部函數,用以將任意JavaScript值和惟一的字符串對應起來
Set._v2s = function(val) {
    switch(val) {
        case undefined:  return 'u';
        case null:           return 'n';
        case true:           return 't';
        case false:           return 'f';
        default: switch(typeof val) {
            case 'number':  return '#' + val;             // 數字都帶有#前綴
            case 'string':  return '"' + val;             // 字符串都帶有" 前綴
            default:  return '@' + objectId(val);         // objs and funcs get @
        }
    }

    // 對於任意對象來講,都會返回一個字符串
    // 針對不一樣的對象,這個函數會返回不一樣的字符串
    // 對於同一個對象的屢次調用,老是返回相同的字符串
    function objectId(o) {
        var prop = "|**objectid**|";          // 私有屬性,用以存放id
        if (!o.hasOwnProperty(prop)) {
            o[prop] = Set._v2s.next++;
        }
        return o[prop];
    }
};

Set._v2s.next = 100;        // 設置初始id的值

 

9.6.3  標準轉換方法

最重要的方法首當toString()。這個方法的做用是返回一個能夠表示這個對象的字符串。在但願使用字符串的地方調用對象的話,JavaScript會自動調用這個方法。

toLocaleString()和toString()極爲相似,若是須要爲對象到字符串的轉換定義toString()方法,那麼一樣須要定義toLocaleString()方法用以處理本地化的對象到字符串的轉換。

valueOf()方法用於將對象轉換爲原始值。好比,當數學運算符(除了「+」運算符)和關係運算符做用於數字文本表示的對象時,會自動調用valueOf()方法。

toJSON()方法是由JSON.stringify()自動調用的。JSON格式用於序列化良好的數據結構,並且能夠處理JavaScript原始值、數組和純對象。它和類無關,當對一個對象執行序列化操做時,他會忽略對象的原型和構造函數.

 

 

 

10.2  用於模式匹配的String方法

最簡單的是search()。它的參數是一個正則表達式,返回第一個與之匹配的子串的起始位置,若是找不到匹配的子串,它將返回-1。若是,下面的調用返回值爲4:

"JavaScript".search(/script/i);

若是search()的參數不是正則表達式,則首先會經過RegExp構造函數將它轉換成正則表達式,search()方法不支持全局檢索,由於他忽略正則表達式參數中的修飾符g;

 

13.1  客戶端JavaScript

Window對象是全部客戶端JavaScript特性和API的主要接入點。在客戶端JavaScript中,Window對象也就是全局對象。

 

13.6.2  同源策略(P348)

腳本只能讀取和所屬文檔來源相同的窗口和文檔的屬性。

文檔的來源包含協議、主機以及載入文檔的URL端口。從不一樣Web服務器載入的文檔具備不一樣的來源。經過同一主機的不一樣端口載入的文檔具備不一樣的來源。使用http:協議載入的文檔和使用https:協議載入的文檔具備不一樣的來源,即便它們來自同一個服務器。

第14章  window對象

 

14.1  計時器

setTimeout()和clearTimeout();

setInterval()和clearInterval();

 

14.2 瀏覽器定位和導航

 

14.2.1  解析URL

Window對象的location屬性引用的是Location對象,它表示該窗口中當前顯示的文檔的URL,並定義了方法來使窗口載入新的文檔。

Location對象的href屬性是一個字符串,其包含URL的完整文本。Location對象的toString()方法返回href屬性的值。

其餘屬性——protocol、host、hostname、post、pathname和search,分別表示URL的各個部分。它們稱爲"URL分解"屬性。search()屬性返回問號以後的URL,這部分一般是某種類型的查詢字符串。

 

14.2.2  載入新的文檔

Location對象的assign()方法可使窗口載入並顯示你指定的URL中的文檔。replace()方法在載入新文檔以前會從瀏覽歷史中把當前文檔刪除。reload()方法可讓瀏覽器從新載入當前文檔。

 

14.3  瀏覽歷史

Window對象的history屬性引用的是該窗口的History對象。History對象的back()forward()方法與瀏覽器的「後退」和「前進」按鈕同樣:它們使瀏覽器在瀏覽歷史中先後跳轉一格。第三個方法go()接受一個整數參數,能夠在歷史列表中向前(正參數)或向後(負參數)跳過任意多個頁。

 

14.4  瀏覽器和屏幕信息

 

14.4.1  Navigator對象

Window對象的navigator屬性引用的是包含瀏覽器廠商和版本信息的Navigator對象。

在須要解決存在於某個特定的瀏覽器的特定版本中的特殊的bug時,瀏覽器嗅探仍然有價值。

 

14.4.2  Screen對象

Window對象的screen屬性引用的是Screen對象。它提供有關窗口顯示的大小和可用的顏色數量的信息。屬性widthheight指定的是以像素爲單位的窗口大小。屬性availWidthavailHeight指定的是實際可用的顯示大小,它們排除了像桌面任務欄這樣的特性所佔用的空間。

 

14.5  對話框

Window對象提供了3個方法來向用戶顯示簡單的對話框。alert()向用戶顯示一條消息並等待用戶關閉對話框。confirm()也顯示一條消息,要求用戶單擊「肯定」或「取消」按鈕,並返回一個布爾值。prompt()一樣顯示一條消息,等待用戶輸入字符串,並返回那個字符串。showModalDialog()顯示一個包含HTML格式的「模態對話框」。

 

14.8  多窗口和窗體

一個Web瀏覽器窗口可能在桌面上包含多個標籤頁。每個標籤頁都是獨立的「瀏覽器上下文」(browsing context),每個上下文都有獨立的Window對象,並且互相之間互不干擾。

可是窗口並不老是和其餘窗口徹底不要緊。一個窗口或標籤頁中的腳本能夠打開新的窗口或標籤頁,當一個腳本這樣作時,這樣多個窗口或窗口與另外一個窗口的文檔之間就能夠互操做。

 

14.8.1  打開或關閉窗口

使用Window對象的open()方法能夠打開一個新的瀏覽器窗口(或標籤頁,這一般和瀏覽器的配置選項相關)。Window.open()載入指定的URL到新的或已經存在的窗口中,並返回表明那個窗口的Window對象。它有4個可選的參數:

open()的第一個參數是要在新窗口中顯示的文檔的URL。若是這個參數省略了(也能夠是空字符串),那麼會使用空頁面的URL about:blank ;

open()的第二個參數是新打開的窗口的名字。若是指定的是一個已存在的窗口的名字(而且腳本容許跳轉到那個窗口),會直接使用已存在的窗口。不然,會打開新的窗口,並將這個指定的名字賦值給它。若是省略此參數,會使用指定的名字「_blank」打開一個新的、未命名的窗口。

open()的第三個參數是非標準的,HTML5規範也主張瀏覽器應該忽略它。

open()的返回值是表明命名或新建立的窗口的Window對象。能夠在本身的JavaScript代碼中使用這個Window對象來引用新建立的窗口:

var w = window.open();                                    // 打開一個新的空白窗口
w.alert("About to visit http://example.com");             // 調用alert()方法
w.location = "http://example.com";                        // 設置它的location屬性

在由window.open()方法建立的窗口中,opener屬性引用的是打開它的腳本的Window對象。在其餘窗口中,opener爲null;

 

關閉窗口

方法close()將關閉一個窗口。若是已經建立了一個Window對象w,可使用w.close()將其關掉;  運行在當前的窗口可使用window.close()關閉;

 

第15章  腳本化文檔

每個Web瀏覽器窗口、標籤頁和框架由一個Window對象表示。每一個Window對象有一個document屬性引用了Document對象。Document對象表示窗口的內容,它是本章的主題,儘管如此,Document對象並不是獨立的,它是一個巨大的API的核心對象,叫作文檔對象模型(Document Object Model,DOM),它表明和操做文檔的內容。

 

15.2  選取文檔元素

  • 用指定的id屬性(document.getElementById());
  • 用指定的name屬性(document.getElementsByName());
  • 用指定的標籤名字(document.getElementsByTagName());
  • 用指定的CSS類(document.getElementsByClassName());
  • 匹配指定的CSS選擇器;

 

15.2.2  經過名字選取元素

HTML的name屬性最初打算爲表單元素分配名字,在表單數據提交到服務器時使用該屬性的值。name屬性的值不是必須惟一。在表單中,單選和複選按鈕一般是這樣狀況。並且,name屬性只在少數的HTML元素中有效,包括表單,表單元素,<iframe>和<img>元素。

getElementsByName()定義在HTMLDocument類中,而不在Document類中,因此它只針對HTML文檔可用,在XML文檔中不可用。

 

15.2.3  經過標籤名選取元素

Element類也定義了getElementsByTagName()方法,其原理和Document版本的同樣,可是它只選取調用該方法的元素的後代元素。所以,要查找文檔中第一個<p>元素裏面的全部<span>元素,代碼以下:

var firstpara = document.getElementsByTagName("p")[0];
var firstParaSpans = fristpara.getElementsByTagName("span");

document.body是一個HTML文檔的<body>元素,document.head是<head>元素。

 

15.3.1  做爲節點樹的文檔

Document對象、它的Element對象和文檔中表示文本的Text對象都是Node對象。Node對象定義瞭如下重要屬性:

  • parentNode
  • childNodes
  • firstChild、lastChild
  • nextSibling、previousSibling
  • nodeTyde:節點的類型。9表明Document節點,1表明Element節點,3表明Text節點,8表明Comment節點;
  • nodeValue:text節點或Commend節點的文本內容

 

基於元素的文檔遍歷:

  • firstElementChild、lastElementChild
  • nextElementSibling、previousElementSibling
  • childElementCount

Node類型定義了attributes屬性。針對非Element對象的任何節點,該屬性爲null。對於Element對象,attributes屬性是隻讀的類數組對象,它表明元素的全部屬性。相似NodeLists,attributes對象也是實時的。

 

15.3.2 做爲元素樹的文檔

  將文檔看作是Element對象樹,忽略Text、空白和Comment節點。

  1. children屬性

  2. element屬性

firstElementChild, lastElementChild.

nextElementSibling, previousElementSibling

childElementCount

相關文章
相關標籤/搜索