Skip to content
坑坑洞洞
Github

給自己看的 JavaScript 進階 - 變數

程式導師計劃, coding 筆記, JavaScript8 min read

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

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

變數型態

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

  1. null
  2. undefined
  3. string
  4. number
  5. boolean
  6. 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); // 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