你不知道的 JavaScript 基礎類型

前言

今天聊下 js 中的數據類型,數據類型是一個語言的基石,那你真的瞭解我們工作中這些常使用的數據類型嗎。可以先看以下幾個問題?看看你能直接回答上來幾個。本文將從這些問題入手,主要講解我們在使用中容易模糊和產生歧義的點,加深我們對數據類型的理解。

問題

概述

在 JavaScript 中的類型系統可以分爲以下七類,我們稱之爲語言類型,js 不支持自己定義類型,所以在前端代碼中所有值都來源於以下七種類型:

  1. Undefined

  2. Null

  3. Boolean

  4. Number

  5. String

  6. Symbol

  7. Object

前六個是簡單數據類型(原始值),Object是複雜數據類型,也是 js 中的大 boss,整個 js 語言都是在對象的基礎上建立的,依靠 js 異常靈活的類型系統,現有的 7 種類型可滿足幾乎所有使用場景。

Undefined

Undefined類型的值只有一個就是'undefined',屬於 js 中的特殊類型,自身的含義表示一個未定義的值,這個值在ES3版本以前是不存在的,引入的目的是爲了明確空對象指針 (null) 與未初始化變量的區別。

應用場景:當聲明一個變量並沒有賦值時,就相當於給變量賦值了一個undefined,所以在任何情況下,我們都沒必要給一個變量顯式的賦值爲 undefined,所以undefind並沒有實際的主動使用的場景。

爲什麼說undefined是變量?那我們能改寫它嗎?

在 MDN 上對此的描述大概是:它掛載在全局對象,是全局作用域下的一個屬性,這個屬性的最初值就是原始數據類型undefined

那問題又來了,既然是變量那我們能改變它嗎? 這個問題真是毫無意義,我們在任何時候都不會,不應該,不可能去嘗試改變undefined的值。

而實際上現代瀏覽器自 ES5 標準以來undefined就是一個不能被配置(non-configurable),不能被重寫(non-writable)的屬性。** 可是在局部作用域中我們仍然可以聲明一個名爲undefined的變量,去覆蓋全局作用域下的undefined**,因爲在 ES 中,undefined既不是關鍵字也不是保留字(無聊的知識增加了),請看下圖:

爲了符合編程規範,有些文章會提到可以使用void操作符合理合法的去獲取undefined的值,。。。寫起來還蠻耳目一新的(無聊的知識又增加了)。

總之,無論這是設計失誤還是有意爲之,對我們對於該語言的使用基本沒有什麼影響,簡單瞭解下就好,這個其實也屬於是沒什麼價值和意義的問題。

Null

Null類型的值只有一個就是'null',與 undefined 類似,也屬於 js 中的特殊類型,既然null是一個原始類型,那就有了這個問題了:

爲什麼 typeof null 是 'object'?null 是對象嗎?

這結果就很奇葩,但仍然表現如此,那多數就是歷史原因導致,所以我們無需糾結null到底是原始值還是一個對象。《JavaScript 高程 4》中對此有比較合乎邏輯的定義:null 值表示一個空對象的指針null當然是個原始類型的值,但它也是個空對象的指針,這也解釋了爲什麼typeof null === 'object'

由此我們也應該能理解,null雖然含義與undefined類似都表示空,但null表示的是一個空對象,當我們要聲明一個變量準備賦值一個對象,卻在當時沒有一個具體的對象可保存時,就要使用 null 來填充該變量,我們永遠不會主動的去賦值一個undefined,卻經常會主動賦值一個null表示一個對象的初始值。

值得一提的是,當我們去判斷undefined == null時,會返回一個true,又是一個迷惑性的操作,背後原因是==操作符讓值做了隱式類型轉換,這也是 js 類型系統異常靈活的原因。

Boolean

布爾值,值有兩個truefalse

Number

Number類型最常用來表示我們常規意義上的十進制數字,也能使用八或十六進制,除此之外還有一些特殊的值如NaN、Infinity、-Infinity等,相關知識點雖多但大多比較容易理解。這裏要專門聊的是 js 老生常談的Number浮點值精度不足的問題:

0.1+0.2 爲什麼不等於 0.3?

前端:王德發??
ES:雨我無瓜。

