JavaScript - Data Types 資料型別

JavaScript - Data Types 資料型別

·

9 min read

資料型別 Data types

原始資料型別 Primitive Data Type

原始資料型別都有「原始值 (primitive value)」,且原始值是不可被更改的。

Javascript 的七種原始資料型別:

除此之外,還有第八種:

物件資料型別 Object Data Type

Ref: https://developer.mozilla.org/en-US/docs/Glossary/Object

  • Object

用 typeof 查看型別

The typeof operator returns a string indicating the type of the operand's value.

console.log(typeof 42) // number
console.log(typeof 'blubber') // string
console.log(typeof true); // boolean
console.log(typeof undeclaredVariable); // undefined
console.log(typeof undefined) // undefined
console.log(typeof null) // object
💡
只有 typeof null 會返回 "object"。要確認一個值是否為 null 時,可使用 === null 來檢驗該值是否為 null。Ref: Javascript - Null, undefined, not defined 差異

Dynamic & Weak Typing

動態型別語言 (dynamic language with dynamic types)

JavaScript is a dynamic language with dynamic types.

  • 宣告變數時不必特別宣告變數的型別

  • 可以以不同的型別使用同一個變數

let someValue = 42; // someValue is now a number
someValue = "bar"; // someValue is now a string
someValue = true; // someValue is now a boolean

弱型別語言 (weakly typed language)

JavaScript is also a weakly typed language

  • 變數會自動轉換型別(implicit coercions)
const someValue = 42; // foo is a number
const result = someValue + "1"; // JavaScript coerces someValue to a string, so it can be concatenated with the other operand
console.log(result); // 421
console.log(typeof result); // string

自動轉型:字串與數字相加、相減

let age = 18; // number
let myName = "Tom"; // string
let total = myName + age; // turn age into string type
console.log(total); // string
// Expected output: "Tom18"

上述例子中, 數字變數 age 與字串變數 myName 相加, age 的值 18 被自動轉型成字串 "18" ,因此相加結果 totalTom18

  • + :當數字與字串相加數字被轉型成字串

  • - :當數字與字串相減字串被轉型成數字

// 數字與字串相加
"37" + 7 // "377"
30 + "7" // "307"

// 數字與字串相減
"37" - 7 // 30
30 - "7" // 23

Undefined

undefined 是型別 Undefined 的值,且 Undefined 這個型別就只有 undefined 這個值。

當一個變數沒有被賦予任何值的時候,Javascript 會給予它一個預設值 undefined

var a
console.log(a) // undefined

使用 typeof undefined 也會得到 undefined

var a
if(typeof a === 'undefined'){
    console.log('a is undefined'); // a is undefined
}

注意: typeof 回傳的值是字串,所以 'undefined' 需以字串表示。

💡
關於 Null / Undefined / Not defined 的詳細說明與比較:Javascript - Null, undefined, not defined 差異

Null

null 是型別 Null 的值,且 Null 這個型別也就只有 null 這個值。它的意思是「存在但沒有值」。null 是刻意讓開發人員用來宣告「空值」用的,Javascript 並不會將值設定為 null

使用 typeof Null 會得到 object

console.log(typeof null) // 'object'

這是 Javascript 最著名的 bug 之一,詳細情形可以看這篇:The history of “typeof null”

Boolean 布林值

Javascript 的 Boolean 用來表示真或假,只會有兩種值:

  • true - 表示真

  • false - 表示假

在 JavaScript 中,只有這些值會被當作是 false

  • undefined

  • null

  • false

  • 0-0 (數值)

  • NaN

  • ''"" 空字串

除了這些值以外的值都會是 true

使用 Boolean() 可以用來將其他的資料型態轉型 (type conversion) 成布林值型態:

Boolean(0) // false
Boolean(100) // true

Boolean('') // false
Boolean('hi') // true

Boolean(null) // false
Boolean(undefined)// false
Boolean(NaN) // false

Boolean( {} ) // true
Boolean( [] ) // true
Boolean( function(){} ) // true

Truthy & Falsy

