這裏的內容是讀書筆記,僅供本身學習所用,有欠缺的地方歡迎留言提示。編程
第1章 類型
ECMAScript語言類型包括Undefined、Null、Boolean、String、Number和Object。
類型:對語言引擎和開發人員來講,類型是值得內部特徵,它定義了值得行爲,以使其區別於其餘值。
喜歡強類型(又稱靜態類型)語言得人也許回認爲「類型」一詞用在這裏不妥。數組
1.1 類型
強制類型轉換是JavaScript開發人員最頭疼得問題之一。瀏覽器
1.2 內置類型
JavaScript有七種內置內容(後面跟着typeof的類型值):安全
除了對象值類,其餘統稱爲「基本類型」,能夠用typeof運算符來查看值得類型,它返回得是類型的字符串值,有意思的是,這七種類型和它們的字符串值並不一一對應。最特殊的就是null,但這個bug在JavaScript中存在了將近二十年,也許永遠也不會修復了,由於這牽涉到了太多的Web系統,「修復」它會產生更多的bug,令許多系統沒法正常工做。
因此須要使用符合條件來檢測null值得類型:編程語言
let a = null; (!a && typeof a === 'object'); // true
function和數組實際上都是object的一個「子類型」,因此用typeof獲取類型值時,返回都是"object"。
tip:判斷是否爲null,用(!a && typeof a === 'object')來判斷。函數
1.3 值和類型
JavaScript中的變量是沒有類型的,只有值纔有。變量能夠隨時持有任何類型的值。
換個角度來理解就是,JavaScript不作「類型強制」;也就是說,語言引擎不要求變量老是持有與其初始值同類型的值。
在對變量執行typeof操做時,獲得的結果並非該變量的類型,而是該變量持有的值的類型。工具
typeof typeof 42; // "string"
typeof 42首先返回字符串"number",而後typeof "number" 返回"string"。學習
1.3.1 undefined和undeclard
變量在未持有值的時候爲undefined。此時typeof返回"undefined":
undeclared(未聲明),undefined與undeclared是徹底不同的。
已在做用域中聲明但尚未賦值的變量,是undefined的;相反,尚未在做用域中聲明過的變量,是undeclared的。flex
let a; a; // undefiend b; // ReferenceError: b is not defined
瀏覽器對這類狀況的處理很讓人抓狂,'b is not defined'容易讓人誤覺得是'b is undefired'。這裏再明確一次,'undefined'和'is not defined'是兩碼事。此時若是瀏覽器報錯成'b is not find'或者'b is not declared'會更明確。
更讓人抓狂的是typeof處理undeclared變量的方式,以下:prototype
let a; typeof a; // "undefined" typeof b; // "undefined" 並且尚未報錯
對於undeclared(或者not defined)變量,typeof照樣返回"undefined"。請注意雖然b是一個undeclared變量,但typeof b 並無報錯。這是由於typeof有一個特殊的安全防範機制。
與undeclared變量不一樣,訪問不存在的對象屬性(設置是在全局對象window上)不會產生ReferenceError錯誤。
1.4 小結
JavaScript有七種內置類型:null、undefined、boolean、number、string、object和symbol,可使用typeof來查看。
變量沒有類型,但它們持有的值有類型。類型定義了值的行爲特徵。
不少開發人員將undefined和undeclared混爲一談,但在JavaScript中它們是兩碼事。undefined是值的一種。undeclared則表示變量尚未被聲明過。
遺憾的是,JavaScript卻將它們混爲一談,在咱們試圖訪問"undeclared"變量時這樣報錯:ReferenceError: a is not defined,而且typeof對undefined和undeclared變量都返回"undefined"。
然而,經過typeof的安全防範機制(阻止報錯)來檢查undeclared變量,有時是個不錯的方法。
第2章 值
數組(array)、字符串(string)和數字(number)是一個程序最基本的組成部分,但在JavaScript中,它們可謂讓人喜憂參半。
2.1 數組
和其餘強類型語言不一樣,在JavaScript中,數組能夠容納任何類型的值,能夠是字符串、數字、對象(object),甚至是其餘數組(多維數組就是經過這種方式來實現的)。
對數組生命後便可向其中加入值,不須要預先設定大小。
須要注意的是,使用delete運算符能夠將單元從數組中刪除,可是請注意,單元刪除後,數組的length屬性並不會發生變化。
數組經過數字進行索引,但有趣的是它們也是對象,因此也能夠包含字符串鍵值和屬性(但這些並不計算在數組長度內)。
類數組
有時須要將數組(一組經過數字索引的值)轉換爲真正的數組,這通常經過數組工具函數(如indexOf(..)、contat(..)、forEach(..)等)來實現。
2.2 字符串
字符串常常被當成字符數組。字符串的內部實現究竟有沒有數組炳皓說,但JavaScript中的字符串和字符數組並非一個回事,最多隻是看上去類似而已。
字符串和數組的確很類似,它們都是類數組,都有length屬性以及indexOf(..)(從ES5開始數組支持此方法)和concat(..)方法。
let a = "foo"; let b = ["f", "o", "o"]; a.length; // 3 b.length; // 3 a.indexOf("o"); // 1 b.indexOf("o"); // 1 let c = a.concat("bar"); // "foobar" let d = b.concat(["b", "a", "r"]); // ["f", "o", "o", "b", "a", "r"] a === c; // false b === d; // false 對象、數組存的是地址 a[1] = "O"; b[1] = "O"; a; // "foo" b; // ["f", "o", "o"]
JavaScript中字符串是不可變的,而數組是可變的。而且a[1]在JavaScript中並不是老是合法語法,在老版本的IE中就不被容許(如今能夠了)。正確的方法應該是a.charAt(1)。
字符串不可變是指字符串的成員函數不會改變其原始值,而是建立並返回一個新的字符串。而數組的成員函數在其原始值上進行操做。
c = a.toUpperCase(); a === c; // false a; // "foo" c; // "FOO"
許多數組函數用來處理字符串很方便。雖然字符串沒有這些函數,但能夠經過「借用」數組的非變動方法來處理字符串。
a.join; // undefined a.map; // undefined let c = Array.prototype.join.call(a, "-"); let d = Array.prototype.map.call(a, function(v) { return v.toUpperCase() + "."; }).join(""); c; // "f-o-o" d; // "F.O.O."
另外一個不一樣點在於字符串反轉。數組有一個字符串沒有的可變動成員函數reverse():
a.reverse(); // undefined b.reverse(); // ["o", "o", "f"] // 惋惜咱們沒法"借用"數組的可變動成員函數,由於字符串是不可變的: Array.prototype.reverse.call(a); // 返回值仍然是字符串"foo"的一個封裝對象
tip:經過Array.ptototype.xxxx.call(str)的方式可讓字符串使用數組函數,可是reverse()不適用。
一個變通(破解)的方法是先將字符串轉換爲數組,待處理完後再將結果換回字符串:
let c = a // 將a的值轉換爲字符串數組 .split("") // 將數組中的字符進行倒轉 .reverse() // 將數組中的字符拼接回字符串 .join(""); c; // "oof" // 這種方法簡單粗暴,但對簡單的字符串卻徹底適用。 // 對於包含複雜字符(Unicode,如星號、多字節字符等)的字符串並不適用。
tip:對於大多數字符串反轉,能夠用str.split().reverse().join('')的方法。
2.3 數字
JavaScript只有一種數值類型:number(數字),包括「整數」和帶小數的十進制數。此處「整數」之因此加引號是由於和其餘語言不一樣,JavaScript沒有真正意義上的整數,這也是它一直依賴爲人詬病的地方。
JavaSctipt中的「整數」就是沒有小數的十進制數。因此42.0即等同於「整數」42。
2.3.1 數字的語法
JavaScript中的數字常量通常用十進制表示。例如:
let a = 42; let b = 42.3; // 數字前面的0能夠省略 let a = 0.42; let b = .42; // 小數點後小數部分最後面的0也能夠省略 let a = 42.0; let b = 42.;
特別大和特別小的數字默認用指數格式顯示,與toExponential()函數的輸出結果相同。例如:
let a = 5E10; a; // 50000000000 a.toExponential(); // "5e+10" 這個是字符串 a == a.toExponential(); // true a === a.toExponential(); // false
因爲數字值可使用Number對象進行封裝,所以數字值能夠調用Number.prototype中的方法。例如,toFixed(..)方法能夠指定小數部分的顯示位數。
toPrecision(..)方法用來指定有效數位的顯示位數。
let a = 42.59; a.toFixed(0); // "43" a.toFixed(1); // "43.6" a.toPrecision(1); // "4e+1" a.toFixed(2); // "42.6" a.toPrecision(2); // "42.59" a.toFixed(3); // "42.590" a.toPrecision(3); // "42.6" a.toPrecision(4); // "42.59" a.toPrecision(5); // "42.590"
2.3.2 較小的數值
二進制浮點數最大的問題(不只JavaScript,全部遵循IEEE754規範的語言都是如此),是回出現以下狀況:
0.1 + 0.2 === 0.3; // false
從數學角度來講,上面的條件判斷應該爲true,可結果爲何是false呢?
簡單來講,二進制浮點數中的0.1和0.2並非十分精確,它們相加的結果並不是恰好等於0.3,而是一個比較接近的數字0.3000000000000004,因此條件判斷結果爲false。
問題是,若是一些數字沒法作到徹底精確,是否意味着數字類型毫無用處呢?答案固然是否認的。
在處理帶有小數的數字時須要特別注意。不少(也許是絕大多數)程序只須要處理整數,對打不超過百萬或者萬億,此時使用JavaScript的數字類型是絕對安全的。
那麼應該怎樣來判斷0.1+0.2和0.3是否相等呢?
最多見的方法是設置一個偏差範圍值,一般稱爲「機器精度」,對JavaScript的數字來講,這個值一般是2^-52。從ES6開始,該值定義在Number.EPSILON中,咱們能夠直接拿來用,也能夠在ES6以前的版本寫polyfill:
function numbersCloseEnoughToEqual(n1, n2) { return Math.abs(n1 - n2) < Number.EPSILON; } let a = 0.1 + 0.2; let b = 0.3; numbersCloseEnoughToEqual(a, b); // true
tip: 小數位運算,因浮點存儲的緣由會形成偏差,能夠用小於Number.EPSILON來判斷是否正確。
2.3.3 整數的安全範圍
數字的呈現方式決定了「整數」的安全值範圍遠遠小於Number.MAX_VALUE。
可以被「安全」呈現的最大整數是2^53 - 1,即9007199254740991,在ES6中被定義爲Number.MAX_SAFE_INTEGER。最小整數時-9007199254740991,在ES6中被定義爲Number.MIN_SAFE_INTEGER。
2.3.4 整數檢測
要檢測一個值是不是整數,可使用ES6中的Number.isInteger(..)方法。
Number.isInteger(42); // true Number.isInteger(42.0); // true Number.isInteGer(42.3); // false
檢測一個值是不是安全的整數,可使用ES6中的Number.isSafeInteger(..)方法:
Number.isSafeInteger(Math.pow(2, 53)); // false Number.isSafeInteger(Math.pow(2, 53) - 1); // true
2.3.5 32位有符號整數
雖然整數最大可以達到53位,可是有些數字操做(如整位操做)只適用於32位數字,因此這些操做中數字的安全範圍就要小不少,變成從Math.pow(-2, 31)到Math.pow(2, 31) - 1。
a | 0能夠將變量a中的數值轉換爲32位有符號整數,由於整位運算符|只適用於32位整數(它只關心32位之內的值,其餘的數位將被忽略)。所以與0進行操做便可截取a中的32位數位。
2.4 特殊數值
JavaScript數據類型中有幾個特殊的值須要開發人員特別注意和當心使用。
2.4.1 不是值的值
undefined類型只有一個值,即undefined。null類型也只有一個值,即null。它們的名稱既是類型也是值。undefined和null常被用來表示「空的」或是「不是值」的值。二至之間有一些細微的差異。例如:
或者
null是一個特殊關鍵字,不是標識符,咱們不能將其看成變量來使用和賦值。然而undefined倒是一個標識符,能夠被看成變量來使用和賦值。
2.4.2 undefined
在非嚴格模式下,咱們能夠位全局標識符undefined賦值(這樣的設計實在是欠考慮!):
function foo() { undefined = 2; // 很是糟糕的作法! } function foo() { "use strict"; undefined = 2; // TypeError! }
void運算符
undefined是一個內置標識符(除非被從新定義),它的值爲undefined,經過void運算符既可獲得該值。
表達式void沒有返回值,所以返回結果是undefined。void並不改變表達式的結果,只要讓表達式不返回值:
let a = 42; console.log(void a, a); // undefined 42
按慣例咱們用void 0 來得到undefined(這主要源自C語言,固然使用void true或其餘void 表達式也是能夠的)。void 0,void 1和undefined之間並無實質上的區別。
2.4.3 特殊的數字
數字類型中有幾個特殊的值。
若是數學運算的操做數不是數字類型,就沒法返回一個有效的數字,這種狀況下返回值爲NaN。
NaN意指「不是一個數字」(not a number),這個名字容易引發誤會。將它理解爲「無效數值」或者「壞數值」可能更準確些。
let a = 2 / "foo"; // NaN typeof a === "number"; // true
tip:typeof並不能徹底判斷是否爲數字類型,還包括NaN。
換句話說,「不是數字的數字」仍然是數字類型。
NaN是一個「警惕值」(有特殊用途的常規值),用於指出數字類型中的錯誤狀況,即「執行數學運算沒有成功,這是失敗後返回的結果。」
也許有人認爲若是要檢查變量的值是否爲NaN,能夠直接和NaN進行比較,就像比較null和undefeind那樣,實則否則。
null === null; // true undefined = undefined; // true let a = 2 / "foo"; a == NaN; //false
NaN是一個特殊值,他和自身不相等,是惟一一個非自反(自反,reflexive,即x === x不成立)的值。而NaN != NaN居然爲true。
tip: NaN是惟一一個非自反的值。
既然咱們沒法對NaN進行比較(結果永遠爲false),那應該怎樣來判斷它呢?可使用內建的全局工具函數isNaN(..)來判斷一個值是不是NaN。
let a = 2 / "foo"; isNaN(a); // true
isNaN(..)有一個嚴重的缺陷,它的檢查方式過於死板,就是「檢查參數是否不是NaN,也不是數字」。這樣作的結果並不太準確。
let a = 2 / "foo"; let b = "foo"; window.isNaN(a); // true window.isNaN(b); // true ????? 'foo'不是一個數字,可是它也不是NaN // 從ES6開始,使用工具函數Number.isNaN(..) Number.isNaN(a); // true Number.isNaN(b); // false
tip:判斷是否爲NaN,用Number.isNaN(..)來判斷;也能夠用非自反來判斷。
2.無窮數
熟悉傳統編譯型語言(如C)的開發人員可能都遇到過編譯錯誤(compiler error)或者運行時錯誤(runtime exception),例如「除以0」:
let a = 1 / 0;
然而在JavaScript中上例的結果爲Infinity(即Number.POSITIVE_INFINITY)。
若是除法運算中的一個操做數爲負數。則結果爲-Infinity(即Number.NEGATIVE_INFINITY)。
let a = 1 / 0; // Infinity let b = -1 / 0; // -Infinity Infinity === Infinity; // true
3.零值
JavsScript有一個常規的0(也叫作+0)和一個-0。
加法和減法運算不會獲得負零。
tip: JSON.stringify(-0)返回"0",而JSON.parse("-0")返回-0。
-0 === 0 ; // true emmm,有待深究
2.4.4 特殊等式
NaN和-0在相等比較時的表現有些特別。因爲NaN和自身不相等,因此必須使用ES6中的Number.isNaN(..),而-0等於0(對於===也是如此),所以咱們必須使用isNegZero(..)這樣的工具函數。
ES6中新加入了一個工具方法Object.is(..)來判斷兩個值是否絕對相等,能夠用來處理上述全部的特殊狀況:
let a = 2/ "foo"; let b = -3 *0; Object.is(a, NaN); // true Object.is(b, -0); // true Object.is(b, 0); // false
tip: 能使用==和===時就儘可能不要使用Object.is(..),由於前者效率更高、更爲通用,後者主要用來處理那些特殊的相等比較。
2.5 值和引用
在許多編程語言中,賦值和參數傳遞能夠經過值複製(value-copy)或者引用複製(reference-copy)來完成,這取決於咱們使用什麼語法。
JavaScript引用指向的是值。若是一個值有10個引用,這些引用指向的都是同一個值,它們相互之間沒有應用/指向關係。
JavaScript對值和引用的賦值/傳遞在語法上沒有區別,徹底根據值的類型來決定。
let a = 2; let b = 2; // b是a的值的一個副本 b++; a; // 2 b; // 3 let c = [1, 2, 3]; let d = c; // d是[1, 2, 3]的一個引用 d.push(4); c; // [1, 2, 3, 4] d; // [1, 2, 3, 4]
簡單值(即標量基本類型值)老是經過值複製的方式來賦值/傳遞,包括null、undefined、字符串、數字、布爾和ES6中的symbol。
複合值——對象(包括數組和封裝對象)和函數,則老是經過引用複製的方式來賦值/傳遞。
因爲引用指向的是值自己而非變量,因此一個引用沒法更改另外一個引用的指向。
let a = [1, 2, 3]; let b = a; b = [4, 5, 6]; // 由於賦值,b改變了本身的引用,並不會改變a的引用,因此b變了,而a沒有。 a; // [1, 2, 3] b; // [4, 5, 6]
2.6 小結JavaScript中的數組是經過數字索引的一組任意類型的值。字符串和數組相似,可是它們的行爲特徵不一樣,在將字符做爲數組來處理時須要特別當心。JavaScript中的數字包括「整數」和「浮點型」。基本類型中定義了幾個特殊的值。null類型只有一個值null,undefined類型也只有一個值undefined。全部變量在賦值以前默認值都是undefined。void運算符返回undefined。數字類型有幾個特殊值,包括NaN(意指「not a number」,更準確地說是「invalid number」)、+Infinity、--Infinity和-0。簡單標量基本類型值(字符串和數字等)經過值複製來賦值/傳遞,而複合值(對象等)經過引用複製來賦值/傳遞。JavaScript中的引用和其它語言中的引用/指針不一樣,它們不能指向別的變量/引用,只能指向值。