給自己看的 JavaScript 進階 - 變數
— 程式導師計劃, coding 筆記, JavaScript — 8 min read
耶耶「給自己看的」系列重開 XD
發現之前做過的筆記有一點點重覆: 給自己看的 JavaScript 基礎 - 迴圈、函式及其他觀念
變數型態
七種資料型態: primitive type 原始型態
- null
- undefined
- string
- number
- boolean
- symbol(ES6)
其他都是 boject 物件 7. 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); // numberconsole.log(typeof []); // objectconsole.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 也是 trueif (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 }
這四行電腦實際在做的事情如下:
- 內容
{ num: 10 }
存入某個記憶體位置0x01
中。 - 指定 b 的內容也是在
0x01
。 - 存取 b 所指向的
0x01
中的內容,將 num 改成 20 。 - 找 a 時就是叫出
0x01
的內容,因此找到{ num: 20 }
。
不過,如果指定 b 的新值時用別的方法,結果又會不一樣了。
var a = { num: 10 };var b = a;b = { num: 20 };consolo.log(a, b); // { num: 10 } { num: 20 }
因為對電腦來說,這四行是在:
- 內容
{ num: 10 }
存入某個記憶體位置0x01
中。 - 指定 b 的內容也是在
0x01
。 - 重新指定 b 的記憶體位置,指向
0x02
,並將{ num: 20 }
放入。 - 找 a 時就是叫出
0x01
的內容,因此找到{ num: 10 }
。
另外,若在判斷時使用 =
:
if ((a = 20)) { consoel.log('hihi'); // hihi}
對電腦來說:
- 將 a 賦值 20 。
- 判斷 a 的值, 20 對應到的 boolean 值是 true。
- 執行 console.log 。
== 和 ===
console.log(2 == '2'); // trueconsole.log(2 === '2'); //false
==
會先將兩邊的型態轉換成一樣的,但 ===
不會,因此會連型態一起檢查。因此會優先推薦使用 ===
最嚴謹,比較不會出 bug 。
值得注意的是,非 primitive type 的物件中, ===
比較的實際上不是內容,而是記憶體位置。因此:
var arr1 = [1];var arr2 = [1];console.log(arr1 === arr2); // falsearr2 = 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(); // 10console.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(); // 10console.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