iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
10
Modern Web

重新認識 JavaScript系列 第 4

重新認識 JavaScript: Day 04 物件、陣列以及型別判斷

  • 分享至 

  • xImage
  •  

本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。

購書連結 https://www.tenlong.com.tw/products/9789864344130

讓我們再次重新認識 JavaScript!


在上篇文章中,我們提到 JavaScript 內建的型別主要可以分成基本型別 (Primitives) 與物件型別 (Object) 兩大類。 而基本型別又分成 stringnumberbooleannullundefined 幾種,除了以上幾種之外,其他都可以歸類至物件型別 (Object)。

那麼這篇文章中,我們就來聊聊「物件型別」。


物件 Object

所有基本型別 (Primitives) 以外的值都是物件

那麼物件是什麼呢? 在大多數的情況下,JavaScript 的物件與其他程式語言的物件行為類似。

ECMA 262 標準中來看物件的定義:

「An object is a collection of properties and has a single prototype object.」

一個物件可以是個零至多種屬性的集合,而屬性是鍵 (key) 與值 (value) 之間的關聯。 一個屬性的「值」可以是某個基本型別,也可以是另一個物件,甚至可以是一個函數。
物件可以是瀏覽器預先定義好的,當然也可以是由自己定義物件的屬性與內容。

物件及屬性

在早期,要建立一個自定義的物件可能會透過 new 關鍵字來建立一個物件實體,再替這個物件新增屬性與方法:

var person = new Object();

person.name = 'Kuro';
person.job = 'Front-end developer';
person.sayName = function() {
  alert( this.name );
};

後來,另一種建立物件的方式更為簡便,也是目前最常見的:

var person = {
  name: 'Kuro',
  job: 'Front-end developer',
  sayName: function() {
    alert( this.name );
  }
};

像這樣,按照字面上的意思直接用大括號 { },即可建立起一個新的物件,並且在建立物件的同時直接指定屬性至該物件內。
這種建立物件的方式稱為「物件實字 (Object literal)」,同時也是 JSON 格式的核心語法。

屬性存取

物件的屬性可以透過 . 來進行存取:

var person = {
  name: 'Kuro',
  job: 'Front-end developer',
  sayName: function() {
    alert( this.name );
  }
};

person.name;         // 'Kuro'
person.sayName();    // 'Kuro'

或是可以透過 [ ] 來進行存取,如:

var person = {
  name: 'Kuro',
  job: 'Front-end developer',
  sayName: function() {
    alert( this.name );
  }
};

person["name"];         // 'Kuro'
person["sayName"]();    // 'Kuro'

後者的好處是,當物件的索引鍵剛好是不合法的 JavaScript 的識別字 (如帶有空白的字串或是數字) 時,執行就會出現錯誤:

var obj = {
  "001": "Hello"
}

obj.001;        //  SyntaxError: Unexpected number

obj["001"];     // "Hello"

屬性新增

若想為物件新增屬性的話,直接用 = 指定就可以了:

var obj = { };
obj.name = 'Object';

obj.name;       // 'Object'

屬性刪除

要是想刪除屬性,則是透過 delete 關鍵字來刪除:

var obj = { };
obj.name = 'Object';

obj.name;       // 'Object'

delete obj.name;

obj.name;       // 刪除屬性後變成 undefined

判斷屬性是否存在

當我們試著去存取物件中不存在的屬性,此時會回傳 undefined,所以若是我們想要判斷屬性是否存在,最簡單的方式就是檢查該屬性是不是 undefined

var obj = {};

console.log( obj.name );      // undefined

但這麼做會有個例外,就是當該屬性剛好就是 undefined 時,這招就沒用了。

除了檢查 undefined 之外,還有 in 運算子 與 hasOwnProperty() 方法,

var obj = {
  name: 'Object'
};

// 透過 in 檢查屬性
console.log( 'name' in obj );     // true
console.log( 'value' in obj );    // false

