給自己看的 JS 進階-變數


Posted by 生菜 on 2020-10-23

耶耶「給自己看的」系列重開 XD

發現之前做過的筆記有一點點重覆:
[Week2] 給自己看的 JavaScript 筆記 - 迴圈、函式、其他觀念

變數型態

七種資料型態:
primitive type 原始型態

  1. null
  2. undefined
  3. string
  4. number
  5. boolean
  6. symbol(ES6)

其他都是 boject 物件

  1. object(array, function, date ...)

原始型態都是 Immutable ,也就是不能改變。

let a = 10
a = 20

是重新賦值而非改變,改變例如:

var str = 'hello'
str.toUpperCase()
consoel.log(str) // 'hello'

上面例子中第二行雖然會回傳新字串 HELLO ,但並不會影響 str 本身。

var str = 'hello'
var newStr = str.toUpperCase()
consoel.log(newStr) // 'HELLO'

array 因為不是原始型態,因此可以被改變:

var arr = ['a']
arr.push('b')
console.log(arr) / ['a', 'b']

偵測型態
typeof 可以偵測型態:

console.log(typeof 10) // number
console.log(typeof []) // object
console.log(typeof function() {}) // function

不過有些小 bug 例如:

console.log(typeof null) // object

MDN - typeof 對此解釋如下:

自從 JavaScript 一開始出現, JavaScript 的值就總以型別標簽跟著一個值的方式表示。物件的型別標簽是 0. 而 null 這個值是使用 NULL 指標 (在大部份平台上是 0x00) 來表示. 因此, null 看起來像是一個以 0 為型別標簽的值, 並使得 typeof 傳回不甚正確的結果.

typeof 時常用在檢測錯誤上。例如 undefined 不能直接被 console.log 出來,此時就可以先用 typeof :

// a 沒有被宣告過,因此 a !== undefined 也是 true
if (typeof a !== 'undefined') {
    console.log(a)
}

就可以避免噴錯了。

不過 typeof 無法檢測一個變數是不是 Array ,因此可以用:

conaole.log(Array.isArray([])) // true

這裡要注意有些比較舊的瀏覽器上沒有這個方法。

另一個檢測型態的方法:

console.log(Object.prototype.toSting.call(<欲檢測物>))
console.log(Object.prototype.toSting.call(1)) // [Object Number]
console.log(Object.prototype.toSting.call('a')) // [Object String]
console.log(Object.prototype.toSting.call(null)) // [Object Null]

賦值 =

primitive type 的賦值是直接儲存在記憶體中,因此賦值時很直觀:

var a = 10
var b = a
b = 20
console.log(a, b) // 10 20

但 object 和 array 就沒那麼簡單了:

var a = { num: 10 }
var b = a
b.num = 20
consolo.log(a, b) // { num: 20 } { num: 20 }

這四行電腦實際在做的事情如下:

  1. 內容 { num: 10 } 存入某個記憶體位置 0x01 中。
  2. 指定 b 的內容也是在 0x01
  3. 存取 b 所指向的 0x01 中的內容,將 num 改成 20 。
  4. 找 a 時就是叫出 0x01 的內容,因此找到 { num: 20 }

不過,如果指定 b 的新值時用別的方法,結果又會不一樣了。

var a = { num: 10 }
var b = a
b = { num: 20 }
consolo.log(a, b) // { num: 10 } { num: 20 }

因為對電腦來說,這四行是在:

  1. 內容 { num: 10 } 存入某個記憶體位置 0x01 中。
  2. 指定 b 的內容也是在 0x01
  3. 重新指定 b 的記憶體位置,指向 0x02 ,並將 { num: 20 } 放入。
  4. 找 a 時就是叫出 0x01 的內容,因此找到 { num: 10 }

另外,若在判斷時使用 =

if (a = 20) {
    consoel.log('hihi') // hihi
}

對電腦來說:

  1. 將 a 賦值 20 。
  2. 判斷 a 的值, 20 對應到的 boolean 值是 true。
  3. 執行 console.log 。

== 和 ===

console.log(2 == '2') // true
console.log(2 === '2') //false

== 會先將兩邊的型態轉換成一樣的,但 === 不會,因此會連型態一起檢查。因此會優先推薦使用 === 最嚴謹,比較不會出 bug 。

值得注意的是,非 primitive type 的物件中, === 比較的實際上不是內容,而是記憶體位置。因此:

var arr1 = [1]
var arr2 = [1]
console.log(arr1 === arr2) // false
arr2 = arr1
console.log(arr1 === arr2) // true

也因此 {} === {} 的結果會是 false 。

特殊例子
NaN 是一種數字,表示它並不是一個數字,例如:

var a = Number('hi')
console.log(typeof a) // NaN

此時來看看:

console.log(a === a) // false

NaN 不等於任何東西,包含它自己。
若要檢測某個東西是否為 NaN 可用 isNaN()

var 、 let 和 const

作用域:變數的生存範圍

ES6 以前作用域的基本單位是 function ,出了 function 就失去作用。
在任何 function 外的變數被稱為全域 (global) 變數,其在所有 function 內皆適用(因為會往上找)。

var a = 20
function test() {
    var a = 10
    console.log(a)
}

test() // 10
console.log(a) // 找到全域變數的 a 是 20

但若第三行不是再宣告一次變數 a 而是重新賦值,結果會如下:

var a = 20
function test() {
    a = 10
    console.log(a)
}

test() // 10 ,還順便更改了 a 的值
console.log(a) // 10

如果在 function 內沒有使用 var 宣告,會自動被認為是全域變數:

function test() {
    a = 10 // 變成全域變數
    console.log(a)
}

test() // 10
console.log(a) // 10

來看個複雜一點的例子:

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 b'
        console.log(a, b) // test scope a inner b
    }
    inner()
}
test()

inner() 中,因為找不到 a 這個變數,所以往上一層找到 var a = 'test scope a' 。往上找的過程稱為 scope chain

另外, scope 之間也不會共享變數, scope chain 和在哪裡有關,而不是在哪裡被呼叫。

var a = 'global'

function change() {
    var a = 'change'
    test()
}

fuction test() {
    console.log(a) // global
}

change()

上面例子中,因為 test scope 中沒有定義 a ,因此會往上找到全域變數 var a = 'global' ,和 change() 中的 a 一點關係也沒有。

回到 const 和 let

var 的作用域在 function 之中,但 const 和 let 的作用域僅限 block (看到 {} 都算)。

var a = 60
if (a === 60) {
    var b = 10
}
consoel.log(b) // 10

// 另一個情境
var a = 60
if (a === 60) {
    let b = 10 // 只在 if 裡面可用
}
consoel.log(b) // 錯誤

另外, constance 是常數,宣告 const 時一定要給一個初始值,而且一旦給定就不能變動。

不過因為 object 的儲存方式是依賴記憶體位置,因此只要記憶體沒變就可:

conat arr = [1]
arr.push(2) // 可以
arr = [1, 2] // 不行,噴 error









Related Posts

Command Line 指令 & Vim 心得筆記

Command Line 指令 & Vim 心得筆記

Android Menu 實作踩坑紀錄

Android Menu 實作踩坑紀錄

最熟悉的陌生人:API

最熟悉的陌生人:API


Comments