細(xì)節(jié)決定成敗,聊聊JS的類型(上)
今天我們來(lái)講講JavaScript的內(nèi)容,在這個(gè)部分,我首先想跟你聊一聊類型
今天我們來(lái)講講 JavaScript 的內(nèi)容,在這個(gè)部分,我首先想跟你聊一聊類型。
(資料圖片)
JavaScript 類型對(duì)每個(gè)前端程序員來(lái)說(shuō),幾乎都是最為熟悉的概念了。但是你真的很了解它們嗎?我們不妨來(lái)看看下面的幾個(gè)問(wèn)題。
為什么有的編程規(guī)范要求用 void 0 代替 undefined?
字符串有最大長(zhǎng)度嗎?0.1 + 0.2 不是等于 0.3 么?為什么 JavaScript 里不是這樣的?
ES6 新加入的 Symbol 是個(gè)什么東西?
為什么給對(duì)象添加的方法能用在基本類型上?
如果你答起來(lái)還有些猶豫的地方,這就說(shuō)明你對(duì)這部分知識(shí)點(diǎn),還是有些遺漏之處的。沒(méi)關(guān)系,今天我來(lái)幫你一一補(bǔ)上。
我在前面提到過(guò),我們的 JavaScript 模塊會(huì)從運(yùn)行時(shí)、文法和執(zhí)行過(guò)程三個(gè)角度去剖析 JS 的知識(shí)體系,本篇我們就從運(yùn)行時(shí)的角度去看 JavaScript 的類型系統(tǒng)。
類型運(yùn)行時(shí)類型是代碼實(shí)際執(zhí)行過(guò)程中我們用到的類型。所有的類型數(shù)據(jù)都會(huì)屬于 7 個(gè)類型之一。從變量、參數(shù)、返回值到表達(dá)式中間結(jié)果,任何 JavaScript 代碼運(yùn)行過(guò)程中產(chǎn)生的數(shù)據(jù),都具有運(yùn)行時(shí)類型。
JavaScript 語(yǔ)言的每一個(gè)值都屬于某一種數(shù)據(jù)類型。JavaScript 語(yǔ)言規(guī)定了 7 種語(yǔ)言類型。語(yǔ)言類型廣泛用于變量、函數(shù)參數(shù)、表達(dá)式、函數(shù)返回值等場(chǎng)合。根據(jù)最新的語(yǔ)言標(biāo)準(zhǔn),這 7 種語(yǔ)言類型是:
Undefined;
Null;
Boolean;
String;
Number;
Symbol;
Object。
除了 ES6 中新加入的 Symbol 類型,剩下 6 種類型都是我們?nèi)粘i_(kāi)發(fā)中的老朋友了,但是,要想回答文章一開(kāi)始的問(wèn)題,我們需要重新認(rèn)識(shí)一下這些老朋友,下面我們就來(lái)從簡(jiǎn)單到復(fù)雜,重新學(xué)習(xí)一下這些類型。
Undefined、Null我們的第一個(gè)問(wèn)題,為什么有的編程規(guī)范要求用 void 0 代替 undefined?現(xiàn)在我們就分別來(lái)看一下。
Undefined 類型表示未定義,它的類型只有一個(gè)值,就是 undefined。任何變量在賦值前是 Undefined 類型、值為 undefined,一般我們可以用全局變量 undefined(就是名為 undefined 的這個(gè)變量)來(lái)表達(dá)這個(gè)值,或者 void 運(yùn)算來(lái)把任意一個(gè)表達(dá)式變成 undefined 值。
但是呢,因?yàn)?JavaScript 的代碼 undefined 是一個(gè)變量,而并非是一個(gè)關(guān)鍵字,這是 JavaScript 語(yǔ)言公認(rèn)的設(shè)計(jì)失誤之一,所以,我們?yōu)榱吮苊鉄o(wú)意中被篡改,我建議使用 void 0 來(lái)獲取 undefined 值。
Undefined 跟 Null 有一定的表意差別,Null 表示的是:“定義了但是為空”。所以,在實(shí)際編程時(shí),我們一般不會(huì)把變量賦值為 undefined,這樣可以保證所有值為 undefined 的變量,都是從未賦值的自然狀態(tài)。
Null 類型也只有一個(gè)值,就是 null,它的語(yǔ)義表示空值,與 undefined 不同,null 是 JavaScript 關(guān)鍵字,所以在任何代碼中,你都可以放心用 null 關(guān)鍵字來(lái)獲取 null 值。
BooleanBoolean 類型有兩個(gè)值, true 和 false,它用于表示邏輯意義上的真和假,同樣有關(guān)鍵字 true 和 false 來(lái)表示兩個(gè)值。這個(gè)類型很簡(jiǎn)單,我就不做過(guò)多介紹了。
String我們來(lái)看看字符串是否有最大長(zhǎng)度。
String 用于表示文本數(shù)據(jù)。String 有最大長(zhǎng)度是 2^53 - 1,這在一般開(kāi)發(fā)中都是夠用的,但是有趣的是,這個(gè)所謂最大長(zhǎng)度,并不完全是你理解中的字符數(shù)。
因?yàn)?String 的意義并非“字符串”,而是字符串的 UTF16 編碼,我們字符串的操作 charAt、charCodeAt、length 等方法針對(duì)的都是 UTF16 編碼。所以,字符串的最大長(zhǎng)度,實(shí)際上是受字符串的編碼長(zhǎng)度影響的。
Note:現(xiàn)行的字符集國(guó)際標(biāo)準(zhǔn),字符是以 Unicode 的方式表示的,每一個(gè) Unicode 的碼點(diǎn)表示一個(gè)字符,理論上,Unicode 的范圍是無(wú)限的。UTF 是 Unicode 的編碼方式,規(guī)定了碼點(diǎn)在計(jì)算機(jī)中的表示方法,常見(jiàn)的有 UTF16 和 UTF8。 Unicode 的碼點(diǎn)通常用 U+??? 來(lái)表示,其中 ??? 是十六進(jìn)制的碼點(diǎn)值。 0-65536(U+0000 - U+FFFF)的碼點(diǎn)被稱為基本字符區(qū)域(BMP)。
JavaScript 中的字符串是永遠(yuǎn)無(wú)法變更的,一旦字符串構(gòu)造出來(lái),無(wú)法用任何方式改變字符串的內(nèi)容,所以字符串具有值類型的特征。
JavaScript 字符串把每個(gè) UTF16 單元當(dāng)作一個(gè)字符來(lái)處理,所以處理非 BMP(超出 U+0000 - U+FFFF 范圍)的字符時(shí),你應(yīng)該格外小心。
JavaScript 這個(gè)設(shè)計(jì)繼承自 Java,最新標(biāo)準(zhǔn)中是這樣解釋的,這樣設(shè)計(jì)是為了“性能和盡可能實(shí)現(xiàn)起來(lái)簡(jiǎn)單”。因?yàn)楝F(xiàn)實(shí)中很少用到 BMP 之外的字符。
Number下面,我們來(lái)說(shuō)說(shuō) Number 類型。Number 類型表示我們通常意義上的“數(shù)字”。這個(gè)數(shù)字大致對(duì)應(yīng)數(shù)學(xué)中的有理數(shù),當(dāng)然,在計(jì)算機(jī)中,我們有一定的精度限制。
JavaScript 中的 Number 類型有 18437736874454810627(即 264-253+3) 個(gè)值。
JavaScript 中的 Number 類型基本符合 IEEE 754-2008 規(guī)定的雙精度浮點(diǎn)數(shù)規(guī)則,但是 JavaScript 為了表達(dá)幾個(gè)額外的語(yǔ)言場(chǎng)景(比如不讓除以 0 出錯(cuò),而引入了無(wú)窮大的概念),規(guī)定了幾個(gè)例外情況:
NaN,占用了 9007199254740990,這原本是符合 IEEE 規(guī)則的數(shù)字;
Infinity,無(wú)窮大;
-Infinity,負(fù)無(wú)窮大。
另外,值得注意的是,JavaScript 中有 +0 和 -0,在加法類運(yùn)算中它們沒(méi)有區(qū)別,但是除法的場(chǎng)合則需要特別留意區(qū)分,“忘記檢測(cè)除以 -0,而得到負(fù)無(wú)窮大”的情況經(jīng)常會(huì)導(dǎo)致錯(cuò)誤,而區(qū)分 +0 和 -0 的方式,正是檢測(cè) 1/x 是 Infinity 還是 -Infinity。
根據(jù)雙精度浮點(diǎn)數(shù)的定義,Number 類型中有效的整數(shù)范圍是 -0x1fffffffffffff 至 0x1fffffffffffff,所以 Number 無(wú)法精確表示此范圍外的整數(shù)。
同樣根據(jù)浮點(diǎn)數(shù)的定義,非整數(shù)的 Number 類型無(wú)法用(= 也不行) 來(lái)比較,一段著名的代碼,這也正是我們第三題的問(wèn)題,為什么在 JavaScript 中,0.1+0.2 不能 =0.3:
1 | console.log(0.1+0.2==0.3); |
這里輸出的結(jié)果是 false,說(shuō)明兩邊不相等的,這是浮點(diǎn)運(yùn)算的特點(diǎn),也是很多同學(xué)疑惑的來(lái)源,浮點(diǎn)數(shù)運(yùn)算的精度問(wèn)題導(dǎo)致等式左右的結(jié)果并不是嚴(yán)格相等,而是相差了個(gè)微小的值。所以實(shí)際上,這里錯(cuò)誤的不是結(jié)論,而是比較的方法,正確的比較方法是使用 JavaScript 提供的最小精度值:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
檢查等式左右兩邊差的絕對(duì)值是否小于最小精度,才是正確的比較浮點(diǎn)數(shù)的方法。這段代碼結(jié)果就是 true 了。
SymbolSymbol 是 ES6 中引入的新類型,它是一切非字符串的對(duì)象 key 的集合,在 ES6 規(guī)范中,整個(gè)對(duì)象系統(tǒng)被用 Symbol 重塑。
在后面的文章中,我會(huì)詳細(xì)敘述 Symbol 跟對(duì)象系統(tǒng)。這里我們只介紹 Symbol 類型本身:它有哪些部分,它表示什么意思,以及如何創(chuàng)建 Symbol 類型。
Symbol 可以具有字符串類型的描述,但是即使描述相同,Symbol 也不相等。
我們創(chuàng)建 Symbol 的方式是使用全局的 Symbol 函數(shù)。例如:
var mySymbol = Symbol("my symbol");
一些標(biāo)準(zhǔn)中提到的 Symbol,可以在全局的 Symbol 函數(shù)的屬性中找到。例如,我們可以使用 Symbol.iterator 來(lái)自定義 for…of 在對(duì)象上的行為:
var o = new Objecto[Symbol.iterator] = function() { var v = 0 return { next: function() { return { value: v++, done: v > 10 } } }};for(var v of o)console.log(v); // 0 1 2 3 ... 9
代碼中我們定義了 iterator 之后,用 for(var v of o) 就可以調(diào)用這個(gè)函數(shù),然后我們可以根據(jù)函數(shù)的行為,產(chǎn)生一個(gè) for…of 的行為。
這里我們給對(duì)象 o 添加了 Symbol.iterator 屬性,并且按照迭代器的要求定義了一個(gè) 0 到 10 的迭代器,之后我們就可以在 for of 中愉快地使用這個(gè) o 對(duì)象啦。
這些標(biāo)準(zhǔn)中被稱為“眾所周知”的 Symbol,也構(gòu)成了語(yǔ)言的一類接口形式。它們?cè)试S編寫(xiě)與語(yǔ)言結(jié)合更緊密的 API。
ObjectObject 是 JavaScript 中最復(fù)雜的類型,也是 JavaScript 的核心機(jī)制之一。Object 表示對(duì)象的意思,它是一切有形和無(wú)形物體的總稱。
下面我們來(lái)看一看,為什么給對(duì)象添加的方法能用在基本類型上?
在 JavaScript 中,對(duì)象的定義是“屬性的集合”。屬性分為數(shù)據(jù)屬性和訪問(wèn)器屬性,二者都是 key-value 結(jié)構(gòu),key 可以是字符串或者 Symbol 類型。
關(guān)于對(duì)象的機(jī)制,后面會(huì)有單獨(dú)的一篇來(lái)講述,這里我重點(diǎn)從類型的角度來(lái)介紹對(duì)象類型。提到對(duì)象,我們必須要提到一個(gè)概念:類。
因?yàn)?C++ 和 Java 的成功,在這兩門語(yǔ)言中,每個(gè)類都是一個(gè)類型,二者幾乎等同,以至于很多人常常會(huì)把 JavaScript 的“類”與類型混淆。
事實(shí)上,JavaScript 中的“類”僅僅是運(yùn)行時(shí)對(duì)象的一個(gè)私有屬性,而 JavaScript 中是無(wú)法自定義類型的。
JavaScript 中的幾個(gè)基本類型,都在對(duì)象類型中有一個(gè)“親戚”。它們是:
Number;
String;
Boolean;
Symbol。
所以,我們必須認(rèn)識(shí)到 3 與 new Number(3) 是完全不同的值,它們一個(gè)是 Number 類型, 一個(gè)是對(duì)象類型。
Number、String 和 Boolean,三個(gè)構(gòu)造器是兩用的,當(dāng)跟 new 搭配時(shí),它們產(chǎn)生對(duì)象,當(dāng)直接調(diào)用時(shí),它們表示強(qiáng)制類型轉(zhuǎn)換。
Symbol 函數(shù)比較特殊,直接用 new 調(diào)用它會(huì)拋出錯(cuò)誤,但它仍然是 Symbol 對(duì)象的構(gòu)造器。JavaScript 語(yǔ)言設(shè)計(jì)上試圖模糊對(duì)象和基本類型之間的關(guān)系,我們?nèi)粘4a可以把對(duì)象的方法在基本類型上使用,比如:
console.log("abc".charAt(0)); //a
甚至我們?cè)谠蜕咸砑臃椒ǎ伎梢詰?yīng)用于基本類型,比如以下代碼,在 Symbol 原型上添加了 hello 方法,在任何 Symbol 類型變量都可以調(diào)用。
Symbol.prototype.hello = () => console.log("hello");var a = Symbol("a");console.log(typeof a); //symbol,a并非對(duì)象a.hello(); //hello,有效
1234 | Symbol.prototype.hello=()=>console.log( "hello" ); var a=Symbol( "a" ); console.log( typeof a); //symbol,a并非對(duì)象 a.hello(); //hello,有效 |
所以我們文章開(kāi)頭的問(wèn)題,答案就是. 運(yùn)算符提供了裝箱操作,它會(huì)根據(jù)基礎(chǔ)類型構(gòu)造一個(gè)臨時(shí)對(duì)象,使得我們能在基礎(chǔ)類型上調(diào)用對(duì)應(yīng)對(duì)象的方法。
?
更多精彩內(nèi)容請(qǐng)點(diǎn):開(kāi)發(fā)者網(wǎng)站代碼查看
?