// 透過 hasOwnProperty() 方法檢查
obj.hasOwnProperty('name');       // true
obj.hasOwnProperty('value');      // false

雖然兩者都可以檢查物件的屬性是否存在,但 hasOwnProperty() 方法不會往上檢查物件的原型鏈 (prototype chain),只會檢查物件本身是否存在這個屬性,而 in 運算子則會繼續往物件原型鏈上檢查:

obj.hasOwnProperty('hasOwnProperty');    // false

'hasOwnProperty' in obj;                 // true

這部分比較複雜,之後會另外寫一篇關於 prototype 的詳細解說,目前只要知道 inhasOwnProperty() 的差異即可。

那麼,以上是關於 JavaScript 物件的簡單介紹。
物件在 JavaScript 是個相當重要的部分,未來我們還會有很多關於物件的討論。


陣列 Array

介紹過物件,接著來看看陣列。

JavaScript 的陣列可以看作是一種特別的「物件」,同樣是零至多個元素的集合,且並沒有規定它能放什麼東西進去。 陣列內可以是原始的資料類型、其他陣列、函式等等。 要注意的是,陣列是個有順序性的集合,且只能透過 [] 來存取。

建立陣列同樣也可以透過 new 關鍵字來建立:

var a = new Array();

a[0] = "apple";
a[1] = "boy";
a[2] = "cat";

a.length;     // 3

但現在實務上更常見的是陣列實字 (Array literal):

var a = [];

a[0] = "apple";
a[1] = "boy";
a[2] = "cat";

a.length;     // 3

或是:

var a = ["apple", "boy", "cat"];
a.length;     // 3

而陣列的長度可以由 ARRAY.length 來取得,而且 length 這個屬性的值是可以被覆寫的。

var a = ["apple", "boy", "cat"];
a.length;         // 3

a.length = 1;
console.log(a);   // ["apple"]

a.length = 3;
console.log(a);   // ["apple", undefined, undefined]

像上面的例子中,陣列 a 原本的長度為 3,後來透過 a.length = 1; 設定成 1 之後,後面的元素就被移除了。
即使之後再度修改成 a.length = 3;,後面的兩個元素也只會被 undefined 所填補。

所以聰明的你應該發現了一件事,既然陣列的內容可以由 undefined 所填補,代表 JavaScript 在同一個陣列中可以使用多種資料類型。 但相信我,你不會想這樣做的,往後講到迴圈、迭代 (如 for loop、forEach 等) 我們會再次說明。

陣列的長度隨時可以增加或減少,就算指定索引元素時也不一定要連續指定:

var array = ['a', 'b', 'c'];
array.length;         // 3

array[7] = 'z';
console.log(array);   // ["a", "b", "c", undefined, undefined, undefined, undefined, "z"]

另外要注意的是,陣列的索引是由 0 開始計算的,也就是說一個 array = ['a', 'b', 'c']; 陣列的第一個元素,實際上要用 array[0] 來取得。

若是想要在陣列末端新增元素時,可以透過 ARRAY.push() 方法:

var array = ['a', 'b', 'c'];
array.length;           // 3

array.push('d');
console.log(array);     // ['a', 'b', 'c', 'd']

當然除了 ARRAY.push() 之外,陣列還有其他的增減方法,如 ARRAY.pop()ARRAY.shift()ARRAY.unshift() 等等,
這裡就不贅述,有興趣的朋友可至 MDN Array 查看有關 ARRAY 提供的各種方法。


typeof: 型別判斷

上回在介紹資料型別的時候有提到,若要在 JavaScript 內檢查變數型別 (正確來講應該是值,變數沒有型別,值才有),可以透過 typeof 運算子來處理:

typeof  true;         // 'boolean'
typeof  'Kuro';       // 'string'
typeof  123;          // 'number'
typeof  NaN;          // 'number'
typeof  { };          // 'object'
typeof  [ ];          // 'object'
typeof undefined;     // 'undefined'

