# Javascript - Data Types & Strings ###### tags: `Javascript`, `data type`, `string` ## 資料型別 Data types ### 原始資料型別 Primitive Data Type 原始資料型別都有「原始值 (primitive value)」,且原始值是不可被更改的。 Javascript 的七種原始資料型別: - Undefined - Null - Boolean: `true` / `false` - String - Number - `NaN` - Not a Number - [BigInt (ES2020)](https://developer.mozilla.org/en-US/docs/Glossary/BigInt) - [Symbol (ES6)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 除此之外,還有第八種: ### 物件資料型別 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. ```javascript 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 ``` :::warning 只有 `typeof` `null` 會返回 `"object"`。要確認一個值是否為 `null` 時,可使用 `=== null` 來檢驗該值是否為 `null`。 Ref: [Javascript - Null, undefined, not defined 差異](/1VJHXV3gRfmNjJLhpZ134g) ::: ## Dynamic & Weak Typing ### 動態型別語言 (dynamic language with dynamic types) > JavaScript is a **dynamic** language with **dynamic types**. - 宣告變數時不必特別宣告變數的型別 - 可以以不同的型別使用同一個變數 ```javascript 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) ```typescript 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 ``` ### 自動轉型:字串與數字相加、相減 ```javascript 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"` ,因此相加結果 `total` 為 `Tom18` - `+` :當數字與字串**相加**,數字被轉型成**字串** - `-` :當數字與字串**相減**,字串被轉型成數字 ```javascript // 數字與字串相加 "37" + 7 // "377" 30 + "7" // "307" // 數字與字串相減 "37" - 7 // 30 30 - "7" // 23 ``` ### 字串轉數字 #### `parseInt(string)`、`parseFloat(string)` ```javascript let age = "30"; // type: string let age = parseInt("30")// type: number let age = parseInt("hello"); // NaN; 無法轉型成數字 console.log(typeof parseInt("hello")); // number --> NaN 屬於 number ``` #### `Number()` ```javascript Number("123"); // returns the number 123 Number("123") === 123; // true Number("unicorn"); // NaN Number(undefined); // NaN Number(null); // 0 Number(true) // 1 Number(false) // 0 ``` #### 使用一元正、負號運算子(`+`、`−`) Unary Operator 除了上述的方法外,還可以運用 Javascript 的[一元正號運算子(Unary Plus)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus)、[一元負號運算子(Unary Negation)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus),將其轉換成數字。 在**字串前方加上 `+` 或 `-` 來將字串轉型為數字並取其數值**: ```javascript '1.1' + '1.1' // '1.11.1';string,字串相加,未被自動轉型 // 使用一元運算子 +'1.1' + +'1.1' // 2.2;number -'1.1' - -'1.1' // 0;number const x = "1" const y = -1 const z = -x console.log(+x) // 1 console.log(+y) // -1 console.log(+z) // -1 console.log(-z) // 1 console.log(+'') // 0 console.log(+true) // 1 console.log(+false) // 0 console.log(+'hello') // NaN ``` ### 數字轉字串 #### `.toString()` > The `toString()` method of [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) values returns a string representing this number value. ```javascript numObj.toString([radix]) ``` - radix 參數(optional):介於 2~36 的整數,用來指定基數,預設為 10 - 2 - 數字以二進位值顯示 - 8 - 數字以八進位值顯示 - 16 - 數字以十六進位值顯示 ```javascript var count = 10; count.toString() // '10'; type: string (17).toString() // '17'; type: string (17.2).toString() // '17.2'; type: string var x = 6; x.toString(2) // '110' 二進位表示法 (254).toString(16) // 'fe' 十六進位表示法 (-10).toString(2) // '-1010' (-0xff).toString(2) // '-11111111' ``` ## Undefined `undefined` 是型別 Undefined 的值,且 Undefined 這個型別就只有 `undefined` 這個值。 當一個變數沒有被賦予任何值的時候,Javascript 會給予它一個預設值 `undefined`: ```javascript var a console.log(a) // undefined ``` 使用 `typeof undefined` 也會得到 `undefined`: ```javascript var a if(typeof a === 'undefined'){ console.log('a is undefined'); // a is undefined } ``` 注意: `typeof` 回傳的值是字串,所以 `'undefined'` 需以字串表示 ## Null `null` 是型別 Null 的值,且 Null 這個型別也就只有 `null` 這個值。它的意思是「存在但沒有值」。`null` 是刻意讓開發人員用來宣告「空值」用的,Javascript 並不會將值設定為 `null`。 使用 `typeof Null` 會得到 `object`: ```javascript console.log(typeof null) // 'object' ``` 這是 Javascript 最著名的 bug 之一,詳細情形可以看這篇:[The history of “typeof null”](https://2ality.com/2013/10/typeof-null.html) :::info 關於 **Null / Undefined / Not defined** 的詳細說明與比較,有另外整理了一篇:[Javascript - Null, undefined, not defined 差異](/1VJHXV3gRfmNjJLhpZ134g) ::: ## Boolean 布林值 Javascript 的 Boolean 用來表示真或假,只會有兩種值: - `true` - 表示真 - `false` - 表示假 在 JavaScript 中,**只有這些值會被當作是 `false`**: - `undefined` - `null` - `false` 值 - `0`、`-0` (數值) - `NaN` - `''`、`""` 空字串 **除了這些值以外的值都會是 `true`!** ![](https://hackmd.io/_uploads/Bkka8Peah.png) 使用 `Boolean()` 可以用來將其他的資料型態轉型 (type conversion) 成布林值型態: ```javascript 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」]((https://developer.mozilla.org/en-US/docs/Glossary/Falsy))值;會變成 `true` 的則稱作 [「truthy」](https://developer.mozilla.org/en-US/docs/Glossary/Truthy)值。 ### Boolean 的方法 > 全域的 Boolean 物件自身沒有任何方法,它只有從原型鏈繼承而來的方法。 > > 所有 Boolean 實體(Instance) 會繼承 [Boolean.prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) 。和所有建構式一樣,原型物件會指派給實體那些繼承的屬性和方法。 #### `Boolean.prototype.toString()` 轉換 將布林值轉換成字串(`'true'`, `'false'`) ```javascript let x = new Boolean(true) x.toString() // 'true';字串 ``` #### `Boolean.prototype.valueOf()` 取得布林物件的原始值 (`true`, `false`) ```javascript let y = new Boolean() y.valueOf() // false let z = new Boolean('hello') z.valueOf() // true ``` ### 原始型別布林值 vs. 布林物件 在判斷式中,任何物件只要不是 `undefined` 或 `null`,儘管是值為`false` 的 Boolean 物件,都會被轉換成 `true`。 > Do not use the `Boolean()` constructor with `new` to convert a non-boolean value to a boolean value — use `Boolean` as a function or a **double NOT** instead 注意,不要用 Boolean 物件(`new Boolean()`)將非布林值轉換成布林值,應將 Boolean 視為函式(`Boolean()`)去轉換非布林值: ```javascript const good = Boolean(expression); // 可以用 Boolean() const good2 = !!expression; // 可以用 double not const bad = new Boolean(expression); // 不要使用 ``` 舉例來說,下列的 if 判斷式中的布林值即為 `true`: ```javascript var x = new Boolean(false); // if (x) { // this code is executed } ``` 以上的例子是建立了一個新的 Boolean 物件,和 Boolean 原始型別沒有關連,因此 x 為 truthy。 下列的 if 判斷式才會正確地將其視為 `false`: ```javascript var x = false; if (x) { // this code is not executed } ``` ## 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 的所有資料型別](https://blog.huli.tw/2022/02/25/javascript-how-many-types/)」的翻譯蒟蒻: > 字串就是一連串的 16-bit 的數字,而這些數字就是 UTF-16 的 code unit,字串的長度最多則是 2^53 - 1。 ### 字串處理的實用屬性與方法 #### `.length` 屬性,獲取字元串長度 > This property returns **the number of code units** in the string String 的 `length` 屬性(property)包含著該字元串的長度(包含空白與標點符號)。 ```javascript const str = 'What\'s up?' console.log(str.length) // 10 ``` 空字串的 `length` 為 `0`: ```javascript const empty = '' console.log(empty.length) // 0 ``` 靜態屬性 `String.length` 與字串長度無關,它是 String 函數的[參數數量](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/length),也就是 1 為字串的 `length` 屬性重新賦值不會有任何作用(在 strict mode 下會報錯): ```javascript 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' ``` Javascript 是使用 UTF-16 編碼,每個 Unicode 字符可以編碼成一到兩個碼元(code unit)。也就是說,對於 Javascript 來說所有的字串都是一系列的 UTF-16 碼元。 `length` 指的是碼元的個數(code units)而不是字元數(characters),因此 `length` 返回的值可能與字串中 Unicode 實際的字符數量不一致。 如果想要取得正確的字元數,可以使用[迭代器(iterator)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length),將字串分隔成字元。或使用 `Array.from(str).length`。 ```javascript const emoji = '😄' console.log(emoji.length) // 2 console.log([...emoji].length) // 1 ``` #### `.trim()` 方法,去掉字串頭尾空白 - `trim()`:去掉前後空白 - `trimStart()`:去掉起始空白 - `trimEnd()`:去掉結尾空白 ```javascript 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' ``` #### 組合多個字串 1. 使用 `.concat()` 方法, ```javascript concat(str1, str2, ..., strN) ``` ```javascript const str1 = 'Hello' const str2 = 'World' str1.concat(', ', str2, '!') // 'Hello, World!'' const strArray = ["Hello", " ", "World", "!"]; "".concat(...strArray); // 'Hello World!' ``` 2. 使用相加運算子 (`+`) ```javascript 'Hello ' + 'everyone' // 'Hello everyone' ``` #### 取得字串中的特定字元(character) 1. 使用 `.charAt(index)` 方法: ```javascript "cat".charAt(1) // 'a' ``` 2. 將字串當作類陣列(array-like)物件,直接存取字串中對應的索引值 ```javascript "cat"[1]; // 'a' ``` ### 樣板字面值 Template Literals (Template Strings 樣板字串) > introduced in ECMAScript 6 (2015) ```javascript 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** ```javascript 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(value)` 將參數轉換成數字,如果參數無法被轉換成數字則返回值為 `NaN`: ```javascript 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`。 安全範圍內的數字可以被明確地表示&比較,一但超出這個範圍就有可能會有誤差。 ```javascript 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 屬性,用來表示無限大或無限小。 ```javascript 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(Not a Number) 顧名思義就是非數字。 - NaN 的型別是 Number: `typeof NaN // Number` - `NaN` 不等於任何值,也不等於自己`NaN === NaN // false` ```javascript typeof NaN // number NaN === NaN // false ``` 關於 NaN 的詳細說明:[Javascript - NaN (Not a Number)](/9FVpL8cLTM2jH6tBvUkwUA) ## 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`: ```javascript const bigNum = 123456789n const verybigNum = BigInt(9007199254740991) const bigStr = BigInt('9007199254740991') ``` 上面當 Number 超越安全範圍時導致運算有誤差的例子,只要改為使用 `BigInt` 就不會有問題: ```javascript const a = 9007199254740992n const b = a + 1n console.log(a === b) // false console.log(a) // 9007199254740992n console.log(b) // 9007199254740993n ``` 使用 `typeof` 檢查型別時,一個`BigInt` 會回傳 `"bigint"`: ```javascript typeof 1n === "bigint"; // true typeof BigInt("1") === "bigint"; // true ``` ::: info `Number` 和 `BigInt` 不能混合計算,如果要運算,必須先被轉換成同樣的型別。然而, `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:** ```javascript 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:** ```javascript 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 型別,並不會被列出來: ```javascript 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 ```javascript 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` 一定要是 `string` 或 `symbol`;即使是空字串也可以拿來當作 key ```javascript var user = { name: "John", age: 18, hobby: "soccer", isMarried: false, spells: ["shazam", "abrakadra", "boo"], shout: function(){ console.log("AHHHHH!"); } } var list = [ { username:"andy", password: "secret" } ] console.log(obj.name) // John console.log(obj[age]) // 18 ``` ### 取得物件的屬性與值 使用 `obj.key` 或 `obj['key']` 來獲取物件的屬性與值 ### 刪除物件的屬性與值 使用 `delete` 運算子移除物件的屬性:`delete obj.key`,其返回值在[大多數情況下](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#return_value)皆為 `true`。 ```javascript let obj = { name: 'John', age: 18 } delete obj.name // true delete obj.hobby // true console.log(obj)// {age: 18} ``` ## Ref - [來數數 JavaScript 的所有資料型別](https://blog.huli.tw/2022/02/25/javascript-how-many-types/)