前端基礎學習之關於 JavaScript 的數據類型
7中數據類型
- 空(Null)
- 未定義(Undefined)
- 數字(Number)
- 字符串(String)
- 布爾值(Boolean)
- 符號(Symbol)
- 對象(Object)
Undefined
只有一個值“undefined”。得到undefined的幾種方式:
- 引用已聲明但未初始化的變量;
- 引用未定義的對象屬性;
- 執行無返回值函數;
- 執行 void 表達式;
- 全局常量 window.undefined 或 undefined。
“void 0” 表達式常用來代表不執行任何操作,實例如下:
x>0 && x<5 ? fn() : void 0;
判斷變量值是否爲undefined常用的方式:
if(typeof x === 'undefined') {
...
}
注意,直接使用if(!x)和if(x===undefined)是不可取的,因爲只要變量 x 的值爲 undefined、空字符串、數值 0、null 時都會判斷爲真。直接使用三等號===
,當x爲undefined,那麼是會拋出錯誤 “ReferenceError: x is not defined” 導致程序執行終止。
Null
表示空值,只有唯一值null。
Boolean
Boolean 數據類型只有兩個值:true 和 false,分別代表真和假。
需要注意,我們在if語句中進行條件判斷時,if語句實惠進行類型轉換的,將判斷條件的變量轉換成 Boolean 數據類型,而 0、空字符串、null、undefined 在轉換時都會返回 false。如下代碼,傳入0時,返回的是undefined,而不是我們期望的"星期日"。
function getWeek(week) {
const dict = ['日', '一', '二', '三', '四', '五', '六'];
if(week) return `星期${dict[week]}`;
}
Number
兩個重要值
Number 是數值類型,有 2 個特殊數值得注意一下,即 NaN 和 Infinity。
- NaN(Not a Number)通常在計算失敗的時候會得到該值。要判斷一個變量是否爲 NaN,則可以通過 Number.isNaN 函數進行判斷。
- Infinity 是無窮大,加上負號 “-” 會變成無窮小,在某些場景下比較有用,比如通過數值來表示權重或者優先級,Infinity 可以表示最高優先級或最大權重。
進制轉換
將其他進制的整數轉換成十進制顯示的時候可以使用 parseInt 函數,此函數有來兩個參數,第一個爲數字或字符串,第二個爲進制數,默認爲10,當進制數轉換失敗時會返回 NaN。
['0', '1', '2'].map(parseInt) // [0, NaN, NaN]
將十進制轉換成其他進制時,可以通過 toString 函數來實現。
(10).toString(2) // "1010"
精度問題
在JavaScript中,我們進行浮點數運算時,往往會遇到精度問題。如下:
0.1 + 0.2 // 0.30000000000000004//並不是期望的0.3
Math.pow(Math.pow(5, 1/2), 2) // 5.000000000000001
原因
JavaScript進行計算時,JavaScript 引擎會先將十進制數轉換爲二進制,然後進行加法運算,再將所得結果轉換爲十進制。
解決方法
兩種方式消除無限小數位。
- 先轉換成整數進行計算,然後再轉換回小數,這種方式適合在小數位不是很多的時候。
- 捨棄末尾的小數位。比如對上面的加法就可以先調用 toPrecision 截取 12 位,然後調用 parseFloat 函數轉換回浮點數。
parseFloat((0.1 + 0.2).toPrecision(12)) // 0.3
String
最常用的類型,相關的方法比較多,不一一介紹,可查看官方文檔。
千位分隔符的兩種實現方式:
//從右往左遍歷數值每一位,每隔 3 位添加分隔符。將字符串數據轉化成引用類型數據,即用數組來實現。
function sep(n) {
let [i, c] = n.toString().split(/(\.\d+)/)
return i.split('').reverse().map((c, idx) => (idx+1) % 3 === 0 ? ',' + c: c).reverse().join('').replace(/^,/, '') + c
}
//通過引用類型,即用正則表達式對字符進行替換來實現
function sep2(n){
let str = n.toString()
str.indexOf('.') < 0 ? str+= '.' : void 0
return str.replace(/(\d)(?=(\d{3})+\.)/g, '$1,').replace(/\.$/, '')
}
Symbol
定義
Symbol 是 ES6 中引入的新數據類型,它表示一個唯一的常量,通過 Symbol 函數來創建對應的數據類型,創建時可以添加變量描述,該變量描述在傳入時會被強行轉換成字符串進行存儲。
一句話概括 Symbol 生成一個全局唯一的值。
常用於兩個場景 避免常量值重複
和避免對象屬性覆蓋
補充知識:類型轉換
類型轉換定義
在處理不同數據類型運算或邏輯操作時會強制轉換成同一數據類型。
通常強制轉換的目標數據類型爲 String、Number、Boolean 這三種。如下爲轉換關係:
常見的觸發類型轉換的操作
- 運算相關的操作符包括 +、-、+=、++、* 、/、%、<<、& 等。
- 數據比較相關的操作符包括 >、<、== 、<=、>=、三等號
===
。 - 邏輯判斷相關的操作符包括 &&、!、||、三目運算符。
Object
引用類型,即鍵值對的集合。
淺拷貝和深拷貝:
- 由於引用類型在賦值時只傳遞指針,這種拷貝方式稱爲淺拷貝。
- 而創建一個新的與之相同的引用類型數據的過程稱之爲深拷貝。
通過typeof查看數據類型的描述:
[undefined, null, true, '', 0, Symbol(), {}].map(it => typeof it)// ["undefined", "object", "boolean", "string", "number", "symbol", "object"]
發現 null 有些特殊,返回結果和 Object 類型一樣都爲"object",所以需要再次進行判斷。按照上面分析的結論,我們可以寫出下面的函數:
function clone(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {//避免null
result[key] = clone(item)
} else {
result[key] = item
}
})
return result
}
在遍歷 Object 類型數據時,我們需要把 Symbol 數據類型也考慮進來,所以不能通過 Object.keys 獲取鍵名或 for…in 方式遍歷,而是通過 getOwnPropertyNames 和 getOwnPropertySymbols 函數將鍵名組合成數組,然後進行遍歷。對於鍵數組長度爲 0 的非 Object 類型的數據可直接返回,然後再遍歷遞歸,最終實現拷貝。
由於嵌套調用,因此當對象數據嵌套時,會出現死循環。怎麼避免這種情況呢?一種簡單的方式就是把已添加的對象記錄下來,這樣下次碰到相同的對象引用時,直接指向記錄中的對象即可。要實現這個記錄功能,我們可以藉助 ES6 推出的 WeakMap 對象,該對象是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是對象,而值可以是任意的。
function clone(obj) {
let map = new WeakMap()//記錄已經拷貝過的對象
function deep(data) {
let result = {}
const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
if(!keys.length) return data
const exist = map.get(data)
if (exist) return exist //重新創建一個對象並加入 WeakMap 中
map.set(data, result)//重新創建一個對象並加入 WeakMap 中
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {
result[key] = deep(item)
} else {
result[key] = item
}
})
return result
}
return deep(obj)
}
總結
- 7種類型:空(Null)、未定義(Undefined)、數字(Number)、字符串(String)、布爾值(Boolean)、符號(Symbol)、對象(Object)。
- Number精度問題。
- 進制轉換parseInt和toString函數。
- Symbol 生成一個全局唯一的值。
- Object深拷貝,getOwnPropertyNames 和 getOwnPropertySymbols 函數。
- WeakMap解決深拷貝遞歸調用死循環問題。