Number浮點值表現出來的這種特性來源於 ES 採用的 IEEE754 二進位浮點數算術標準,該標準運用廣泛,很多常見的編程語言(如 C++、C#、Java)都使用該標準來處理數據的存儲與計算,實際上任何採用此標準的語言,都會有以上特性(有些語言通過內部封裝幫助解決該問題)。而 ES 正是採用其中的雙精度浮點數規則。

雙精度浮點數是計算機中常見的一種數據格式,在內存中佔 64 位。計算機對 Number 類型做存儲時,需將其轉化爲二進制做存儲,十進制小數轉爲二進制時,會出現二進制位數超出處理範圍的問題,如 0.1(0.000110011... 0011 死循環), 計算機會通過 0 舍 1 入來存儲處理範圍內的位數,此時誤差就出現了,但是由於保留位數很多,誤差將非常小,可忽略不計。

但當我們需要測試某個特定的浮點值時,可能就會產生錯誤,所以我們在程序中儘量不去驗證某個特定的浮點值。ES6 之後新增了Number.EPSILON屬性,表示數字最小間隔,也可用它來比較判斷,是正常誤差值還是個錯誤。

(0.1+0.2)-0.3<=Number.EPSILON  //true

ES 也爲我們處理了其他一些場景,如 1/0 並不會拋出錯誤,而是定義了 Infinity 的無窮值,非數值會表示爲特殊的 NaN 值等。

String

本節探討下字符串在 ES 中是如何做存儲的,字符串有個非常常用的length屬性,表示字符數量,憑藉我們程序員最樸素的情感,通常認爲length的值會與我們眼睛看到的結果是一致的,但偶爾會突然發現一些不一樣的情況,如下:

let s = '𠮷';  console.log(s.length)   // 2

栓 Q,又被騙到

實際上字符串的存儲要比我們看到的複雜的多,如何將字符串真正存儲到計算機中,這裏涉及到兩個多數人都聽過,但可能又不是特別瞭解的概念,字符集字符編碼。相關知識點很多可以單開一篇文章,下面簡單講解下。

字符集

字符集相當於一個密碼本,在一個字符集中每個字符會對應一個固定的編號(碼點),編號可以使用數字代替,而字符則可能是各種各樣的文字表情、字母符號、圖形圖像等一切人們使用的符號。如果我們把全世界所有文字都放到一個字符集中,那就在計算機中實現了世界文字的統一。

而現在也正有這樣的一個字符集那就是大名鼎鼎Unicode,這個字符集囊括了迄今爲止世界上所有的文字,到今天發佈到了 15.0 版本,收錄了 149,186 個文字,已經成爲計算機中使用最廣泛的字符集標準。

Unicode使用數字給字符做唯一編號,通常使用十六進制表示,會在U+0000~U+10FFFF範圍定義字符,能使用的總數大概是一百多萬個,目前只有十分之一被定義了字符。比如U+597D代表中文字

Unicode將字符集範圍分爲了 17 個平面,前面的 65536 個字符位,稱爲基本多語言平面(BMP),它的碼點範圍就是 U+0000~U+FFFF。所有最常見的字符都放在這個平面,是 Unicode 最先定義的一個平面,其他字符放在其他 16 個平面,稱爲增補平面(SMP),

字符編碼

而字符編碼是指計算機要如何將Unicode中的字符編號存入計算機中,是一種編碼方式,每個字符集都有其對應的編碼方式,而Unicode對應的編碼方式就是我們常聽到的,UTF8、UTF16、UTF32。特點如下:

編碼方式

我們知道計算機只能存儲二進制數,所以當我們知道一個字符的十六進制碼點數(字符編號),只要把它轉成二進制,存到計算機中即可,而編碼就是如何轉換和存儲的過程。

雖然以上三種編碼方式目的都是將碼點轉成二進制數,但轉換的方式、轉換後存儲的二進制數、計算機讀取二進制的方法都是不同的,這是因爲在 U+0000~U+10FFFF 範圍的碼點,轉換成二進制存儲,最小的只需要 1 個字節,最多需要 3 個字節

UTF-16 將這 20 位拆成兩半,前 10 位映射在 U+D800 到 U+DBFF(空間大小 210),稱爲高位(H),後 10 位映射在 U+DC00 到 U+DFFF(空間大小 210),稱爲低位(L)。這意味着,一個輔助平面的字符,被拆成兩個基本平面的字符表示。

JS 中的字符編碼

說了這麼多,我們再聊回 js,js 中到底採用哪種編碼方式是 UTF8 還是 UTF16 呢,重點來了,都不是!js 採用的是 UTF16UCS-2 的編碼混合策略,從今天看來 UTF16 可以算是 UCS-2 的升級版。爲什麼 js 不直接採用 UTF16 呢,因爲 js 首次面世時 UTF16 還未推出,兩者混用也算是一個歷史遺留問題。

對於 U+0000~U+FFFF 基本平面的字符 UCS-2UTF16 是完全沒區別的,我們日常使用的絕大部分字符都來源於這個平面,所以 js 開發者在一般情況下對此無感。

UCS-2 是固定的將 2 字節 / 16 位認爲是一個字符(因爲 Unicode 早期只有一個平面,16 位已經完全足夠,後期進行了擴容)。當字符中出現基本平面之外的字符,因爲上文說的代理對策略,該字符會用兩個 2 字節 / 16 位去存儲,而 UCS-2 固定的認爲 2 字節就是一個單獨的字符,所以此時使用字符類的操作時,會出現錯誤,所以纔會出現上述問題'𠮷'.length = 2

當然這些都算是歷史問題,自 ES6 推出,Unicode相關的編碼問題已經得到解決,ES 也完全有能力自動識別字符是 2 個字節還是 4 個字節,但開發者對於.length的使用習慣由來已久,爲了保證兼容性,並未對其結果做出修正。

結尾

由於衆所周知的歷史原因和複雜多樣的執行環境,JavaScript 在使用過程中可能會遇到各種奇怪難理解的現象,有一些是因爲更深層的底層原理,也有很多隻是因爲設計失誤或歷史包袱,在這裏作者建議我們在學習中只聚焦少部分有價值的問題,而忽略無意義的探究。

參考文獻

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/wtyMVQzzCnuIDeYpqfDoaA