JavaScript淺嚐紀錄No.3-JS變數值為基本型別與影響

ECMAScript標準定義的七種資料型別

基本型別 (primitive type)

  1. String-字串”hello”。
  2. Number-數字 (在 -(253 -1) and 253 -1 之間的數字), +Infinity、-Infinity、NaN也是數字型別。
  3. Boolean-布林值true / false。
  4. Undefined-已宣告,但未賦值的變數。
  5. Null-空值。
  6. Symbol()-ES6新創的,還不熟。

物件(object)

基本資料型態以外的都是物件型別。

  • object-ket-value組合{name:’Celeste’,age:18,…}。
    • Array-陣列[1,2,3,…],使用typeof foo() {}時,會顯示’object’。
    • Function-使用typeof foo()時,會顯示”function”。

參考資料:
Fooish - JavaScript ES6 Symbol 資料型態
MDN-JavaScript 的資料型別與資料結構

變數儲存By value?By reference?

先簡單解釋一下什麼是by value跟by reference的差異:

  • By value

    傳遞的值的方法是透過複製「值」,在傳遞基本型別值時都是透過「by value」。簡單的測試一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let a = 1
    let b = a
    console.log(a) //1
    console.log(b) //1
    console.log(a === b) //true

    a = 2 // a重新賦值
    console.log(a) //2
    console.log(b) //1
    console.log(a === b) //false

    變數a重新賦值後並不會更改到變數b的值,這是因為變數a值跟變數b值儲存位置不同,變數b僅僅只是複製了變數a的值(By value)。

  • By reference

    物件通常是透過「By reference」傳遞儲存位址。先來看看下面例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let obj = {
    a: 1
    }
    let main = obj
    main.a = 2
    main.b = 3
    console.log(obj) //{a:2,b:3}
    console.log(main) //{a:2,b:3}
    console.log(obj === main) // true

    變數main修改了值卻影響了變數obj,這都是因為變數obj跟變數main都是指向同一個儲存位址(有點像是我們的地址),地址不會變,但是地址裡面的東西可以被更改(請注意是更改,像是這個地址裡的戶口會有變更,但不會影響這個地址的位置)。

    將變數obj代入function的參數,也是透過「By reference」,將變數obj的儲存位址複製給這個參數,所以調用function的話,也會變更原本的物件值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let obj = {
    a: 1
    }
    function change(main) {
    main.a = 2
    main.b = 3
    }
    change(obj)
    console.log(obj) //{a: 2, b: 3}
    • 需要特別注意,當物件被重新賦值的話,這個物件會被儲存到新的位址(有點像是搬家了,但原本的地址還是存在)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      let address = {
      dad: 'Paul'
      }
      let main = address
      main.dad = 'Mark'
      main.mom = 'Mary'
      console.log(address) //{dad: "Mark", mom: "Mary"}
      console.log(main) //{dad: "Mark", mom: "Mary"}
      console.log(address === main) //true

      //address重新賦值
      address= {
      dad: 'Mark',
      mom: 'Mary'
      }
      console.log(address) //{dad: "Mark", mom: "Mary"}
      console.log(main) //{dad: "Mark", mom: "Mary"}
      console.log(address === main) //false

      注意到最後一行console.log(address=== main)顯示的是false,但上面的console.log(address=== main)卻是true,這是因為上面的變數main只是將儲存位址裡的值做了修改(有點像是地址裡的戶口中人的名字作改變並且增加人口),但是將變數address重新賦值就代表有了新的儲存位址(類似搬家了,並且新家裡也是有同樣名字的dad跟mom),但變數main還是指向原本的的位址,所以變數address的儲存位址不等於變數main的。
      在重新賦值的情況下內容當然是可以改變,只是為了說明2個變數雖然內容相同但是物件是透過「By reference」儲存的,即使內容相同但是儲存位置不同,那麼2者比較就會是false

      如果不想要重新賦值原本設定好的物件,請一定要用const宣告喔!

參考資料:

Programming with Mosh-Value Types and Reference Types in JavaScript