typeof window.alert;  // 'function'
typeof null;          // 'object'

要注意的是,透過 typeof 運算子回傳的東西都是「字串」。
另外,在上面的各種範例中,你可能會發現有幾個結果跟預期的不太一樣,不是說型別只有基本類型與物件嗎?

怎麼會出現 functiontypeof null 為什麼是 object ?

typeof function(){} 為什麼是 "function" 不是 "object" ?

確實前面說過,除了基本型別以外的都是物件。
當我們透過 typeof 去檢查一個「函式 (function) 」的時候,
雖然你會得到 "function" 的結果,讓你以為 function 也是 JavaScript 定義的一種型別,但實際上它仍屬於 Object 的一種,你可以把它想像成是一種可以被呼叫 (be invoked) 的特殊物件。

來看看 ECMAScript 的定義:
https://ithelp.ithome.com.tw/upload/images/20171207/20065504nRJ9Zx6r0J.png
來源

以及 MDN 對 function 的定義:
https://ithelp.ithome.com.tw/upload/images/20171207/200655040lWL5tiUow.png

來源

針對 function 的部分,我打算在後面另外寫一篇專文介紹,這裡大家只要知道雖然 typeof function(){ } 回傳的是 "function",但實際上仍然是屬於「物件」的型別。

typeof null 為什麼是 "object" ?

「其實這只是一個 Bug !」 (完)

如果只寫這樣的話我應該會被噓到 XX

這又要從 JavaScript 的歷史講起了。 還記得前面強調過的嗎?

「變數沒有型別,值才有」

在 JavaScript 初期的實作中,JavaScript 的值是由一個表示「型別」的標籤,與實際內容的「值」所組合成的。

由於物件 (Object) 這個型別的標籤是「0」,而且 null 代表的是空值 (NULL pointer,慣例上會以 0x00 來表示),於是代表 null 的標籤就與物件的標籤搞混,而有著這樣錯誤的結果。 簡單來說就是前人留下的屎。

在 ES6,Brendan Eich (JS 老爸) 曾經想要提出修正提案,將 typeof null 修正成 "null",但 Douglas Crockford (JSON 老爸) 持保留意見:

I think it is too late to fix typeof. The change proposed for typeof null will break existing code.

因為修改這樣的問題,會影響到太多舊有的程式,因此最後還是被 reject 了。

如何判別是否為陣列

最後,雖然現在大家都知道,當我們利用 typeof 去檢查一個「陣列」時,會得到 "object" 的結果,但如果在實務上仍會有需要判斷某變數是否為一個陣列而非物件的時候,該怎麼處理呢?

好在自從 ES5 之後, Array 定義了 isArray() 方法,

Array.isArray([]);            // true
Array.isArray([1]);           // true
Array.isArray(new Array());   // true

Array.isArray();              // false
Array.isArray({});            // false
Array.isArray(null);          // false
Array.isArray(undefined);     // false

那麼以上,就是今天為各位介紹的物件、陣列以及型別判斷部分。
在未來後續的文章當中,我們會繼續來介紹 JavaScript 型別的轉換、運算子/式、流程判斷,歡迎對此有興趣的朋友持續關注。


上一篇
重新認識 JavaScript: Day 03 變數與資料型別
下一篇
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
系列文
重新認識 JavaScript37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

3
小碼農米爾
iT邦高手 1 級 ‧ 2017-12-07 18:44:44

感謝大大的分享,原來 typeof null 會是 "object ",一直沒注意到 XD
其中有一段好像寫錯了

typeof null 修正成 Object

這裡是不是應該是,將 typeof null 修正成 null?
/images/emoticon/emoticon41.gif

Kuro Hsu iT邦新手 1 級 ‧ 2017-12-07 18:46:09 檢舉

對欸,打太順就手誤了,感謝提醒

0
konekoya
iT邦新手 5 級 ‧ 2017-12-08 09:26:18

寫的很清楚 :) 覺得你可以出書了

我要留言

立即登入留言