1. 程式人生 > >javascript權威指南筆記(01)

javascript權威指南筆記(01)

第一、二章

  • JavaScript的5種原始型別:undefined、null、布林值、數字和字串。

  • JavaScript中兩個非常重要的資料型別是物件和陣列。

  • 字元區分大小寫

  • 通過方括號定義陣列元素和通過花括號定義物件屬性名和屬性值之間的對映關係。

  • 貸款計算器demo

第三章

3.1 數字

-9007199254740992~9007199254740992

3.1.1 整型直接量

  • JavaScript不區分整數值和浮點數值,JavaScript中的所有數字均用浮點數值表示。

  • 支援十進位制、十六進位制、不能使用以0為字首的整型直接量(八進位制),es6禁止

3.1.2 浮點型直接量

6.02e23 //6.02*10^23
6.02e-23 //6.02*10^-23 也可是E

3.1.3

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

  • 下溢(underflow)是當運算結果無限接近於零並比JavaScript能表示的最小值還小的時候發生的一種情形。這種情況下,JavaScript將會返回0。當一個負數發生下溢時,JavaScript返回一個特殊的值“負零”。這個值(負零)幾乎和正常的零完全一樣,JavaScript程式設計師很少用到負零。

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

  • 零除以零是沒有意義的,這種整除運算結果也是一個非數字(non-a-number)值,用NaN表示。無窮大除以無窮大,給任意負數作開方運算或者算術運算子與不是數字或無法轉換為數字的運算元一起使用時都將返回NaN。

  • NAN與任何值都不相等,包括她自身,所以當x為NAN時,X!=X(true)
    isFinite():在引數不是NAN,Infinity或者-Inifinity的時候返回true

1/0正無窮大 1/-0負無窮大

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();

getFullYear();
getMonth();
getDate();
getDay();
getHours();

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.4 null undefined

null:沒有值
undefinded:不存在

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語句是非常有幫助的,同樣對理解閉包的概念也至關重要。