轉換後會得到 false 的,就稱作 「falsy」值;會變成 true 的則稱作 「truthy」值。

String 字串

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 2^53 - 1 elements. The String type is generally used to represent textual data in a running ECMAScript program, in which case each element in the String is treated as a UTF-16 code unit value (p.72)

來自「來數數 JavaScript 的所有資料型別」的翻譯蒟蒻:

字串就是一連串的 16-bit 的數字,而這些數字就是 UTF-16 的 code unit,字串的長度最多則是 2^53 - 1。

以下整理了字串處理的幾個實用屬性與方法:

.length 屬性,獲取字元串長度

This property returns the number of code units in the string

String 的 length 屬性(property)包含著該字元串的長度(包含空白與標點符號)。

const str = 'What\'s up?'
console.log(str.length) // 10

空字串的 length0

const empty = ''
console.log(empty.length) // 0

靜態屬性 String.length 與字串長度無關,它是 String 函數的參數數量,也就是 1。

為字串的 length 屬性重新賦值不會有任何作用(在 strict mode 下會報錯):

const myString = 'Hello'

myString.length = 2
console.log(myString); // 'Hello'
console.log(myString.length); // 5

"use strict"
myString.length = 2
// Uncaught TypeError: Cannot assign to read only property 'length' of string 'hello'

碼元(code unit) vs 字元(character)

Javascript 是使用 UTF-16 編碼,每個 Unicode 字符可以編碼成一到兩個碼元(code unit)。也就是說,對於 Javascript 來說所有的字串都是一系列的 UTF-16 碼元。

length 指的是碼元的個數(code units)而不是字元數(characters),因此 length 返回的值可能與字串中 Unicode 實際的字符數量不一致。

如果想要取得正確的字元數,可以使用迭代器(iterator),將字串分隔成字元。或使用 Array.from(str).length

const emoji = '😄'
console.log(emoji.length) // 2
console.log([...emoji].length) // 1

如果想要取得正確的字元數,可以使用迭代器(iterator),將字串分隔成字元。或使用 Array.from(str).length

const emoji = '😄'

function getCharacterLength(str) {
    return [...str].length
}

console.log(getCharacterLength(emoji)) // 1
console.log(Array.from(emoji).length) // 1

.trim() 方法,去掉字串頭尾空白

  • trim():去掉前後空白

  • trimStart():去掉起始空白

  • trimEnd():去掉結尾空白

const userEmail1 = '          eddy@gmail.com'
const userEmail2 = 'johnny123@gmail        '
const userEmail3 = '    alice@gmail.com.     '

userEmail1.trimStart() // 'eddy@gmail.com'
userEmail2.trimEnd() // 'johnny123@gmail'
userEmail3.trim() // 'alice@gmail.com'

組合多個字串

使用 .concat() 方法

concat(str1, str2, ..., strN)
const str1 = 'Hello'
const str2 = 'World'
str1.concat(', ', str2, '!') // 'Hello, World!''

const strArray = ["Hello", " ", "World", "!"];
"".concat(...strArray); // 'Hello World!'

使用相加運算子 (+)

'Hello ' + 'everyone' // 'Hello everyone'

取得字串中的特定字元(character)

使用 .charAt(index) 方法

"cat".charAt(1) // 'a'

將字串當作類陣列(array-like)物件,直接存取字串中對應的索引值

"cat"[1]; // 'a'

樣板字面值 Template Literals (Template Strings 樣板字串)

introduced in ECMAScript 6 (2015)

const name = "Sally";
const age = 34;
const pet = "dog";

// const greeting = "Hello " + name + "you seem to be doing" + greeting + "!";

//Using template strings
const greeting = `Hello ${name} you seem to be ${age-10}. What a lovely ${pet} you have.`;
  • use ``

  • ${value} for variables

預設參數 default arguments

funtion greet (name='',age=30, pet='cat'){
    return `Hello ${name} you seem to be ${age-10}. What a lovely ${pet} you have.`
}

greet(); //didn't provide value, use default arguments
"Hello you seem to be 20. What a lovely cat you have"

