資料型態
JavaScript 有 7 種資料型態,而這些型態又可被分列到兩大類:原始型態以及物件型態。
原始型態(primitive type)
Immutable 不能改變 (註一)(註二)
- null
- undefined
- string
- number
- boolean
- symbol(ES6)
物件型態(object)
只要不歸類到原始型態,皆屬於物件型態
- array
- function
- date
...
(註一)原始型態與物件型態的差別
var str = 'hello'
str.toUpperCase() // 不能改變 str 這個變數
console.log(str) // hello
===
var str = 'hello'
var newStr = str.toUpperCase()
console.log(str, newStr) // hello, HELLO
- 舉 array 的例子
var arr = [1]
arr.push(2)
console.log(arr) // [1, 2]
關於賦值
(註二)原始資料型態直接存放數值,物件資料型態存放記憶體位置
var a = 10
var b = a
console.log(a, b) // 10, 10
b = 200
console.log(a, b) // 10,200
0x01: {
number:10
}
obj: 0x01 // 存放記憶體位置
obj2: 0x01 // 宣告 var obj2 = obj,把 obj 的記憶體位置,複製到 obj2
===
var obj = {
number: 10
}
var obj2 = obj
console.log(obj, obj2) // 印出{number:10}, {number:10}
obj2.number = 20 // 存取 obj2 指向記憶體位置下的屬性,來做調整
console.log(obj, obj2) // 印出{number:20}, {number:20}
- 同樣適用在 array
0x10: []
arr: 0x10
arr2: 0x10
===
var arr = []
var arr2 = arr
console.log(arr, arr2) // 印出[], []
arr.push('arr2')
console,log(arr, arr2) // 印出['arr2'], ['arr2']
- 更進一步
0x10: []
0x20: ['arr2']
arr: 0x10
arr2: 0x20
===
var arr = []
var arr2 = arr
console.log(arr, arr2) // [], []
arr2 = ['arr2'] // 指向一個新記憶體位置,放新的值
console,log(arr, arr2) // [], ['arr2']
= 與 == 的差別
var a = 10
if (a == 20) {
console.log(123) // 1. a(10) 不等於 20 2. 無印出
}
===
var a = 10
if (a = 20) {
console.log(123)
// 執行步驟 1. a 被重新賦值 2. 判斷是 a 是不是 true 3. 再印出
}
== 與 === 的差別
- 轉換型態
console.log(2 == '2') // true,轉換型態
console.log(2 === '2') // false,不會幫你轉換型態
- 相同型態比較
var obj = {
number: 1
}
var obj2 = obj
obj2.number = 2
console.log(obj === obj2) // true
// 用兩個等號還是三個等號都可以,因為沒有型態轉換的問題,相比的是記憶體的位置
- 再次強調:物件型態與記憶體位置關係
0x01: [1]
0x02: [2]
arr: 0x01
arr2: 0x02
===
var arr = [1]
var arr2 = [1]
console.log(arr === arr2) // false
console.log([] === []) // false,記憶體位置不一樣
Special Case- NaN
- NaN,意即 not a number,屬於數字型態,但又不是真的數字
console.log(typeof NaN) // number
===
var a = Number('hello')
console.log(a) // NaN
console.log(typeof a) // number
- 超級特例
NaN 不等於任何型態,甚至是她本身自己。
var a = Number('hello')
console.log(a === a) // false
- 如何檢查 NaN
var a = Number('hello')
console.log(isNaN(a))
let 與 const
ES6 引進兩個宣告變數的方式。
- constant 常數(const)
- variable 變數(var & let 差別在作用域)
const
// 宣告 const 時,就要先給初始值
const b
b = 20
===
// 因為是常數,不能改變他的值
const a = 20
a = 30
再次來到 object 的世界
- const 與 物件型態關係
0x01:{
number: 2
}
obj: 0x01
const obj = {
number: 1
} // 指向同個記憶體,不會報錯
const arr = [1, 2, 3]
arr[0] = 3 // 沒有改變記憶體位置,只是改變底下的值
- 報錯的例子
0x01:{
number: 2
}
obj: 0x01
const obj = {
number: 1
}
obj = {number: 2} // 改變記憶體位置,會報錯
變數的生存範圍- Scope
Scope 又稱為作用域、變數的生存範圍。
談談 ES6 以前的作用域
- 作用域的基本單位:function
function test() { // 產生了 test 的 scope
var a = 10 // a 宣告在 test scope 裡面
console.log(a)
}
// 所以只有在這個 function 裡面,a 才能被其他人看到
test()
console.log(a) // a is not defined
// 一但跑出了作用域外,找不到 a 的變數宣告,印出 a 就會報錯
- 全域變數:global variable
var a = 10 // 所有人都得到,以檔案為單位
function test() {
console.log(a)
}
test()
console.log(a)
// 1. 會先在 function 裡面找,有沒有宣告 a
// 2. 如果沒有,就往上一層找(也就是 global),找到了 a = 10
- 綜合練習:function 作用域 & global variable
第一題:
第二題:var a = 20 function test() { var a = 10 console.log(a) } test() console.log(a) // 印出 10, 20
第三題:var a = 20 function test() { a = 10 // 並非變數宣告,往上一層找變數宣告,然後去改變 a 值 console.log(a) } test() console.log(a) // 印出 10, 10 // 1. 會先在 function 裡面找,有沒有宣告 a // 2. 發現沒有之後,往上層找,找到 global variable // 3. 找到之後,去改變 a 值
第四題:function test() { a = 10 console.log(a) } test() console.log(a) // 印出 10, 10 // 1. 會先在 function 裡面找,沒有的話,往上層找 // 2. global variable 也找不到的話,就會直接在宣告一個全域變數`var a`
第五題:var a = 'global' function test() { var a = 'test scope a' var b = 'test scope b' console.log(a, b) // 印出 test scope a, test scope b function inner() { var b = 'inner scope b' console.log(a, b) // 印出 test scope a, inner scope b } inner() } test() console.log(a) // global // 形成 scope chain // inner scope => test scope => global scope
第六題:var a = 'global' function change() { var a = 10 test() // 儘管 test() 被呼叫的地方是在 change scope } function test() { console.log(a) // 印出 } change() // O test scope -> global scope // X test scope -> change scope -> global scope
第七題:var a = 'global' function change() { var a = 10 function inner() { var a = 'inner' test() } inner() } function test() { console.log(a) // 印出 global } change() // 作用域的判斷,與 function 在哪裡被呼叫,一點關係都沒有 // 作用域的判斷,唯一判斷是:變數在哪裡被宣告,scope chain 就被定義好
var a = 'global' function change() { var a = 'change' function inner() { var a = 'inner' test() } function test() { console.log(a) // 印出 change } inner() } change() // test chain -> change chain -> global chain // 如何決定作用域,看程式碼放置的位置
談談 ES6 之後的作用域
作用域的概念不同,跟新引進的兩種變數宣告有關:let & const
- before ES6:用
var
來宣告變數,作用域的基本單位是 function scope after ES6:用
let
const
宣告變數,作用域為 block scope舉例來說
- 第一題
function test() { var a = 60 if (a === 60) { var b = 10 } console.log(b) } test() // 印出 10 // 作用域以 function 為主 // `var b = 10`會存在 test() 裡面
- 第二題
function test() { var a = 60 if (a === 60) { let b = 10 } console.log(b) } test() // 印出 b is not defined // 變數 b 只能在 if 的 block 可以被用到 // 作用域以 block {} 為主
- 第三題
function test() { for(var i=0; i<3; i++) { //執行到 i=3 時,是 false,然後跳出迴圈 console.log('i:', i) } console.log('final value:', i) } test() // 印出 i:0, i:1, i:2, final value:3 // 如果 var 改成 let,會印出 i:0, i:1, i:2, i is not defined
- 第一題
如何知道變數的資料型態是什麼?
使用 typeof
console.log(typeof 10) // number
console.log(typeof 'hello') // string
console.log(typeof undefined) // undefined
console.log(typeof true) // boolean
令人混淆的地方
console.log(typeof null) // object
console.log(typeof []) // object
console.log(typeof function(){}) // function
var a // 宣告一個變數
console.log(typeof a) // undefined
// 變數都沒有宣告
console.log(typeof a) // undefined
更進一步
- 判斷 array
console.log(Array.isArray([])) // true
- 判斷 function
console.log(typeof function(){}) // function
較為精準的判斷方式
console.log(Object.prototype.toString.call())
- 檢查變數有沒有被使用
console.log(a) // 出錯,變數沒宣告:a is not defined
===
if (typeof a !== 'undefined') {
console.log(a) // 不會出錯,程式照跑
}
補充資料
- 更多 == v.s. === 比較:https://dorey.github.io/JavaScript-Equality-Table/