PJCHENder-[筆記] 談談 JavaScript 中 by reference 和 by value 的重要觀念

型別強制轉換(Type coercion)

MDN對JavaScript的型別解釋:JavaScript 是弱型別,也能說是動態的程式語言。這代表你不必特別宣告變數的型別。程式在運作時,型別會自動轉換。這也代表你可以以不同的型別使用同一個變數。

來分開解釋上面的意思:

  1. 因為JavaScript 的型別針對的是變數值,而非是變數,所以不必特別宣告變數的型別。
  2. 因為JavaScript 是一個對型別轉換非常寬鬆的語言,JavaScript 引擎很少會直接顯示錯誤,它會盡可能接受你寫給它的程序,甚至自己猜測你的意思(要數字、字串或布林值等等),幫你做型別自動轉換(像是把數字變字串、把false變成數字0)。

Falsy家族

falsy 值 (虚值) 是在 Boolean 上下文中认定为 false 的值。在型別轉換時,被視為0。

  • false。
  • 0。
  • “”、’’、`` -空字串。
  • null。
  • undefined。
  • NaN。

Falsy家族跟型別轉換有甚麼關係呢?看看下面的程式碼:

1
2
3
4
5
6
7
8
9
10
//JS的型別是針對變數值,所以是值的型別再更換
let foo = 42; // foo 目前是數字
foo = 'bar'; // foo 變成字串
foo = true; // foo 變成布林值
//型別轉換
console.log(10 * null) // 0, null 被當成零
console.log('5' - 1) // 4, '5' 轉換成數字
console.log('5' + 10) // '510', 10 被轉換成字串
console.log('five' * 2) // NaN, NaN 代表「不是數字」
console.log(false + 1) // 1, false 轉換成 0

使用「===」或「!==」來避免型別轉換發生的問題

兩種等號

==(相等)

型別會自動轉換,所以相互比較的值,只要內容相同,就會呈現true。

1
2
3
4
5
6
7
8
9
10
11
12
//==
//例1let A = 1 //數字
let B = false //布林值
let result = (A + B) == "1" //布林值型別轉換後true=1,false=0,所以會變成 1 + 0 == 1
console.log(typeof(A+B)) //"number"
console.log(result) //true 1 == "1"會是true

//例2let A = 1 //數字
let B = "2"//字串
let result = (A + B) == 12 //數字1會變成字串,"1" + "2" ==12
console.log(typeof(A+B)) //"string"
console.log(result) //true "12"==12 內容相同,型別不同沒關係

=== (嚴格相等)

型別無法自動轉換,也因此相互比較的值,除了內容相同外,型別也必須一樣,才會呈現true。

1
2
3
4
5
6
7
8
9
10
11
12
13
//===
//truelet A = 1 //數字
let B = 2 //數字
let result = (A + B) === 3 //1 + 2 === 3
console.log(typeof(A+B)) //"number"
console.log(result) //true

//false
let A = 1 //數字
let B = "2" //字串
let result = (A + B) === 12 //"1" + "2" === 12
console.log(typeof(A+B)) //"string"
console.log(result) // false "12"===12 型別不同,就會是false

兩種不等號

!= (不相等)

相互比較的值,內容相同,但型別不同,就會呈現true。

1
2
3
4
5
//!=
let A = 1 //數字
let B = "1" //字串
let result = A != B //1 !="1"
console.log(result) //false 內容相同,不論型別怎麼樣,就會認定2個值是一樣的。

!== (嚴格不相等)

相互比較的值,內容相同,但型別不同,就會呈現true。

1
2
3
4
5
6
7

// !==
//true
let A = 1 //數字
let B = "1" //字串
let result = A !== B // 1 !== "1"
console.log(result) //true 內容相同,但型別不同,會呈現true。

JavaScript 的 == 和 != 會「先將兩個值轉換成相同的資料型別」,請記得永遠使用 === 和 !==。參考MDN-JavaScript 的資料型別與資料結構、Falsy。

參考資料:
MDN-JavaScript 的資料型別與資料結構
Falsy