greet("john", 50, "monkey") // default get ignored, passed the parameters
"Hello john you seem to be 40. What a lovely monkey you have"

Number 數字

Number values represent floating-point numbers like 37 or -9.25.

使用 Number() 將參數轉換成數字

可以使用 Number(value) 將參數轉換成數字,如果參數無法被轉換成數字則返回值為 NaN

Number('123') // 123
Number('123') === 123 // true

Number('hello') // NaN
Number(undefined) // NaN
Number(null) // 0
Number(true) // 1
Number(false) // 0

使用 Number() 返回的值,根據資料的型別有所不同:

  • Numbers are returned as-is.

  • Undefined turns into NaN.

  • Null turns into 0.

  • Boolean:

    • true turns into 1

    • false turns into 0.

  • Strings: converted by parsing them as if they contain a number literal. Parsing failure results in NaN.

  • BigInts throw a TypeError to prevent unintended implicit coercion causing loss of precision.

  • Symbols throw a TypeError.

  • Objects are first converted to a primitive by calling their [@@toPrimitive]() (with "number" as hint), valueOf(), and toString() methods, in that order. The resulting primitive is then converted to a number.

數字的範圍

The Number type has exactly 18,437,736,874,454,810,627 (that is, 2^64 - 2^53 + 3) values, representing the double-precision 64-bit format IEEE 754-2019 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9,007,199,254,740,990 (that is, 2^53 - 2) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special NaN value. (p.76)

Javascript 的 Number 是用 64 bit 來存,遵循的規格是 IEEE 754-2019。64 bit 是個有限的空間,但數字卻是無限的。這代表 Number 是一個有限範圍的數字(有儲存上限)。

我們可以使用 Number.MAX_SAFE_INTEGAR 來拿到正整數的安全範圍,即:2^53 - 1,也就是 9007199254740991

安全範圍內的數字可以被明確地表示&比較,一但超出這個範圍就有可能會有誤差。

const a = 9007199254740992
const b = a + 1
console.log(a === b) // true
console.log(b) // 9007199254740992

console.log(9007199254740992 === 9007199254740993) // true
console.log(Number('9007199254740993')) // 9007199254740992

Infinity

Infinity-Infinity 是 JavaScript 的一個 global 屬性,用來表示無限大無限小

console.log(typeof Infinity) // number

console.log(100000000000 > Infinity) // false
console.log(-99999 > -Infinity) // true

var x =  2 / 0 // x = Infinity
var y = -2 / 0 // y = -Infinity

NaN (Not a Number)

NaN 也包含在 Number 型別中。

💡
關於 NaN 的詳細說明:Javascript - NaN (Not a Number)
let age = 18;
let myName = "Tom";

let total = myName * age; // NaN
console.log(typeof total); // number

BigInt

BigInt 是 ES2020 新增的型別:

BigInt values represent numeric values which are too large to be represented by the number primitive.

這邊的 "too large" 指的是超越了上述 Number 所說的安全範圍 MAX_SAFE_INTEGER,也就是大於 2^53 的整數。

透過在數值尾端加上一個 n 或呼叫 BigInt(value) 來生成一個 BigInt

const bigNum = 123456789n
const verybigNum = BigInt(9007199254740991)
const bigStr = BigInt('9007199254740991')

上面當 Number 超越安全範圍時導致運算有誤差的例子,只要改為使用 BigInt 就不會有問題:

const a = 9007199254740992n
const b = a + 1n
console.log(a === b) // false
console.log(a) // 9007199254740992n
console.log(b) // 9007199254740993n

使用 typeof 檢查型別時,BigInt 會回傳 "bigint"

typeof 1n === "bigint"; // true
typeof BigInt("1") === "bigint"; // true
💡
NumberBigInt 不能混合計算,如果要運算,必須先被轉換成同樣的型別。然而, BigInt 在被轉換成 Number 時可能會遺失部分精度的資訊。因此,建議當數值會超過 2^53 時只使用 BigInt ,而不要在兩者之間進行轉換。

Symbol

Symbol 是 ES6 新增的型別。

