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
的結構,其中catch
和finally
子句不能同時省略
// 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 呼叫 |
由apply 或call 的引數指定的物件 |
其他方式的呼叫 | 全域性物件 |
原型繼承
所有的函式(物件)都具有名為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
的屬性的時候,將首先查詢自身的屬性。如果沒有找到,則會進一步查詢物件MyClass
的prototype
物件的屬性。這就是原型鏈的基本原理。這樣一來,在通過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