1. 程式人生 > >Meteor學習筆記之三——《JavaScript程式設計全解》讀書筆記

Meteor學習筆記之三——《JavaScript程式設計全解》讀書筆記

Micheal力薦的JS教程,寫的還是挺不錯的,記錄一些有用的東西吧

比較時的注意事項

前面提到多次的一點是,在比較時注意比較的是物件還是值,舉個例子

var sobj1 = new String('abc');
var sobj2 = new String('abc');
sobj1 === sobj2; // false, 雖然字串的內容相同,但是並非引用了同一個物件

var sobj = new String('abc');
var s = 'abc';
sobj == s; // true, 隱式進行資料型別轉換
sobj === s; // false, 未進行資料轉換,一個是物件(object)一個是值(string)

對數字也是一樣,要注意比較的是Number物件還是數值
當然,浮點數仍然是需要注意的,我們知道浮點數並不能正確地表示小數點後面的部分,大多數情況下都只能得到近似值而已

0.1 + 0.2 // 0.30000000000000004
(0.1 + 0.2) === 0.3; // false
1/3 // 0.333333333333333
10/3 - 3 // 0.333333333333335
(10/3 - 3) === 1/3; // false

特殊數值

可以通過 Number 物件的屬性值來獲知 64 位浮點數所支援的最大正值和最小正值。如果在其之前添
加負號(運算子),就能夠獲得相應的最大負值和最小負值

Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324 -Number.MAX_VALUE; // -1.7976931348623157e+308 -Number.MIN_VALUE; // -5e-324 Number.POSITIVE_INFINITY // 正無窮大 Number.NEGATIVE_INFINITY // 負無窮大 Number.NaN // Not a Number var inf = Number.MAX_VALUE * 2; // 將最大正數值乘以 2 之後能夠得到正無窮大 inf / 2; // Infinity,仍然為無窮大 inf * 0; // NaN,即使乘以0也不會得到0

NaN比較特殊,對它進行單獨說明。
首先,對NaN

進行任何運算,其結果都是NaN。因此,如果在計算過程中出現了一次NaN,最終的結果就一定會是NaN

NaN + 1; // NaN
NaN * 0; // NaN
NaN - NaN; // NaN

NaN不與其他任何數值相等,即使兩個NaN等值判斷,其結果也為假

NaN === 1// false
NaN === NaN; // false
NaN > 1; // false
NaN > NaN; // false

雖然無法判斷NaN的數值,但可以使用isNaN函式判斷是否為NaN

資料型別轉換

JavaScript中實現資料轉換的方式有很多中,具體哪種方法的執行速度更快和具體的實現有關,不能一概而論。此外,對於客戶端 JavaScript 的情況,重要的不僅僅是需要考慮程式碼的執行速度,還應當儘可能地縮短原始碼的長度。只有縮短程式碼長度才能夠減少網路傳輸的時間。因此,以較短的程式碼長度實現資料型別轉換的寫法成為了首選。
下面是最為簡短的資料型別轉換寫法。雖然使用String(n)Number(s),程式碼可讀性可能會更高一些,不過這樣的寫法能夠實現更好的效能。

// 慣用的資料型別轉換方法(最簡短的寫法)
// 從數值轉換為字串值
var n = 3;
n+''; // 將數值 3 轉換為字串值 '3'(利用了字串連線運算子)

// 從字串值轉換為數值
var s = '3';
+s; // 將字串值 '3' 轉換為了數值 3(利用了正號運算)

在字串值和數值之間進行資料型別轉換時需要注意的地方

轉換的物件 資料型別轉換 結果
無法被轉換為數值的字串值 轉換為數值型別 數值NaN
空字串值 轉換為數值型別 數值0
數值NaN 轉換為字串型 字串"NaN"
數值Infinity 轉換為字串型 字串"Infinity"
數值-Infinity 轉換為字串型 字串"-Infinity"

轉換為布林型時,只有以下列舉的值在轉換為結果為false,除此之外都為true

  • 數值0
  • 數值NaN
  • null
  • undefined
  • 字串值''(空字串值)

通常通過!!來進行隱式的資料型別轉換,!運算是用於布林型運算元的邏輯非運算。在運算元不是布林型的情況下會自動將其轉換為布林型。因此只要使用!!這樣的雙重否定,就能夠將值轉換為布林型。

!!1; // true
!!'x'; // true
!!0; // false
!!''; // false
!!null; //false

在進行布林型的資料型別轉換時,應當對Object型別的情況多加註意。Object型別在被轉換為
布林型之後結果必定為true

var b = new Boolean(false);
if (b) { print('T'); } else { print('F'); } // T
var z = new Number(0);
if (z) { print('T'); } else { print('F'); } // T
var s = new String('');
if (s) { print('T'); } else { print('F'); } // T

而像下面這樣,通過函式呼叫方式獲得的結果就不再是Object型別,而是相應的內建型別了。這樣
一來,轉換的結果就會與直覺一致。