The Symbol type is the set of all non-String values that may be used as the key of an Object property.

Each possible Symbol value is unique and immutable.

Each Symbol value immutably holds an associated value called [[Description]] that is either undefined or a String value. (p.73)

由此我們可以知道:

  • Symbol 是拿來當作物件的 key 使用的

  • Symbol 是除了 string 以外唯一可以被用來當作 object 的 key 的東西

  • 每一個 Symbol 的值都是獨一無二的;你無法建立兩個一樣的 Symbol

建立 Symbol

Every Symbol() call is guaranteed to return a unique Symbol.

使用 Symbol() 建立一個獨一無二的 Symbol:

const sym1 = Symbol()
const sym2 = Symbol("foo")
const sym3 = Symbol("foo")

console.log(sym2 === sym3) // false,每個 Symbol 都是獨一無二的
console.log(sym2.description)// foo,用這樣來取得敘述

const obj = {}
obj[sym2] = 'hello' // 可以當成 key 使用
console.log(obj[sym2]) // hello

Symbol 獨一無二的特性讓它在被當成物件的 key 使用時,不需要擔心會與其他的 key 衝突。

取得 Symbol

Every Symbol.for("key") call will always return the same Symbol for a given value of "key"

使用 Symbol.for("key") 來獲取相同的或創建一個新的 Symbol:

const sym1 = Symbol.for('a')
const sym2 = Symbol.for('a')
console.log(sym1 === sym2) // true

When Symbol.for("key") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned.

這時候 sym1 === sym2 為什麼又是 true 了呢?

實際上,當你呼叫 Symbol.for("key") 函式時,Javascript 會先在全域的 Symbol registry 裡找該 key 的 Symbol。

如果有找到,則會回傳該 Symbol;如果沒有找到,就會建立一個新的 Symbol ,並且寫入 Symbol registry 中,然後回傳該 Symbol。

隱藏資訊

Symbol 的另一個特性是隱藏資訊,當你在用 for...in loop 的時候,如果 key 是 Symbol 型別,並不會被列出來:

const obj = {
    a: 1,
    [Symbol.for('b')]: 2
}

for(let key in obj) {
    console.log(key) // a
}

Global Symbol registry

要建立全域(甚至跨文件、跨域)的 Symbol ,可以透過使用 Symbol.for()Symbol.keyFor() 來註冊和取得 global registry 裡的 Symbol。

需注意,這裡說的 global symbol registry 只是一個抽象的概念,並真的存在於 Javascript 的資料結構中。

Symbol.for(tokenString) takes a string key and returns a symbol value from the registry.

Symbol.keyFor(symbolValue) takes a symbol value and returns the string key corresponding to it.

Each is the other's inverse

使用 Symbol.for(tokenString)

  • param: string key

  • return: symbol value

使用 Symbol.keyFor(symbolValue)

  • param: symbol value

  • return: string key

Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true

Object

An Object is logically a collection of properties.

Properties are identified using key values. A property key value is either an ECMAScript String value or a Symbol value. All String and Symbol values, including the empty String, are valid as property keys. A property name is a property key that is a String value.

Property keys are used to access properties and their values(p.89)

  • 物件是由許多屬性(properties)所組成

  • 物件的屬性也就是所謂的 key,使用 key 來取得物件的屬性和對應的值

  • key 一定要是 stringsymbol;即使是空字串也可以拿來當作 key

var user = {
    name: "John",
    age: 18,
    hobby: "soccer",
    isMarried: false,
    spells: ["shazam", "abrakadra", "boo"],
    shout: function(){
        console.log("AHHHHH!");
    }
}

console.log(user.name) // John
console.log(user[age]) // 18

取得物件的屬性與值

使用 obj.keyobj['key'] 來獲取物件的屬性與值

刪除物件的屬性與值

使用 delete 運算子移除物件的屬性:delete obj.key,其返回值在大多數情況下皆為 true

let obj = {
    name: 'John',
    age: 18
}

delete obj.name // true
delete obj.hobby // true

console.log(obj)// {age: 18}

Ref