iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
3

你所不知道的 JS

本文主要會談到

  • 何謂「型別」?內建型別有哪些?常見疑難雜症與解法。
  • 未定義(undefined)vs 未宣告(undeclared)。

何謂「型別」?

「型別」是固有的、內建的特徵,能唯一識別特定值的行為。例如:數字 123 和字串 '123' 就是不一樣的,數字 123 可做數學運算處理,而字串 '123' 可能就是做些顯示到畫面上的操作。

內建型別(Built-in Types)

JavaScript 定義了以下七種內建型別

  • number 數字,例如:12345。
  • string 字串,例如:'Hello World'
  • boolean 布林,例如:true、false。
  • null
  • undefined
  • object 物件,例如:{ name: 'Jack' }[1, 2, 3]function foo() { ... }
  • symbol

其中,這些內建型別又可分為兩大類-基本型別(primitives)和物件型別(object)。基本型別有 number、string、boolean、null、undefined、symbol,而物件型別就是物件與其子型別(subtype),例如:物件、陣列、函式、日期等。

我們可用 typeof 來檢測值的資料型別為何。

typeof 'Hello World!' // 'string'
typeof true // 'boolean'
typeof 1234567 // 'number'
typeof null // 'object'
typeof undefined // 'undefined'
typeof { name: 'Jack' } // 'object'
typeof Symbol() // 'symbol'
typeof function() {} // 'function'
typeof [1, 2, 3] // 'object'
typeof NaN // 'number'

這裡會看到幾個有趣的(奇怪的)地方...

  • null 是基本型別之一,但 typeof null 卻得到 object,而非 null!這可說是一個 bug,可是若修正了這個 bug 則可能會導致很多網站壞掉,因此就不修了!
  • 雖然說 function 是物件的子型別,但 typeof function() {} 是得到 function 而非 object,和陣列依舊得到 object 是不一樣的。另外,順道一提,函式是一種「可呼叫的物件」(callable object),它擁有 [[Call]] 的內部特性,讓它成為能夠被調用的物件。
  • NaN 表示是無效的數字,但依舊還是數字,因此在資料型別的檢測 typeof NaN 結果就是 number,不要被字面上的意思「不是數字」(not a number)給弄糊塗了。另外,NaN 與任何數字運算都會得到 NaN,並且 NaN 不大於、不小於也不等於任何數字,包含 NaN 它自己。

看到這裡是不是覺得 JavaScript 很難懂呢? (/‵Д′)/~ ╧╧

翻桌


好的,在翻桌之前,先來解決剛剛提到的幾個問題。

Q1:如何檢測 null?

先回顧一下之前提到的「Truthy & Falsy」的概念,在做比較時會被轉型為 false 的值有

  • "" 空字串
  • 0, -0, NaN
  • null
  • undefined
  • false

而除了以上之外,都會被轉為 true,舉例如下

  • 'Hello World' 非空字串
  • 42 非零的有效數字
  • [], [1, 2, 3] 陣列,不管是不是空的
  • {}, { name: 'Jack' } 物件,不管是不是空的
  • function foo() {} 函式
  • true

咦?是不是看到一線生機?

shock

我們可利用 null 會被 typeof 檢測為 object 並且會轉為 false 的結果來驗證值是否為 null。

const happy = null;

if (!happy && typeof happy === 'object') {
  console.log('我是 null!');
}

得到「我是 null!」,輕鬆解決,得分!

shock

Q2:既然函式與陣列都是物件,那其中的屬性 length 有什麼不同?

函式的 length 是指參數個數,而陣列的 length 是指內部成員個數。

function testMe (arg1, arg2, arg3) {
  console.log('This is testMe!');
}

const list = [1, 2, 3, 4, 5];

testMe.length // 3
list.length // 5

Q3:typeof 檢測的對象是誰?

再次強調,變數沒有型別,變數可在不同時間點持有不同型別的值,因此,只有「值」才有型別。雖然我們可用 typeof 檢測某個變數所儲存的值的型別,但記得並不是檢測變數本身,而是變數所存的值。

const name = 'Jack';
typeof name // 'string'

Q4:辨識物件子型別的方法?

稍後在 Natives(原生功能)的部份會說明取得物件內部分類的方法,這裡就先大略提一下。

物件型別的值其內部有一個 [[Class]] 屬性來標記這個值是屬於物件的哪個子分類,雖然無法直接取用,但可透過 Object.prototype.toString 間接取得,範例如下。

Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call({ name: 'Jack' }); // "[object Object]"
Object.prototype.toString.call(function sayHi() {}); // "[object Function]"
Object.prototype.toString.call(/helloworld/i); // "[object RegExp]"
Object.prototype.toString.call(new Date()); // "[object Date]"