var b = Boolean(false);
if (b) { print('T'); } else { print('F'); } // F
var z = Number(0);
if (z) { print('T'); } else { print('F'); } // F
var s = String('');
if (s) { print('T'); } else { print('F'); } // F

異常

可以通過throw語句來丟擲異常物件,表示式可以是任意型別
若需要捕捉異常,則需要使用try-catch-finally的結構,其中catchfinally子句不能同時省略

// try-catch-finally 結構的語法
try {
語句
語句
......
} catch ( 變數名 ) { // 該變數是一個引用了所捕捉到的異常物件的區域性變數
語句
語句
......
} finally {
語句
語句
......
}

如果在try子句中(以及在try子句中呼叫的函式內)發生異常的話,執行就會中斷,並開始執行catch子句的部分。執行catch子句被稱為捕捉到了異常。在try語句之外,或者沒有catch子句的try語句,都是無法捕捉異常的。這時函式會中斷並返回至呼叫該函式之處。

throw語句和return語句在中斷函式的執行上是相似的,不過throw語句並沒有返回值。而且,如果沒能在呼叫了該函式的函式內的catch子句中捕捉異常的話,還會進一步返回到更上一層的呼叫函式(這種行為稱為異常的傳遞)。

如果最終都沒能成功捕捉異常,整個程式的執行就將被中斷。

finally子句必定會在跳出try語句之時被執行。即使沒有產生異常,finally子句也會被執行。也就是說,如果沒有產生異常的話,在執行完try子句之後會繼續執行finally子句的程式碼;如果產生了異常,則會在執行finally子句之前首先執行catch子句。對於沒有catch子句的try語句來說,異常將會被直接傳遞至上一層,但finally子句仍然會被執行。

try {
print('1');
null.x; // 在此處強制產生一個 TypeError 異常
print('not here'); // 這條語句不會被執行
} catch (e) { // 物件 e 是 TypeError 物件的一個引用
print('2'); 
} finally {
print('3');
}

前置運算子和後置運算子的區別

// 前置運算子的行為
var n = 10;
var m = ++n;
print(m, n); // n 變為了 11。++n 的值是進行了加法之後的值,所以 m 為 11。
11 11
// 後置運算子的行為
var n = 10;
var m = n++;
print(m, n); // n 變為了 11。n++ 的值是進行了加法之前的值,所以 m 為 10。
10 11

this引用

  • 在最外層程式碼中,this引用引用的是全域性物件
  • 在函式內,this引用根據函式呼叫方式的不同而有所不同
函式的呼叫方式 this引用的引用物件
建構函式呼叫 所生成的物件
方法呼叫 接收方物件
apply或是call呼叫 applycall的引數指定的物件
其他方式的呼叫 全域性物件

原型繼承

所有的函式(物件)都具有名為prototype的屬性(prototype屬性所引用的物件則稱為prototype物件)。所有的物件都含有一個(隱藏的)連結,用以指向在物件生成過程中所使用的建構函式(Function物件)的prototype物件。

function MyClass() { this.x = 'x in MyClass'; }

var obj = new MyClass(); // 通過 MyClass 建構函式生成物件
print(obj.x);
x in MyClass // 訪問物件 obj 的屬性 x

print(obj.z); // 物件 obj 中沒有屬性 z
undefined

// Function 物件具有一個隱式的 prototype 屬性
MyClass.prototype.z = 'z in MyClass.prototype'; // 在建構函式 prototype 物件新增屬性 z
print(obj.z); // 這裡的 obj.z 訪問的是建構函式 prototype 物件的屬性
z in MyClass.prototype

在讀取物件obj的屬性的時候,將首先查詢自身的屬性。如果沒有找到,則會進一步查詢物件MyClassprototype物件的屬性。這就是原型鏈的基本原理。這樣一來,在通過MyClass建構函式生成的物件之間就實現了對MyClass.prototype物件的屬性的共享。

這種共享用面向物件的術語來說就是繼承。通過繼承可以生成具有同樣執行方式的物件。不過請注
意,在上面的程式碼中,如果修改MyClass.prototype,已經生成的物件也會發生相應的變化。

而屬性的寫入與刪除則與原型鏈無關。

function MyClass() { this.x = 'x in MyClass'; }
MyClass.prototype.y = 'y in MyClass.prototype';

var obj = new MyClass(); // 通過 MyClass 建構函式來生成物件
print(obj.y); // 通過原型鏈讀取屬性
y in MyClass.prototype 

obj.y = 'override'; // 在物件 obj 中新增直接屬性 y
print(obj.y); // 讀取直接屬性
'override' 

var obj2 = new MyClass();
print(obj2.y); // 在其他的物件中,屬性 y 不會發生變化
y in MyClass.prototype
delete obj.y; // 刪除屬性 y

print(obj.y); // 該直接屬性不存在,因此將搜尋原型鏈
y in MyClass.prototype

delete obj.y; // 雖然 delete 運算的值為 true......
true
print(obj.y); // 但無法 delete 原型鏈中的屬性
y in MyClass.prototype