未定義(undefined)vs 未宣告(undeclared)

  • 未定義(undefined):未賦值的變數所儲存的值是 undefined,對此變數做 typeof 也會得到 'undefined'
  • 未宣告(undeclared):變數在未宣告並使用的狀況下會得到 ReferenceError,並指出該變數並未宣告;變數在未宣告並賦值的狀況下,在嚴格模式下會報錯 ReferenceError,而在非嚴格模式下,變數會成為全域變數的屬性。注意,對未宣告的變數做 typeof 也會得到 'undefined'

總結就是,無論變數是未定義或未宣告,typeof 這兩種狀況皆會得到 'undefined'

那麼,對未宣告的變數做 typeof 而得到 'undefined',有什麼用處呢?(*´・д・)?

對未宣告的變數做 typeof

對未宣告的變數做 typeof 而得到 'undefined' 可說是一種保護措施,可避免瀏覽器丟出 ReferenceError 的錯誤訊息,在撰寫測試特定條件時常會用到。

範例如下,我們可能在非正式環境下會引用了某支 js 檔案,其中會設定 DEBUG 為 true,而其他檔案會根據 DEBUG 變數是否被宣告或宣告並設定為 true 時做出相對應的事情。

if (typeof DEBUG !== 'undefined') {
  // start to debug...
}

或著,我們也可以不用 typeof 的作法,而改用檢測 window 屬性的方式,同樣也不會丟出 ReferenceError。

if (typeof window.DEBUG) {
  // start to debug...
}

再或者,使用依存性注入(dependency injection)的方式也是可以的,將要檢測的條件當參數傳入函式,若條件不存在則使用預設值。

function doSomethingCool(DEBUG) {
  var helper = DEBUG || function() { /* 預設值 */ };
  // ...
}

只是這解法已被 ES6 的預設傳入參數(Default Paramaters)取代了。

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到...

  • JavaScript 的內建七種型別有 number、string、boolean、null、undefined、object、symbol,其中...
    • Truthy & Falsy 的轉換規則,會被轉為 false 的值有 "" 空字串、0、-0、NaN、null、undefined 和 false,其餘都會被轉為 true。
    • typeof 可用來識別值的資料型別;Object.prototype.toString 可用來檢測值是屬於物件的哪個子分類。
    • 函式的 length 是指參數個數,而陣列的 length 是指內部成員個數。
  • 未定義(undefined)表示變數曾被宣告但值未被定義,此時值為 undefined;未宣告(undeclared)表示變數未曾被宣告過,而 typeof 這兩種狀況的變數皆會得到 'undefined'。對未宣告的變數做 typeof 而得到 'undefined' 的結果可作為具有保護性的檢測措施,在撰寫測試特定條件時常會用到。

References


同步發表於部落格


上一篇
你懂 JavaScript 嗎?#3 暖身 (๑•̀ㅂ•́)و✧ Part 2 - 變數、嚴格模式、IIFEs、閉包、模組、this、原型、Polyfill 與 Transpiler
下一篇
你懂 JavaScript 嗎?#5 值(Values)Part 1 - 陣列、字串、數字
系列文
你懂 JavaScript 嗎?30

2 則留言

1
hannahpun
iT邦新手 5 級 ‧ 2018-10-11 21:51:21

感謝分享~~
好強平常 null 到底用在哪??
因為這是人為的程式不會丟null出來啊

我會用到的地方有...

  1. 防呆,!a 可阻擋 a 為 null、undefined、空字串、false 等串接 API 時遇到某欄位沒資料(就給我 null)或沒丟這個欄位(就是 undefined)或被亂丟資料。
  2. 沒有資料或初始值,我不會用空陣列或是空物件,而是放 null。如下範例,這是顯示某商品的規格的資料,其中欄位 spec_ext 的資料可能是一個物件(內含 goods_no 和 goods_note),沒資料的時候就是 null,這樣之後處理就滿好判斷的(就是 1 的狀況)。
{
	"level": 1,
	"specs": {
		"temp_77965072": {
			"spec_id": "temp_77965072",
			"spec_name": "紅色",
			"spec_num": "10",
			"spec_status": "Y",
			"spec_ext": {
				"goods_no": "1234567",
				"goods_note": "需要補貨時請洽小明 02-27123456"
			}
		},
		"temp_76736337": {
			"spec_id": "temp_76736337",
			"spec_name": "藍色",
			"spec_num": "20",
			"spec_status": "Y",
			"spec_ext": null
		}
	}
}
1
we684123
iT邦新手 5 級 ‧ 2018-10-12 01:49:21

優質好文 OwObbbbbbbbbb
感謝大大

感謝支持,我會繼續努力!

/images/emoticon/emoticon58.gif

我要留言

立即登入留言