WAI-ARIA基礎 - 學習該如何開發 Web

WAI-ARIA基礎
==========

* 前頁  (en-US)
* Overview: Accessibility
* 次頁  (en-US)

接續之前的文章，有時在涉及非語意 HTML 與動態 JavaScript 更新的內容製作複雜的 UI 控制措施將是個難題。WAI-ARIA 即是一個能藉由添加進一步的語意幫助處理這種問題的技術 ，讓瀏覽器與輔助科技可以辨識及用以讓使用者知道發生甚麼事情。這裡我們將展示如何以基本水準的運用來增進無障礙使用。

|  |  |
| --- | --- |
| 先決條件： | 基本電腦技能、基本瞭解HTML, CSS與JavaScript、瞭解本課程先前文章。 |
| 目標： | 能熟悉WAI-ARIA，以及在必要時如何用於提供有用的附加語意強化無障礙。 |

甚麼是 WAI-ARIA?
-------------

開始瞭解甚麼是 WAI-ARIA，以及它可為我們做些甚麼。

### 一個全新問題集

當網站應用程式開始更複雜與動態，新的無障礙特性與問題集就開始出現。

例如，HTML5 導入許多語意的元素來定義一般頁面的特性(`<nav>`, `<footer>` (en-US)等) 在沒有這些可用之前，開發者僅簡單使用`<div>`搭配 IDs 或 classes，如<div class="nav">，但這些是有問題的，因為沒有很簡單的方法可程式化地容易找到特定的頁面特性如主要導覽功能。

起初的解決方案是在頁面的頂端添加一個或更多隱藏的連結去連結到導覽功能(或其他任何的功能)，例如：

```html
<a href="#hidden" class="hidden">Skip to navigation</a>
```

但這仍然不是非常精確，並且僅能使用於螢幕報讀器從頁面頂端閱讀下來的時候。

如同另一個例子，應用程式開始具有複雜的控制措施如日期選取器提供選擇日期，內容滑塊提供選取內容值等。HTML5 提供特定的輸入型態來呈現這些控制措施：

```html
<input type="date" /> <input type="range" />
```

這些在跨瀏覽器之間沒有全面性支援，而且也非常困難去為他們指定樣式，而使他們與網站設計整合時不是很好用。因此，開發者時常依賴 JavaScript 資源庫來產生這些一系列內嵌的控制措施 `<div>`或具有 classnames 的表格元素，透過 CSS 指定樣式與使用 JavaScript 控制。

這個的問題是視覺上可以運作，但螢幕報讀器則一點也無法理解它們是甚麼，以及它們的使用者僅被告知他們可以看到一堆沒有語意的元素來描述它們的含意。

### 進入 WAI-ARIA

WAI-ARIA 是一個由 W3C 編撰的規格，定義一套額外的 HTML 屬性能用於元素上提供額外的語意及改善可及性，當元素缺乏這些條件時可適用。本規格定義三個主要的特點：

* **Roles** — 這些角色用以定義元素是甚麼或做甚麼事。在這些當中許多被稱為地標角色，大部分重複了 HTML5 結構化元素的語意值如`role="navigation"` (`<nav>`) or `role="complementary"` (`<aside>` (en-US)), 但也有其他描述不同的頁面結構者，如`role="banner"`, `role="search"`, `role="tabgroup"`, `role="tab"`等常見於使用者介面中。
* **Properties** — 這些用以定義元素的屬性，可用來賦予元素額外的意義或語意。舉例，`aria-required="true"`指定一個表單輸入必須填寫才有效，而`aria-labelledby="label"`讓你可以在一個元素上置放一個 ID，當該頁面上任何內容設定標籤後而引用它，頁面包含多個元素而不可能使用`<label for="input">`。舉例，你可使用`aria-labelledby`指定一個鍵盤鍵描述包含在一個`<div>`之中是多個表格儲存格的標籤，或者你可使用它做為圖片 alt 文字的替代方案 — 指定頁面上現有的資訊做為圖片的 alt 文字，而非將它重複放在`alt`屬性之內. 範例可參考Text alternatives。
* **States** — 定義目前元素狀態的特殊屬性，例如 `aria-disabled="true"`，是對螢幕報讀器指定表單輸入目前是停用的狀態。狀態不同於屬性，在於屬性在應用程序整個生命週期中不會改變，而狀態通常會透過 JavaScript 程式化改變。

有關 WAI-ARIA 屬性的重要觀點是他們不會影響網頁的任何內容，除了透過瀏覽器無障礙 API 揭露資訊之外(螢幕報讀器即從中獲得資訊)。儘管 WAI-ARIA 屬性對 CSS 選擇元素很有用，但不會影響網頁結構、DOM 等。

**備註：** 你可以在 WAI-ARIA 規格中找到所有 ARIA 的角色及其用法的很有用清單，請參見 Definition of Roles連結以獲得更進一步的資訊。

本規格也涵括所有屬性與狀態的清單，請參見 Definitions of States and Properties (all aria-\* attributes)連結以獲得更進一步的資訊。

### 哪裡支援 WAI-ARIA？

這不是一個容易回答的問題。要難找到一個決定性的資源來陳述何者是支援 WAI-ARIA 的特點以及在何處，因為：

1. 在 WAI-ARIA 規格中有很多特點。
2. 要考量作業系統、瀏覽器與螢幕報讀器的許多組合。

最後一點是關鍵—首先要使用螢幕報讀器，你的作業系統必須執行具有必要的無障礙 API 的瀏覽器去揭露螢幕報讀器必須去完成工作的資訊。大部分主流的作業系統有 1 個或 2 個瀏覽器可供螢幕報讀器使用。Paciello Group 一則最近的文章提供這些數據—請參見 Rough Guide: browsers, operating systems and screen reader support updated。

接著，你必須擔心瀏覽器是否支援 ARIA 特徵並透過其 API 揭露，同時螢幕報讀器是否辨識該資訊並以可用的方式向使用者呈現的問題。

1. 瀏覽器支援一般相當好—在撰文當下， caniuse.com表示全球瀏覽器對 WAI-ARIA 的支援率大約為 88%。
2. 螢幕報讀器對 ARIA 特徵的支援沒有相當於此水平，但大部分主流的螢幕報讀器是有達到此水平。你可查閱 Powermapper 的 WAI-ARIA Screen reader compatibility這篇文章了解支援的水平。

在本文中，我們未試圖涵蓋每一個 WAI-ARIA 特徵及其確切的支援細節。相反地，我們將涵蓋最關鍵的 WAI-ARIA 特徵讓你知道；如果我們沒有提到任何支援細節，你可認定該特徵得到很好的支援。我們會明確地提到這個例外情況。

**備註：** 某些 JavaScript 儲存庫支援 WAI-ARIA，亦即當他們產生 UI 特徵如複雜的表單控制措施，他們添加 ARIA 屬性來增進這些特徵的無障礙。如果你在尋找第三方的 JavaScript 解決方案來快速的開發 UI，你應該謹慎地考量其 UI 插件的無障礙作為你決定使用的重要因素。jQuery UI(參見About jQuery UI: Deep accessibility support)、 ExtJS與 Dojo/Dijit是良好範例。

### 何時應該使用 WAI-ARIA?

我們討論了促使 WAI-ARIA 早期建立的一些問題，但基本上 WAI-ARIA 在 4 個主要領域很有用：

1. **路標/地標**：ARIA 的角色屬性值可作為地標，不是複製 HTML5 元素的語意(如`<nav>`)，就是超越 HTML5 語意而對不同的功能區域提供路標，如搜尋、頁籤群組、頁籤、清單框等。
2. **動態內容更新**：螢幕報讀器往往難以報讀不斷改變的內容；當某個內容區域更新時，我們可以使用 aria-live 通知螢幕報讀器的使用者，例如透過 XMLHttpRequest或DOM APIs。
3. **增強鍵盤無障礙**：內建的 HTML 元素具有原生的鍵盤無障礙；當其他元素伴隨使用 JavaScript 模擬相似的互動時，鍵盤無障礙操作與螢幕報讀器報讀會遭遇問題。如果這是不可避免的，WAI-ARIA 提供讓其他元素獲得焦點的一種方法(使用 `tabindex`)。
4. **非語意控制措施的無障礙**：當一系列巢狀的`<div>`搭配 CSS/JavaScript 用於創建一個複雜的 UI 特徵，或者一個透過 JavaScript 大幅增強/改變的原生控制措施，無障礙會遭遇到困難—如果沒有語意或其他線索，螢幕報讀器使用者將發覺難以理解該特徵的作用。在這種情況下，ARIA 可以幫助提供缺失的部分使用如按鈕、清單框或頁籤群組等角色組合，以及 aria-required 或 aria-posinset 等屬性對功能性提供進一步的線索。

記住一件事 — **僅在必需時才使用 WAI-ARIA**! 理想情況下，你應該總是使用原生 HTML 特徵提供螢幕報讀器所需要的語意來告訴其使用者接下來將發生甚麼事情。有時候這是不可能的情形，不是因為你受限於控制該程式碼，就是因為你創建一些複雜而無法用簡易的 HTML 元素來開發。在這種情況下，WAI-ARIA 可作為有價值的無障礙增強工作。

再說一次，只有需要的時候才使用它！

**備註：** 請確保有各類實際的使用者來測試你的網站 — 非身障者、使用螢幕報讀器者、使用鍵盤導覽者等。他們將比你更能了解它運作的效果。

實作 WAI-ARIA 開發
--------------

在下一個章節我們將更仔細地看看這 4 個領域，並附帶實際的範例。在繼續之前，你應該將備妥螢幕報讀器測試設置，以便在過程中你可以測試這些範例。

更多資訊請參見螢幕報讀器測試 (en-US)章節。

### 路標/地標

WAI-ARIA 添加角色屬性給瀏覽器，讓你可以在必要時添加額外的語意值到你網站上的元素。這第一個主要的領域在為螢幕報讀器提供資訊方面非常有用，讓螢幕報讀器的使用者可以找到常見的頁面元素。我們來看個範例 — 網站-無-角色範例(看實際頁面)有以下的結構：

```html
<header>
  <h1>...</h1>
  <nav>
    <ul>
      ...
    </ul>
    <form>
      <!-- search form -->
    </form>
  </nav>
</header>

<main>
  <article>...</article>
  <aside>...</aside>
</main>

<footer>...</footer>
```

若你嘗試在現代瀏覽器中使用螢幕報讀器來測試此範例，你將可以獲得一些有用的資訊。例如，VoiceOver 給你以下的資訊：

* 在 `<header>` 元素上— "橫幅，2 個項目"(含有一個標題與 `<nav>`)。
* 在 `<nav>` 元素上—"導航列 2 個項目" (含有一個清單與一個表單)。
* 在 `<main>` 元素上—"主要內容 2 個項目" (含有一則文章與一個附帶內容)。
* 在`<aside>` 元素上—"補充 2 個項目" (含有一個標題與一個清單)。
* 在搜尋表單輸入—"搜尋查詢，插入文字開始處"。
* 在`<footer>` 元素上—"頁尾 1 個項目"。

如果你到 VoiceOver 地標選單(使用 VoiceOver 主鍵+ U，然後使用游標鍵循環整個選單選項)，你會看到大部分元素被列的很好，因此可以很快地訪問他們。

![](/zh-TW/docs/Learn/Accessibility/WAI-ARIA_basics/landmarks-list.png)

然而，這裡我們可以做得更好，搜尋表單是一個人們想要找到的很重要的地標，但是它並沒有列在地標選項之中或者被視為顯著的地標，除了在實際輸入而被召喚作為搜尋輸入之外(`<input type="search">`)。另外，有些舊的瀏覽器(大部分是指 IE8)無法辨識 HTML5 元素的語意。

讓我們使用一些 ARIA 特徵來改善它。首先，我們將添加一些角色屬性到我們的 HTML 結構。你可以試著複製我們原始的檔案(參見index.html與style.css)，或者瀏覽我們的網站-aria-角色範例(看實際頁面)，其結構如下：

```html
<header>
  <h1>...</h1>
  <nav role="navigation">
    <ul>
      ...
    </ul>
    <form role="search">
      <!-- search form -->
    </form>
  </nav>
</header>

<main>
  <article role="article">...</article>
  <aside role="complementary">...</aside>
</main>

<footer>...</footer>
```

在本範例中我們也將給你一個額外的特徵—`<input>` (en-US) 元素賦予 `aria-label`屬性，藉由給予描述性的標籤讓螢幕報讀器可以報讀出來，即使我們沒有包含`<label>` (en-US)元素。像這種情況就非常有用—搜尋表單是一個非常常見、容易辨識的特徵，而添加視覺的標籤可能破壞頁面的設計。

```html
<input
 type="search"
 name="q"
 placeholder="Search query"
 aria-label="Search through site content" />
```

現在如果我們使用 VoiceOver 來看這個範例，我們可以獲得一些改善：

* 召喚搜尋表單當作獨立的項目，無論是在瀏覽頁面，或在地標選項之中。
* 當表單輸入凸顯時，將報讀出包含在`aria-label`屬性的標籤文字。

除此之外，該網站對舊瀏覽器如 IE8 的使用者更可能無障礙；為了這個目的，包含 ARIA 角色是值得的。如果為了某些原因，你的網站僅使用 `<div>`建置，那麼肯定你應該包含 ARIA 角色來提供這些非常需要的語意！

當 ARIA 超越從 HTML5 所能獲得的語意時，搜尋表單的改進語意已經顯示實現的可能性。你將會在下面看到更多關於這些語意與 ARIA 屬性的能力，尤其在Accessibility of non-semantic controls章節。現在，我們先來看看 ARIA 如何幫助動態內容更新。

### 動態內容更新

從文本內容到附著於圖片的替代文字，其內容載入至 DOM 中可方便螢幕報讀器使用。因此，大部分使用文字內容的傳統靜態網站能輕易讓視覺障礙者無障礙使用。

問題在於現代網頁應用程式通常不只是靜態的文字—他們傾向有很多動態更新的內容，例如透過像XMLHttpRequest, Fetch, 或DOM APIs等機制更新的內容而不必重新載入全部的頁面。這些有時稱為即時區塊。

我們來看一個簡單的範例—請看 aria-no-live.html (看實際頁面)。本範例中我們有簡單的隨機引言框：

```html
<section>
  <h1>Random quote</h1>
  <blockquote>
    <p></p>
  </blockquote>
</section>
```

我們的 JavaScript 透過含有一系列的隨機引言與其作者的 `XMLHttpRequest` `載入一個`JSON 檔案。一旦這些完成，我們就開始 `setInterval()` (en-US) 迴圈每 10 秒載入新的隨機引言到引言框之中。

```js
var intervalID = window.setInterval(showQuote, 10000);
```

這個運作正常，但對無障礙不是很好—此內容更新無法被螢幕報讀器所偵測，所以他們的使用者不知道發生甚麼事情。這是一個相當平凡的例子，但只要想像一下如果你正在創建一個有著大量不斷更新內容的複雜 UI，如聊天室、一個策略型的遊戲 UI、或者一個即時更新的購物車顯示窗—則將不可能以任何有效的方式使用該應用程式而沒有某種提醒使用者該更新內容的方法。

很幸運地 WAI-ARIA 提供一項有用的機制來提供這些警告—即`aria-live` 屬性。將此屬性用在元素上可讓螢幕報讀器讀出更新的內容。報讀內容的緊急性決定於以下屬性值：

* `off:` 預設值，更新不應該通知。
* `polite`: 更新應該只在使用者空閒時才通知。
* `assertive`: 更新應盡快通知使用者。
* `rude`: 更新應立即通知，即使打斷使用者。

一般而言， `assertive` 設置足以讓你的更新內容在他們出現時依序地讀出，因此如果同時有多個事件改變，你將獲得所有的更新。只有對非常高優先順序的更新使用 `rude` 才能覆蓋其他所有的更新。

我們希望你複製 aria-no-live.html 與 quotes.json，並更新 `<section>` 標籤如下所示：

```html
<section aria-live="assertive"></section>
```

這將使螢幕報讀器在內容更新時讀出更新的內容。

**備註：** 如果你嘗試從 `XMLHttpRequest` 執行 `file://` URL`，`大部分的瀏覽器會拋出安全異常，例如你直接上傳該檔案到瀏覽器(透過雙擊滑鼠鍵等)。為了這項可以執行，你需要將檔案上傳到一個網站伺服器如 GitHub (en-US)，或本機網站伺服器如 Python's SimpleHTTPServer。

這裡有一項額外的考量—只有文字更新才讀出。如果我們也總是讀出標題，那將很好，以讓使用者記住讀出的內容。為做到這樣，我們可以添加 `aria-atomic` 屬性到這個部分，再次更新你的 `<section>` 標籤如下所示：

```html
<section aria-live="assertive" aria-atomic="true"></section>
```

`aria-atomic="true"`屬性告訴螢幕報讀器以一個原子單位方式讀出完整的元素內容，而不僅只讀出更新的部分。

**備註：** 你可以查看完成的範例 aria-live.html (看實際頁面)。

**備註：** `aria-relevant` 屬性在即時區塊更新時對於控制讀出甚麼內容也相當有用，例如你可以僅獲得讀出新增或移除的內容。

### 增強鍵盤無障礙

如同在本模組其他章節所討論， HTML 關於無障礙的關鍵優勢之一是內建鍵盤無障礙特徵如按鈕、表單控制措施及連結。一般而言，你可以使用 tab 鍵在控制措施之間移動，輸入(Enter)/返回(Return)鍵用來選擇或觸發控制措施，以及偶爾需要其他的控制措施(例如上下游標在 `<select>` 框中的選項間移動)。

然而，有時你最終必須撰寫不是使用非語意的元素如按鈕(或其他控制的型態)，或者使用可獲焦點的控制措施用作非正確目的程式碼。你可能嘗試修正一些繼承來的不好的程式碼，或者你可能創建需要某些種類的複雜插件。

讓非焦點的程式碼可獲得焦點，WAI-ARIA 使用一些新值擴展 `tabindex` 屬性：

* `tabindex="0"` —如上述，此值允許正常不被 tab 遊走到的元素變得可以 tab 遊走，這是`tabindex`最有用的值。
* `tabindex="-1"` — 此允許正常不被 tab 遊走到的元素，可以程式化地獲得焦點，如透過 JavaScript 或作為連結的對象。

我們更詳細討論這一點並在我們的 HTML 無障礙文章中展示典型的實作—請參見 Building keyboard accessibility back in (en-US).

### 非語意控制措施的無障礙

本部份接續前一章節—當一系列巢狀的 `<div>`搭配 CSS/JavaScript 用於創建一個複雜的 UI 特徵，或者一個透過 JavaScript 大幅增強/改變的原生控制措施，不僅鍵盤無障礙會遭遇到困難，而且如果沒有語意或其他線索，螢幕報讀器使用者也會發覺難以理解該特徵的作用。在這種情況下，ARIA 可以幫助提供缺失的語意。

#### 表單驗證與錯誤警告

首先，讓我們再看一次在我們的 CSS 與 JavaScript 無障礙文章中第一次看的表單範例(請閱讀 Keeping it unobtrusive (en-US)完整回顧)。在本章節文末我們展示當你試著送出表單而驗證錯誤時，出現我們包含一些在錯誤訊息框的 ARIA 屬性。

```html
<div class="errors" role="alert" aria-relevant="all">
  <ul></ul>
</div>
```

* `role="alert"` 自動地將其應用的該元素轉換為即時區塊，以利讀出它的改變；它也將語意地辨識其為警告訊息(重要時間/內容脈絡敏感資訊)，而呈現以更好、更無障礙的方式向使用者傳遞警告(模態對話框如 `alert()` (en-US) 具有許多無障礙的問題；請參見 WebAIM 的 Popup Windows)。
* `aria-relevant` 的 all 值指示螢幕報讀器當它任何改變時讀出錯誤清單的內容—例如當錯誤新增或移除時。這個很有用因為使用者會想知道留著的錯誤是甚麼，而不只有知道清單中甚麼是已經新增或移除。

我們可進一步使用我們的 ARIA，並提供更多的驗證協助。如何指出區塊是否需要在第一個位置，以及年齡的範圍應該多少？

1. 在此，複製我們的 form-validation.html 與 validation.js 檔案，並將他們儲存在本機目錄。
2. 在文字編輯器開啟他們並且看一下該程式碼如何運作。
3. 首先，在開始的 `<form>` 標籤之上增加一個段落，如下所示，並且用星號標記兩個表單的 `<label>`。這是一般我們對有視力的使用者標記必要區塊的方法。

```html
<p>Fields marked with an asterisk (*) are required.</p>
```

4. 這讓視覺有意義，但這對螢幕報讀器使用者不能輕易理解。很幸運地，WAI-ARIA 提供 `aria-required` 屬性以提示螢幕報讀器應告訴使用者需要填寫的表單輸入欄位。更新`<input>` 元素如下：

```html
<input type="text" name="name" id="name" aria-required="true" />

<input type="number" name="age" id="age" aria-required="true" />
```

5. 如果你現在儲存本範例並使用螢幕報讀器測試，你應該聽到這個內容 "Enter your name star, required, edit text"。
6. 如果我們給予螢幕報讀器使用者與視覺的使用者有關年齡的值應該是甚麼的概念，這樣也將會很有用。這個或許常以提示或在表單輸入區內預設文本的方式呈現。WAI-ARIA 用包含 `aria-valuemin` 與 `aria-valuemax` 屬性來指定最小與最大值，但這些目前未受到很好的支持；比較受支持的特徵是 HTML5 `placeholder` 屬性，它在沒有輸入值的時間將含有的訊息顯示在輸入框，並能由許多螢幕報讀器讀出。更新你的數值輸入如下所示：

```html
<input
 type="number"
 name="age"
 id="age"
 placeholder="Enter 1 to 150"
 aria-required="true" />
```

**備註：** 你可以查看完成的範例 form-validation-updated.html。

除了傳統的 `<label>` (en-US) 元素之外，WAI-ARIA 也能賦予一些進階的表單標籤技術。我們已經談論過使用 `aria-label` 屬性在我們不希望標籤讓有視覺的使用者看見的地方來提供標籤(參見上述 路標/地標 章節。如果你想要指定一個非`<label>` 元素當作標籤或具有相同標籤多重的表單輸入標籤，這裡有使用其他屬性如 `aria-labelledby` 的其他標籤技術，如果你想要其他的訊息與表單輸入關聯並且報讀出來，使用 `aria-describedby`。更多細節請參見 WebAIM's Advanced Form Labeling article 。

還有許多其他有用的屬性與狀態，用來指出表單元素的狀態。例如， `aria-disabled="true"` 可用於指出表單區域是處於停用的狀態。許多瀏覽器只會跳過停用的表單區塊，並且甚至不會被螢幕報讀器讀出，但在某些情況下，他們可被感知，所以最好包含此屬性讓螢幕報讀器知道停用的輸入事實上即是停用的狀態。

如果輸入的停用狀態可能產生改變，也最好在發生時指出，以及其結果為何。例如，在我們的 form-validation-checkbox-disabled.html 展示中，當核取框被核取時，可以讓另一個表單輸入允許輸入更進一步的資訊。我們已經設置一個隱藏的即時區塊：

```html
<p class="hidden-alert" aria-live="assertive"></p>
```

這是使用絕對位置從視窗中隱藏。當核取/未核取時，我們更新在隱藏的即時區塊中的文字，告訴螢幕報讀器使用者選取這個核取框時結果會是甚麼，以及更新 `aria-disabled` 的狀態，連同一些視覺的指示：

```js
function toggleMusician(bool) {
  var instruItem = formItems[formItems.length - 1];
  if (bool) {
    instruItem.input.disabled = false;
    instruItem.label.style.color = "#000";
    instruItem.input.setAttribute("aria-disabled", "false");
    hiddenAlert.textContent =
      "Instruments played field now enabled; use it to tell us what you play.";
  } else {
    instruItem.input.disabled = true;
    instruItem.label.style.color = "#999";
    instruItem.input.setAttribute("aria-disabled", "true");
    instruItem.input.removeAttribute("aria-label");
    hiddenAlert.textContent = "Instruments played field now disabled.";
  }
}
```

#### 描述非語意按鈕為按鈕

本課程我們已經談過幾次按鈕、連結或表單元素(參見 HTML 無障礙文章的 UI controls (en-US) ，以及前述增強鍵盤無障礙的原生無障礙(以及在使用其他元素偽造背後的無障礙議題)。基本上，使用 `tabindex` 與一些 JavaScript，在很多情況下，你可以在沒有太多困難下增加鍵盤無障礙支援功能。

但螢幕報讀器的情況如何呢？他們仍然無法將這些元素視為按鈕。如果我們用螢幕報讀器測試我們的 fake-div-buttons.html 範例，我們偽造的按鈕將會用句子如 "Click me!, group"讀出，很顯然地令人困惑。

我們可以使用 WAI-ARIA 角色來修正它。請複製 fake-div-buttons.html在本機，並且對每個按鈕`<div>`增加 `role="button"` ，範例如下：

```html
<div data-message="This is from the first button" tabindex="0" role="button">
  Click me!
</div>
```

現在當你使用螢幕報讀器測試它時，按鈕將會用句子如"Click me!, button" 讀出—這樣好多了。

**備註：** 記住最好盡可能使用正確的語意元素，如果你希望創建一個按鈕，並且可使用 `<button>` (en-US) 元素，就應該使用 `<button>` (en-US) 元素！

#### 透過複雜的插件引導使用者

除了標準 HTML 可用外，還有一堆其他角色可以辨識非語意的元素結構作為一般的使用者介面特徵，例如 `combobox`, `slider`, `tabpanel`, `tree`。你可在 Deque university code library中找到很多有用的範例，可給你這些控制措施如何做到無障礙的想法。

我們來看看我們自己的範例，我們回到我們簡單的絕對位置頁籤的介面(參見在我們的 CSS 與 JavaScript 無障礙文章中的 Hiding things (en-US) )，你可以找到 頁籤資訊框範例(看原始碼).

本範例以鍵盤無障礙而言運作正常—你可以開心地在不同的頁籤間跳位，並且選擇他們顯示該頁籤的內容，也相當地容易操作—你可以滾動內容並使用標題來導覽，即使你看不到螢幕上正發生的事情。然而內容是甚麼並非很明顯—螢幕報讀器目前以連結的清單報讀內容，以及有三個標題的內容。這樣無法給你了解內容之間的關係。最好給予使用者更多關於內容結構的線索。

為改善這些，我們創建新的範例版本為 aria-tabbed-info-box.html (看實際頁面)，我們已經更新頁籤介面的結構如下：

```html
<ul role="tablist">
  <li
 class="active"
 role="tab"
 aria-selected="true"
 aria-setsize="3"
 aria-posinset="1"
 tabindex="0">
    Tab 1
  </li>
  <li
 role="tab"
 aria-selected="false"
 aria-setsize="3"
 aria-posinset="2"
 tabindex="0">
    Tab 2
  </li>
  <li
 role="tab"
 aria-selected="false"
 aria-setsize="3"
 aria-posinset="3"
 tabindex="0">
    Tab 3
  </li>
</ul>
<div class="panels">
  <article class="active-panel" role="tabpanel" aria-hidden="false">
    ...
  </article>
  <article role="tabpanel" aria-hidden="true">...</article>
  <article role="tabpanel" aria-hidden="true">...</article>
</div>
```

**備註：** 這裡最引人注目的是我們移除在原來範例中的連結，並且只使用清單項目作為頁籤—這樣做是因為對螢幕報讀器使用者比較少困擾(連結並非真正地帶妳到哪個地方；他們只是改變視窗)，並且可讓組件大小/位置在組件特徵中有很好的運作—當這些是放在連結上的時候，瀏覽器將維持報讀"1 of 1"，而非"1 of 3"、"2 of 3"等。

新的特徵如下：

* 新的角色— `tablist`, `tab`, `tabpanel` — 這些確認頁籤介面的重要區域—頁籤的容器、頁籤本身，以及相對應的頁籤控制盤。
* `aria-selected` — 定義目前被選取的是哪一個頁籤。當不同的頁籤被使用者選取時，本屬性在不同頁籤的值透過 JavaScript 更新。
* `aria-hidden` — 隱藏元素不讓螢幕報讀器讀出來。當不同的頁籤被使用者選取時，本屬性在不同頁籤的值透過 JavaScript 更新。
* `tabindex="0"` — 由於我們已經移除連結，我們需要給予清單項目本屬性，提供鍵盤焦點。
* `aria-setsize` — T 本屬性允許你向螢幕報讀器指定某元素是一系列中的部分，以及該系列有多少項目。
* `aria-posinset` — 本屬性允許你指定某個元素在一系列中的位置，伴隨 `aria-setsize，`提供螢幕報讀器有足夠資訊來告訴你目前的位置在"1 of 3"項目等。在許多情況下，瀏覽器應該可從元素層級結構推測這項資訊，但它肯定有助於提供更多的線索。

在我們的測試中，這些新的結構確實提供整體的改善。頁籤現在被認定為頁籤（如螢幕報讀器讀出「索引標籤」），被選取的頁籤以「已選取」指出並讀出頁籤的名稱，螢幕報讀器也告訴你目前所在的頁籤數目。此外，因為設置 `aria-hidden`（只有非隱藏的頁籤才設定 `aria-hidden="false"`），非隱藏的頁籤是唯一你可以向下導覽的內容，意即所選取的內容很容易找到。

**備註：** 如果有任何你很明確地不希望螢幕報讀器讀出的內容，你可賦予它們 `aria-hidden="true"` 屬性。

總結
--

本文並未涵蓋所有 WAI-ARIA 的內容，但應該給予你足夠的資訊來了解如何使用它，以及了解一些你會遇到而需要它的最常見型態。

相關參考資訊
------

* Definition of Roles — ARIA 角色參考資訊。
* Definitions of States and Properties (all aria-\* attributes) — 屬性與狀態參考資訊。
* Deque university code library — 實用的範例資源庫，展示使用 WAI-ARIA 特徵讓複雜的 UI 控制措施無障礙
* WAI-ARIA Authoring Practices — 由 W3C 發佈之非常詳細的設計模型，解釋如何使用 WAI-ARIA 特徵實作不同類型的複雜 UI 控制措施無障礙
* ARIA in HTML — W3C 標準針對每一個 HTML 特徵定義由瀏覽器設置隱含特徵的無障礙(ARIA)語意，以及如果需要額外的語意，你可以設置的 WAI-ARIA 特徵。
* 前頁  (en-US)
* Overview: Accessibility
* 次頁  (en-US)


JavaScript 物件介紹 - 學習該如何開發 Web

JavaScript 物件介紹
===============

在 JavaScript 裡面，從諸如字串與陣列的核心功能、到以 JavaScript 建構的瀏覽器 API，大部分的東西都可算是「物件 (Object)」。你甚至可建立自己的物件，將相關函式與變數封裝 (Encapsulate) 為有效封包，並可作為多樣的資料容器 (Data container)。如果你想更精進既有的程式語言知識，就必須了解 JavaScript 的「物件導向 (Object-Oriented；OO)」本質。為此，我們設計了相關文章來協助你更進一步。本文將先說明物件理論和語法的細節，再引導你建立自己的物件。

必備條件
----

在開始閱讀本文之前，你應該已經對 HTML 與 CSS 有一定程度的認識。建議你先看過〈HTML 介紹 (en-US)〉以及〈CSS 介紹〉，再開始了解 JavaScript。

你也應該已經初步了解過 JavaScript 基本概念，再進一步閱讀 JavaScript 物件。所以另請先看過〈JavaScript 的第一步〉與〈JavaScript 基礎要件〉。

**備註：** 如果你在使用的桌機＼平板＼其他裝置，無法讓你建立自己的檔案，則可透過如 JSBin 或 Glitch 的線上編碼程式，來體驗（大多數的）範例程式碼。

指南
--

物件基本概念

第一篇主述 JavaScript 物件。我們將說明基本的 JavaScript 物件語法，並重新講解某些先前已經說過的 JavaScript 功能，也會再提及許多物件是你現正使用中的功能。

適合初學者的 OO JaveScript

說明過基本概念之後，就會將重點放在物件導向的 JavaScript (OOJS) 本質上。本文會先介紹 OO 程式設計的基礎理論，再說明 JavaScript 是如何透過建構子 (Constructor) 函式模擬物件類別並建立物件實體 (Instance)。

物件原型

原型 (Prototype) 是 JavaScript 物件用以互相繼承功能的機制，且其與典型 OO 程式語言中的繼承機制有所不同。本文將探討其中相異性、說明原型鏈的運作方式，並透過原型屬性將函式新增至現有建構子。

JavaScript 中的繼承

再說明了 JavaScript 大多數的 OO 特性之後，將說明應如何建立「子」物件類別 (建構子) 並繼承其「母」類別的功能。此外，我們將針對你可能使用 OOJS 的時機提供建言。

利用 JSON 資料

JavaScript Object Notation (JSON) 為標準格式，用以將「結構化資料 (Structured data)」呈現為 JavaScript 物件，並常用於網站之間呈現＼傳輸資料 (如從伺服器傳送資料到用戶端，以於網頁上顯示內容)。你一定會常接觸到類似情形，所以本文將提供用 JavaScript 搭配 JSON 開發時的所有資訊，包含在 JSON 物件中存取資料項目，以及撰寫你自己的 JSON。

物件打造實例

前幾篇文章帶領你看過基本的 JavaScript 物件理論和語法細節，幫你打下厚實的基礎。而本文要讓你實際操作，透過更多實例自訂出 JavaScript 物件，讓你享受多采多姿的學習過程 (讓你寫出彩色的跳跳球喔)。

評量
--

為彩色跳跳球展示範例新增其他功能

在此評量中，你已經先寫出了跳跳球範例。接著要讓你新增其他有趣的功能。


JavaScript 解決常見的問題 - 學習該如何開發 Web

JavaScript 解決常見的問題
==================

 以下連結針對你需要修復的常見問題提供解決方案，以便讓你的 JavaScript 正確執行。
初學者常見的錯誤
--------

### 糾正語法與程式碼

如果你的程式碼毫無反應或瀏覽器回應某些內容「未定義（undefined）」，請檢查你是否「正確輸入」所有變數名稱，函數名稱等。

以下為常見造成問題的預設瀏覽器功能比較：

| **正確** | **錯誤** |
| --- | --- |
| `getElementsByTagName()` | `getElementbyTagName()` |
| `getElementsByName()` | `getElementByName()` |
| `getElementsByClassName()` | `getElementByClassName()` |
| `getElementById()` | `getElementsById()` |

### 分號位置

你必須確定所有分號都放置在正確的位置。例如：

| **正確** | **錯誤** |
| --- | --- |
| `elem.style.color = 'red';` | `elem.style.color = 'red;'` |

### 功能內容

有很多種錯誤能造成函數無法正確執行，其中一個最常見的錯誤是宣告完函數後卻從未呼叫它。舉一個例子：

```js
function myFunction() {
  alert("這是我的功能。");
}
```

這段程式碼不會做任何事，直到它被呼叫，例如：

```js
myFunction();
```

#### 函數作用域

記得函數有自己的作用域——你無法從函數外部存取函數內部宣告的變數，除非你將該變數設為全域變數（也就是不在任何函數內部），或是回傳變數的值 (en-US)。

#### 在 return 敘述後執行語法

同樣記得當你把一個值從函數回傳出去後，JavaScript 編譯器便離開該函數——在 return 敘述之後的程式碼皆不會執行。

其實，當有程式碼出現在 return 敘述後時，某些瀏覽器會在開發人員工具的主控台中顯示 error 訊息，像是 Firefox 瀏覽器會顯示「unreachable code after return statement」。

### 物件表示法與正確的指定

通常當你在 JavaScript 指定某個值給一個變數時，你只會用到一個等號，例如：

```js
const myNumber = 0;
```

然而，這在物件裡是行不通的——此時你需要用冒號區隔物件成員的名稱與值，再用逗號區隔每個成員，例如：

```js
const myObject = {
  name: "Chris",
  age: 38,
};
```

基本定義
----

* 什麼是 JavaScript？
* 什麼是變數？
* 什麼是字串？
* 什麼是陣列？
* 什麼是迴圈？
* 什麼是函數？
* 什麼是事件？ (en-US)
* 什麼是物件？
* [什麼是 JSON？]/zh-TW/docs/Learn/JavaScript/Objects/JSON#說真的，到底什麼是\_json？)
* 什麼是 Web API？ (en-US)
* 什麼是 DOM？ (en-US)
基本使用例子
------

### 概括

* 如何將 JavaScript 加到網頁裡？
* 如何在 JavaScript 裡新增註解？
### 變數

* 如何宣告變數？
* 如何初始化變數的值？
* 如何改變變數的值？（更多請看指定運算子）
* JavaScript 有哪些資料型態？
* 「弱型別」是什麼意思？
### 數學運算

* 網路開發需要處理哪些型態的數字？
* 如何在 JavaScript 中進行基本數學運算？
* 什麼是運算次序，以及它在 JavaScript 中是如何運作的？
* 如何在 JavaScript 中遞增與遞減值？
* 如何在 JavaScript 比較不同的值？（例如比較哪一個值比較大，或是比較該值是否等同於另一個值）
### 字串

* 如何在 JavaScript 中建立字串？
* 要使用單引號還是雙引號？
* 如何在字串中處理跳脫字元？
* 如何串接字串？
* 可以將字串和數字串接在一起嗎？
* 如何獲得字串長度？
* 如何獲得位於字串特定位置的字元？
* 如何從字串裡找到並擷取指定子字串？
* 如何改變字串大小寫？
* 如何替換指定子字串？
### 陣列

* 如何建立陣列？
* 如何存取與修改陣列元素？（此篇文章包含多維陣列）
* 如何獲得陣列長度？
* 如何新增與移除陣列元素？
* 如何將字串分割成陣列元素，或將陣列元素串接成字串？
### JavaScript 除錯

* 有哪些 error 的基本型態？
* 什麼是瀏覽器開發人員工具以及如何獲取？ (en-US)
* 如何在 JavaScript 主控台輸出一個值？
* 如何使用中斷點及其它 JavaScript 除錯功能？

欲獲得更多 JavaScript 除錯相關資訊，參閱處理 JavaScript 常見問題；此外，參閱其它常見 error 以了解常見 error。

### 程式碼的條件判斷

* 如何基於變數的值或其它條件來執行不同程式碼區塊？
* 如何使用 if ...else 敘述？
* 如何置入巢狀條件區塊？
* 如何在 JavaScript 使用運算子「且」、「或」與「非」？
* 如何方便地基於一個條件處理大量決定？
* 如何使用三元運算子來在基於 true 或 false 判斷的兩個選項間快速決定
### 循環/迭代

* 如何一遍一遍地重覆執行相同的程式碼片段？
* 如何在滿足特定條件時提前離開迴圈？
* 如何在滿足特定條件時跳到下一次迴圈？
* 如何使用 while 與 do ... while 迴圈？
* 如何遍歷陣列元素？
* 如何遍歷多維陣列元素？
* 如何遍歷物件成員？
* 如何遍歷陣列中物件的成員？
進階使用例子
------

### 函數

* 如何在瀏覽器裡找到函數
* 函數（function）與方法（method）的區別是什麼？
* 如何創建自己的函數？ (en-US)
* 如何執行（呼叫）一個函數？
* 什麼是匿名函數？
* 如何在呼叫函數時指定參數？
* 什麼是函數作用域？
* 什麼是回傳值以及如何利用它？ (en-US)
### 物件

* 如何創建物件？
* 什麼是點記法（dot notation）？
* 什麼是括弧記法（bracket notation）？
* 如何獲取與設定物件方法與屬性？
* 這個 `this` 是什麼？
* 什麼是物件導向？
* 什麼是建構子與實例以及如何創建？
* 有哪些創建 JavaScript 物件的方式？
### JSON

* 如何建構 JSON 資料並從 JavaScript 讀取？
* 如何在網頁中載入 JSON 檔案？
* 如何把 JSON 物件轉換成字串以及轉換回去？
### 事件

* 什麼是事件處理器以及如何使用？ (en-US)
* 什麼是行內事件處理器？ (en-US)
* `addEventListener()`函數在做什麼以及如何使用？ (en-US)
* 我該使用哪種機制來在網頁中新增事件程式嗎？ (en-US)
* 什麼是事件物件以及如何使用？ (en-US)
* 如何阻止事件預設行為？ (en-US)
* 如何在巢狀元素中觸發事件？（事件傳遞機制：事件冒泡與捕獲） (en-US)
* 什麼是事件指派以及如何運作？ (en-US)
### JavaScript 物件導向

* 什麼是物件原型？
* 什麼是建構子屬型以及如何使用？
* 如何在建構子中新增方法？
* 如何新增一個繼承了父建構子成員的新建構子？
* JavaScript 中什麼時候該使用繼承？
### 網頁 Web API

* 如何使用 JavaScript 操控 DOM（例如新增或移除元素）？ (en-US)


JavaScript building blocks - 學習該如何開發 Web

JavaScript building blocks
==========================

在本單元中，我們繼續介紹所有 JavaScript 的關鍵基本功能，將注意力轉向常見的代碼塊類型，如條件語句，循環，函數和事件。 你已經在課程中看到了這些東西，但只是順便說一下 - 在這裡我們將明確地討論它。

先決條件
----

開始這個模組之前，你應該已經熟悉 HTML 和 CSS 的基礎知識了，你也應該閱讀完前一個模組 - JavaScript 入門 - 的內容了。

**備註：** 假如你正使用平板、電腦或任何無法讓你可以建立檔案的裝置時，你可以試著透過線上程式碼工具（像是 JSBin 或 Glitch）測試文章的測試碼。

指南
--

在代碼中做出決定 - 條件

在任何編程語言中，代碼都需要根據不同的輸入做出決策並相應地執行操作。 例如，在遊戲中，如果玩家的生命數量為 0，則遊戲結束。 在天氣應用程序中，如果在早上查看，則顯示日出圖形; 如果是夜晚，則顯示星星和月亮。 在本文中，我們將探討條件結構如何在 JavaScript 中工作。

程式碼迴圈

有時候你需要超過一行的程式碼完成一項任務，舉例來說：尋找一個擁有許多名字的清單。透過編輯程式，迴圈可以幫助你完美的完成這項工作，在本文中，我們將探討迴圈的結構如何在 JavaScript 中運作。

函式 — 可重複使用的程式碼區塊

撰寫程式的另一個基本概念是函式 (function)，它允許你在一個定義好的區塊內，存放一些程式碼。定義好的函式後，無論你需要在一個單一簡短的指令列用到它，或者是重複使用相同的片段程式碼，你都可以不段重複地呼叫這些函式執行對應的內容。在本文中，我們將探索函式的基本概念，像是基本語法、如何定義函式內容、以及函式將會使用的參數。

建立自己的函式 (en-US)

前面許多文章已經提到大部分的基本理論基礎了，在本文中，我們將分享一個實際的經驗，你將學習到關於打造自定義函式的實務範例。透過這樣的學習方式，我們也進一步解釋一些函式相關的細節知識。

函式回傳值 (en-US)

這堂課程中最後一個想要說明的基本概念就是函式的回傳值，一些函式執行完畢後，不會回傳一個數值；但有些函式則會。理解這些函式的回傳值是重要的概念，本文中，你將學會如何在自定義的函式中，回傳有用的數值提供給其他函式使用。

事件介紹 (en-US)

事件代表你所撰寫的程式在一套系統內所產生的動作或發生的事情，舉例來說：假如一位使用者在網頁點擊一個按鈕時，你可能想要讓使用者點擊該按鈕後，在畫面呈現一個訊息方塊。在本課程最後一篇文章內，我們將討論一些與事件相關的重要概念、以及這些事件在瀏覽器中，如何呈現給使用者。

評量
--

下方的試題將會測驗你對於上述幾項概念的 JavaScript 基本知識。

圖庫

我們已經看過許多 JavaScript 的基礎程式碼，我們將測驗你關於迴圈、函式、條件式迴圈與事件等相關的知識，測驗的方式是透過建立一個由 JavaScript 打造而成的圖庫應用程式。


用戶端 Web API - 學習該如何開發 Web

用戶端 Web API
===========

在為網站或應用程式編寫用戶端 JavaScript 時，你將很快遇到應用程式介面（API）。API 是應用程式介面，用於操作運行站點的瀏覽器和操作系統的不同方面，或操縱來自其他網站或服務的資料。在本單元中，我們將探討 API 是什麼，以及如何使用你在開發工作中經常遇到的一些最常見的 API。

必備知識
----

為了最有效學習本模組，你應該完成在此之前的一系列 JavaScript 模組（JavaScript 第一步、JavaScript 基礎要件與 JavaScript 物件介紹）。那些模組皆涉略到 API 的簡單用法，因為寫前端 JavaScript 很難不用到。在本教學中，我們假設你對核心 JavaScript 語言已有相當程度的認識，而我們將更深入探索常見的 Web API。

了解 HTML 和 CSS 的基礎知識也會有所幫助。

**備註：** 假如你正在使用 電腦/平板/其他裝置，你不需要建立自己的檔案，你可以嘗試線上程式撰寫系統來撰寫範例程式，像是 JSBin 或 Glitch。

概觀
--

Web API 簡介 (en-US)

首先，我們將從 API 的宏觀視角開始看起——它是什麼、如何運作、如何在程式碼裡運用，以及它是如何建構起來的？我們還將探討不同 API 主要類別分別是什麼，以及它們有什麼功能。

文件操作 (en-US)

在撰寫網頁或應用程式時，你會想做的其中一件最常見的事情，是透過某個辦法操控網頁文件。這通常藉由使用文件物件模型（Document Object Model，簡稱 DOM）達成，這是一組 API，用來控制 HTML 與修飾大量使用 `Document` (en-US) 物件的訊息。這篇文章中，我們將深入探索如何使用 DOM，以及其它一些能把你的環境變得很有趣的好玩 API。

從伺服器獲取資料

另一個在當今網站與應用程式非常常見的任務是從伺服器獲取個別資料來更新網頁的一部份而不用重新渲染整個網頁。這個似乎不起眼的小細節對網站效能與表現有著巨大影響。這篇文章中，我們將解釋概念，並看看讓這件事成真的技術，像是 `XMLHttpRequest` 以及 Fetch API。

第三方 API (en-US)

我們到目前為止提到的 API 皆內建在瀏覽器裡，但並非所有 API 都是。許多像 Google 地圖、Twitter、Facebook、PayPal 等等的大型網站與服務皆提供 API 讓開發者能利用他們的資料（例如將 Twitter 貼文顯示在你的部落格上）或服務（例如將自訂 Google 地圖顯示在你的網站上、或讓你的使用者透過 Facebook 帳號登入）。這篇文章會看到瀏覽器 API 與第三方 API 的區別，以及後者的一些典型用法。

繪製圖形 (en-US)

瀏覽器含有一些非常強大的圖形程式設計工具，從可縮放向量圖形（SVG）語言，到用來在 HTML `<canvas>` 元素與 WebGL (en-US)) 上繪圖的 API。這篇文章介紹 Canvas API，並提供讓你能深入學習的資源。

視訊及音訊 API (en-US)

HTML5 擁有將多媒體嵌入到文件中的元素——`<video>` (en-US) 及 `<audio>` (en-US)——並衍生出用來控制播放、查詢等等的它們自己的 API。這篇文章向你展示如何做到諸如創造自訂播放器等常見任務。

用戶端儲存 (en-US)

當今網路瀏覽器提供很多不同技術，讓你能儲存網站相關的資料並在需要時取出，使你能長期保存資料、離線儲存網站，以及更多。這篇文章用最簡單的方式解釋這些如何運作。


非同步的 JavaScript - 學習該如何開發 Web

非同步的 JavaScript
===============

在本單元我們來討論非同步的（ asynchronous ） JavaScript ，為何其如此重要，並了解它如何有效率的處理像是從伺服器獲取資源的這類潛在性阻塞（ blocking ）操作

#### 想要成為前端開發人員?

我們已為你準備一門實現你目標所需要具備的所有基礎知識課程

**立即開始** (en-US)

事前準備
----

非同步的 JavaScript 是一個相當進階的主題，因此建議你在嘗試本單元前能先通過 JavaScript 初探以及 JavaScript 構成元素單元。

如果你對非同步程式設計的概念還不太熟悉，強烈建議你應該先從非同步程式設計通用概念的文章開始學習。如果你已經具備其概念，那麼你或許可以跳至非同步的 JavaScript 介紹單元開始。

**備註：** 如果你正在使用電腦／平板／任何其它無法讓你建立檔案的裝置上，你可以嘗試在 JSBin 或是 Glitch 上測試本單元的範例程式碼。

指南
--

非同步程式設計通用概念

在本篇文章我們會介紹一些關於非同步程式設計的重要觀念，以及在網頁瀏覽器和 JavaScript 中的行為。在閱讀其他文章之前你應該先具備這些觀念。

非同步的 JavaScript 介紹

在本篇文章中我們會先簡短的回顧我們在同步的 JavaScript 中所遭遇到的問題，並預先看看稍後將會使用哪些非同步的 JavaScript 技巧來解決此問題。

協同的非同步 JavaScript ： Timeout 和 interval

在這裡看看我們在傳統上是如何透過設定的時間到期或是透過定時的方式（例如：每秒發生的次數）讓 Javascript 能夠以非同步的方式執行程式碼，談談這些方法有哪些用處以及存在哪些既有的問題。

優雅的使用 Promise 來處理非同步操作 (en-US)

Promise 是在 Javascript 語言中相對較新的功能，它能夠讓你延遲活動直到先前的活動回報完成或失敗。這方法對設置一連串的操作並讓其正確的循序執行相當有用。本篇文章向你展示 Promise 是如何運作，你將會看到如何被使用在 WebAPI ，以及如何寫出屬於自己的 Promise 。

利用 async 及 await 讓非同步程式設計變得更容易 (en-US)

Promise 在設置上可能會有些複雜並難以理解，因此現代瀏覽器已經實作出 `async` 函式以及 `await` 運算子。前者能夠讓標準的函式隱含的使用 Promise 方式來實現非同步行為，然而後者可以被用在 `async` 函式內部，讓程式碼繼續執行之前去等待一個 Promise 完成。這能讓我們在鏈結一連串的 Promise 的情況之下更加簡潔易懂。

參閱
--

* Asynchronous Programming 來自作者為 Marijn Haverbeke 的極佳的 Eloquent JavaScript 電子書。


JavaScript 初探 - 學習該如何開發 Web

JavaScript 初探
=============

帶領各位開始體驗撰寫 JavaScript 前，在第一個 JavaScript 單元中，我們首先來回答一些基本的問題，像是「什麼是 JavaScript 啊？」、「它長什麼樣子？」以及「它能拿來做什麼？」。之後我們會詳細討論幾個關鍵元素，諸如變量(variables)、字串(strings)、數字(numbers)和陣列(arrays)。

事前準備
----

在開始這個教學之前，你不需要具備任何的 JavaScript 相關知識，但是你應該對 HTML 及 CSS 有一點熟悉。建議你在開始學習 JavaScript 前，先看過下列內容 :

* 開始使用 Web (包括 JavaScript 基礎簡介).
* HTML 簡介.
* CSS 簡介.

**備註：** 假如你正在使用 電腦/平板/其他裝置，你還不需要知道如何建立自己的檔案，你可以在線上程式撰寫系統來嘗試範例程式，像是 JSBin 或 Glitch。

概觀
--

什麼是 JavaScript?

歡迎來到 MDN JavaScript 新手村教學！在第一個章節中我們將一個較高的層次看 JavaScript，回答像是「它是什麼？」、「有什麼用？」之類的問題，並且確保你能了解 JavaScript 的目的。

和 JavaScript 的第一次接觸

現在你已經學到一些關於 JavaScript 的原理，以及可以用它來作什麼，我們將經由完整的引導，給你一個關於 JavaScript 基礎功能的速成課。這裡你將會一步一步地建立一個簡單的「猜數字」遊戲。

什麼出錯了？ JavaScript 的疑難排解（除錯）

當你在前一章節建立的「猜數字」遊戲過程中，你可能會發現它不能運作。不要慌，這個章節的目的是提供一些提示，用來找出及修正在 JavaScript 程式裡的錯誤，讓你不會因為這些問題而煩惱。

儲存你需要的資訊 — 變數

在讀過前面幾個章節後，你現在應該能由較高的層次了解 JavaScript 是什麼，它能為你做些什麼，如何與其他的網站技術一起使用，以及它的主要的特性。在本章節中，我們將深入探討真正的基礎知識，看看 JavaScript 最基本的組成 — 變數（Variables）是如何運作 。

JavaScript 的基本運算— 數字 與 運算子

本章的重點，我們討論 JavaScript 的數值計算 — 我們如何依我們的要求組合運算子與其它元素來操控數值。

處理文字 — JavaScript 的字串

接下來我們將焦點轉到字串（strings） — 這是程式裡對一段文字的稱呼。這個章節我們將會檢視學習 JavaScript 中，所有你通常想要知道於字串事情，例如創建字串、字串跳脫（escaping）操作，以及把它們組合起來。

有用的字串方法

我們已經看過了字串的基礎知識，現在讓我們加快腳步，看看可以使用內建的方法對字串進行哪些有用的操作 ，例如：取得字串的長度、串接或分割字串、替換字串中的某個字……等等。

陣列

在這個單元的最後一個章節，我們來看看陣列 (Array) — 把一連串資料整齊地儲存在一個變數中。我們會看到為什麼它有用，然後探索如何創建一個陣列，對陣列內的項目進行檢索、新增、刪除……等操作。

測試一下
----

下面的任務會測試你對於前面提到的 JavaScript 基本概念是否了解。

傻故事產生器

本次測試中，你的任務是使用在這個單元所學到的知識，建立一個能隨機產生笑話的有趣程式。祝你愉快！

參考資源
----

Learn JavaScript

對於想成為網站開發者一個很好的資源，以互動的方式學習 JavaScript ，包含短課程、互動測驗，自動評估狀況給予指引。前 40 堂課是免費，完整的課程可以在一次付費買下。


為彈跳彩球添增其他功能 - 學習該如何開發 Web

為彈跳彩球添增其他功能
===========

* 前頁
* Overview: Objects

在本文中，你將繼續使用前一篇文章的彈跳彩球展示程式，另外加入幾項有趣的新功能。

|  |  |
| --- | --- |
| 必備條件： | 在開始本文所提的實作之前，應先看過先前的相關文章。 |
| 要點： | 測試 JavaScript 物件與 OO 架構的完整性。 |

開始
--

在開始之前，請先複製先前文章所提供的 index-finished.html、style.css、main-finished.js 等檔案，儲存於本端磁碟的新資料夾中。

**備註：** 你也可透過如 JSBin 或 Glitch 等網站進行此一實作。你可將 HTML、CSS、JavaScript 貼入相關線上編輯器之一。如果你所用的線上編輯器並未提供獨立的 JavaScript/CSS 面板，則可將之放入 HTML 頁面內的行內 `<script>`/`<style>` 元素中。

專案簡介
----

彈跳彩球很有趣，但接著我們要加入使用者可控制的「邪惡圈」，在碰到彩球之後隨即吃掉彩球，添加更多互動性。也希望透過邪惡圈與彩球所繼承的通用 `Shape()` 物件，測試你的物件技術。最後還要加上計分功能，顯示尚未吃掉的彩球數量。

下方擷圖則讓你了解最終成品的樣子：

![](/zh-TW/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features/bouncing-evil-circle.png)

可先參考完成範例讓心裡有個底 (別偷看原始碼啊！)

須進行的步驟
------

下列段落將逐步說明。

### 建立新物件

首先將現有的 `Ball()` 建構子變更為 `Shape()` 建構子，以及新的 `Ball()` 建構子：

1. `Shape()` 建構子對 `x`、`y`、`velX`、`velY` 屬性的定義方式，就如同 `Ball()` 建構子所用的方式。
2. 另須定義新的 `exists` 屬性，用以追蹤球體是否存在於程式之中 (也就是尚未遭邪惡圈所吃掉)。此屬性必為布林值 (Boolean)，初始值為 `true`。
3. `Ball()` 建構子應從 `Shape()` 建構子繼承 `x`、`y`、`velX`、`velY`、`exists` 等屬性。另必須將這些屬性定義為參數以利呼叫之。
4. 必須定義 `color` 與 `size` 屬性各 1 組，且由於兩者均來自於原始的 `Ball()` 建構子之中，所以剛開始的隨機值亦須相同。
5. 記得應正確設定 `Ball()` 建構子的 `prototype` 與 `constructor`。

彩球的 `draw()`、`update()`、`collisionDetect()` 函式定義，均與之前完全相同。

到此為止可重新載入程式碼，搭配重新設計的物件也應該運作無誤。

### 定義 EvilCircle()

再來見見這個壞蛋 — `EvilCircle()`！這個遊戲要加入 1 個會吃球的邪惡圈，而且要透過繼承自 `Shape()` 的建構子來定義這個邪惡圈。你可能也想添增另個讓第二個玩家控制的圈圈，或許多加幾個由電腦控制的邪惡圈。當然，光一個邪惡圈並無法統治世界，但可為此遊戲增添不少樂趣。

`EvilCircle()` 建構子應繼承 `Shape()` 的 `x`、`y`、`exists`。

亦可定義自有的屬性如下：

* `color` — `'white'`
* `size` — `10`
* `velX` — `20`
* `velY` — `20`

再次提醒，請記得要將所繼承的屬性在建構子中定義為參數，並應正確設定 `prototype` 與 `constructor` 屬性。

### 定義 EvilCircle() 的函式

`EvilCircle()` 應具備 4 個函式，如下：

#### `draw()`

此函式的功能與 `Ball()` 的 `draw()` 函式相同，就是在 canvas 上繪製物件實體；且運作的方式也類似，所以你可以複製 `Ball.prototype.draw` 定義來開始。接著要完成下列改變：

* 我們要空心的邪惡圈，但不只是單純實心黑線畫成的圓而已 (要有邊框)。只要將 `fillStyle` (en-US) 與 `fill()` (en-US) 更新成 `strokeStyle` (en-US) 與 `stroke()` (en-US) 即可辦到。
* 還可以讓邊框更粗一點，有助你更容易看到邪惡圈。只要在 `beginPath()` (en-US) 呼叫之後的某個地方設定 `lineWidth` (en-US) 的值 (「3」就可以) 即可。

#### `checkBounds()`

此函式功能就與 `Ball()` 的 `update()` 函式第一部分相同，負責邪惡圈是否跳出螢幕邊界之外並適時阻止。同樣的，你還是可以複製 `Ball.prototype.update` 定義來用，但須更改下列：

* Get rid of the last two lines — we don't want to automatically update the evil circle's position on every frame, because we will be moving it in some other way, as you'll see below.
* Inside the `if()` statements, if the tests return true we don't want to update `velX`/`velY`; we want to instead change the value of `x`/`y` so the evil circle is bounced back onto the screen slightly. Adding or subtracting (as appropriate) the evil circle's `size` property would make sense.

#### `setControls()`

This method will add an `onkeydown` event listener to the `window` object so that when certain keyboard keys are pressed, we can move the evil circle around. The following code block should be put inside the method definition:

```js
var _this = this;
window.onkeydown = function (e) {
  if (e.keyCode === 65) {
    _this.x -= _this.velX;
  } else if (e.keyCode === 68) {
    _this.x += _this.velX;
  } else if (e.keyCode === 87) {
    _this.y -= _this.velY;
  } else if (e.keyCode === 83) {
    _this.y += _this.velY;
  }
};
```

So when a key is pressed, the event object's keyCode (en-US) property is consulted to see which key is pressed. If it is one of the four represented by the specified keycodes, then the evil circle will move left/right/up/down.

* For a bonus point, let us know which keys the specified keycodes map to.
* For another bonus point, can you tell us why we've had to set `var _this = this;` in the position it is in? It is something to do with function scope.

#### `collisionDetect()`

This method will act in a very similar way to `Ball()`'s `collisionDetect()` method, so you can use a copy of that as the basis of this new method. But there are a couple of differences:

* In the outer `if` statement, you no longer need to check whether the current ball in the iteration is the same as the ball that is doing the checking — because it is not longer a ball, it is the evil circle! Instead, you need to do a test to see if the ball being checked exists (with which property could you do this with?). If it doesn't exist, it has already been eaten by the evil circle, so there is no need to check it again.
* In the inner `if` statement, you no longer want to make the objects change color when a collision is detected — instead, you want to set any balls that collide with the evil circle to not exist any more (again, how do you think you'd do that?).
### Bringing the evil circle into the program

Now we've defined the evil circle, we need to actually make it appear in our scene. To do this, you need to make some changes to the `loop()` function.

* First of all, create a new evil circle object instance, then call its `setControls()` method. You only need to do these two things once, not on every iteration of the loop.
* At the point where you loop through every ball and call the `draw()`, `update()`, and `collisionDetect()` functions for each one, make it so that these functions are only called if the current ball exists.
* Call the evil ball instance's `draw()`, `checkBounds()`, and `collisionDetect()` methods on every iteration of the loop.
### Implementing the score counter

To implement the score counter, follow the following steps:

1. In your HTML file, add a `<p>` (en-US) element just below the `<h1>` (en-US) element containing the text "Ball count: ".
2. In your CSS file, add the following rule at the bottom:

```css
p {
  position: absolute;
  margin: 0;
  top: 35px;
  right: 5px;
  color: #aaa;
}
```

3. In your JavaScript, make the following updates:
    * Create a variable that stores a reference to the paragraph.
    * Keep a count of the number of balls on screen in some way.
    * Increment the count and display the updated number of balls each time a ball is added to the scene.
    * Decrement the count and display the updated number of balls each time the evil circle eats a ball (causes it not to exist).
Hints and tips
--------------

* This assessment is quite challenging. Take each step slowly and carefully.
* It might be an idea to keep a separate copy of the demo after you get each stage working, so you can refer back to it if you find yourself in trouble later on.
交作業
---

如果你是在某個課堂上操作這份作業，那麼請將成品交給你的老師 / 助教；如果你是自學者，在我們的專屬討論區或 Mozilla IRC 上的 #mdn 頻道都可以很輕鬆地找到人給予指教。記得先認真做一下習題，要怎麼收獲先那麼栽呀！

* 前頁
* Overview: Objects


物件原型 - 學習該如何開發 Web

物件原型
====

* 前頁
* Overview: Objects
* 次頁

JavaScript 的物件即透過原型 (Prototype) 機制相互繼承功能，且與典型的物件導向 (OO) 程式語言相較，其運作方式有所差異。我們將透過本文說明相異之處、解釋原型鍊 (Prototype chain) 運作的方式，並了解原型屬性是如何將函式新增至現有的建構子 (Constructor) 之中。

|  |  |
| --- | --- |
| 必備條件： | 基本的電腦素養、已初步了解 HTML 與 CSS、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉以及 OOJS 基礎概念 (參閱〈Introduction to objects (en-US)〉。 |
| 主旨： |
 了解 JavaScript
 的物件原型、原型鍊的運作方式、應如何將新的函式加入原型屬性之中。
  |

「原型」架構的程式語言？
------------

常有人說 JavaScript 是**原型架構的程式語言** — 各個物件均具備 1 組**原型物件**作為範本物件，用以繼承函式與屬性。物件的原型物件可能也具備原型物件，並繼承了其上的函式與屬性。這就是我們所謂的「**原型鍊 (Prototype chain)**」，同時正好說明為何 A 物件的屬性與函式是透過 B 物件的屬性與函式所定義。

精確點說，這些屬性與函式都是透過物件的建構子函式所定義，並非物件實例本身。

傳統的 OOP 都是先定義了類別，接著在建立物件實例之後，在類型上定義的所有屬性與函式均複製到此實例。但 JavaScript 不會複製這些屬性與函式，卻是在物件實例與其建構子之間設定連結 (原型鍊中的連結)，只要順著原型鍊就能在建構子之中找到屬性與函式。

先看個範例會比較清楚點。

了解原型物件
------

先回到我們寫過的 `Person()` 建構子範例。在你的瀏覽器裡載入範例。如果你還沒看完前篇文章並製作出此範例，可先使用 oojs-class-further-exercises.html 這個範例 (可看到原始碼)。

我們在此範例中定義了建構子函式：

```js
function Person(first, last, age, gender, interests) {
  // property and method definitions
}
```

接著建立如下的物件實例：

```js
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
```

如果你在自己的 JavaScript 主控台中鍵入「`person1.」，應該會看到瀏覽器根據此物件可用的成員名稱開始自動補完：`

![](/zh-TW/docs/Learn/JavaScript/Objects/Object_prototypes/object-available-members.png)

在此列表中，可以看到 `person1` 原型物件上所定義的成員，也就是 `Person()` 建構子 — `name`、`age`、`gender`、`interests`、`bio`、`greeting`。你也會看到其他如 `watch`、`valueOf` 等，同樣也是定義在 `Person()` 建構子原型物件之上的成員，如此構成 `Object` (en-US)。下圖顯示原型鍊的運作方式。

![](/zh-TW/docs/Learn/JavaScript/Objects/Object_prototypes/mdn-graphics-person-person-object-2.png)

所以當你在 `person1 上呼叫了「實際上是定義於 Object 上的函式」，會發生什麼事呢？舉例來說：`

```js
person1.valueOf();
```

此函式僅回傳所呼叫的物件數值。此範例所將發生的是：

* 瀏覽器先檢查 `person1` 物件上是否有可用的 `valueOf()` 函式。
* 其實沒有，所以瀏覽器接著檢查 `person1` 物件的原型物件 (`Person`) 上是否有可用的 `valueOf()` 函式。
* 同樣沒有，所以瀏覽器再檢查 `Person()` 建構子的原型物件 (`Object`) 上是否有可用的 `valueOf()` 函式。這次有，所以就會呼叫。

**備註：** 再次重申，在原型鍊中的函式與屬性並**不是**從任一物件複製到另一個物件，而是如上述的，沿著該原型鍊向上存取而得。

**備註：** 直接存取物件的原型物件，並沒有一定的方式。原型鍊中，項目之間的「連結」均定義於內部屬性之內，即 JavaScript 規格中的 `[[prototype]]` (可參閱 ECMAScript)。新版瀏覽器均具備所謂的「`__proto__` (en-US) (兩邊都是 2 個底線)」屬性，其中就包含了物件的原型物件。舉例來說，你可嘗試「`person1.__proto__」`與「`person1.__proto__.__proto__`」看看程式碼中的鍊會是什麼樣子！

原型屬性也定義所要繼承的成員
--------------

所以該在哪裡定義所要繼承的屬性與函式呢？若看一下 `Object` (en-US) 參考頁面，你就會看到左邊列出許多屬性與函式，遠超過上方擷圖所列 `person1` 物件所繼承的成員數量。有些繼承了，有些則無？為什麼呢？

原因在於，繼承的成員就是在 `prototype` 屬性 (你也能稱之為子命名空間 sub namespace) 中定義的成員，也就是以「`Object.prototype`.」開頭的成員；並非只以「`Object`.」開頭的成員。`prototype` 屬性值就是 1 個物件，基本上儲存了許多我們想「讓原型鍊上的物件一路繼承下去」的屬性與函式。

所以如 `Object.prototype.watch()`、`Object.prototype.valueOf()` (en-US) 等等，均可用於繼承自 `Object()` 的任何物件類型，包含以建構子建立的新物件實例。

`Object.is()` (en-US)、`Object.keys()`，及其他未於 `prototype` 內定義的成員，也就不會繼承至 1). 物件實例或 2). 從 `Object()` 繼承而來的物件類型。這些函式、屬性都只能用於 `Object()` 建構子本身。

**備註：** 這看起來很奇怪：你怎麼能在建構子上定義函式 (Method)，而且這建構子本身也是函式 (Function)？其實「Function」也屬於一個物件類型，可參閱 `Function()` (en-US) 建構子參考以進一步了解。

1. 你可自行檢查現有的原型屬性。回到我們之前的範例，試著於 JavaScript 主控台中輸入：

```js
Person.prototype;
```

2. 輸出結果很平淡，畢竟我們並未在自定的建構子原型上定義任何東西。依預設值，建構子的 `prototype` 都是從空白開始。現在可嘗試下列：

```js
Object.prototype;
```

這樣就會看到 `Object` 的 `prototype` 屬性中所定義的許多函式，而繼承自 `Object` 的物件也能找到這些函式。

只要試著尋找如 `String`、`Date`、`Number`、`Array` 等全域物件的原型上定義的函式與屬性，就會看到 JavaScript 中的其他原型鍊繼承範例。這些物件都在其原型上定義了多個成員，因此可作為你建立字串時的範例：

```js
var myString = "This is my string.";
```

`myString` 上立刻就有多個有用的函式，如 `split()` (en-US)、`indexOf()` (en-US)、`replace()` (en-US) 等。

**警告：** `prototype` 這個屬性，是 JavaScript 中最讓人混淆的名稱之一。你可能會認為`this`屬性即指目前物件(current object)的原型物件(prototype object)，但它其實不是原型 (應該是可透過 `__proto__` 存取的內部物件(internal object)才對，記得上面說過的嗎？)。`prototype`是一個物件(object)，內含了你定義所應該繼承的成員。

再次溫習 create()
-------------

我們先前講過用 `Object.create()` 函式建立新物件實例的方法。

1. 舉例來說，你可先在前面的 JavaScript 主控台範例中試著輸入：

```js
var person2 = Object.create(person1);
```

2. `create()` 實際上是透過特定的原型物件，來建立新的物件。我們在這裡將 `person1` 作為原型物件，建立了 `person2`。你可於主控台輸入下列以測試之：

```js
person2.__proto__;
```

如此將回傳 `person1` 物件。

建構子的屬性
------

每個物件實例都具備 1 個`建構子` (en-US)屬性，指向「用以建立實例」的原始建構子函式。

1. 舉例來說，若在主控台中輸入下列指令：

```js
person1.constructor;
person2.constructor;
```

應該兩者都會回傳 `Person()` 建構子，因為此建構子包含這些實例的原始定義。
 偷吃步的方法，是將圓括弧加到 `constructor` 屬性 (須包含任何必要參數) 末端，以從該建構子建立其他物件實例。畢竟建構子也是函式 (Function)，所以可透過圓括弧將之觸發。你只要納入 `new` 這個關鍵字，即可將此函式作為建構子。
2. 在主控台中輸入：

```js
var person3 = new person1.constructor("Karen", "Stephenson", 26, "female", [
  "playing drums",
  "mountain climbing",
]);
```

3. 現在可試著存取新物件的功能，例如：

```js
person3.name.first;
person3.age;
person3.bio();
```

這樣運作得還不差。你不需常常用這方法，但當你要建立新的實例，又因為某些原因找不到原始建構子的參照，這就特別有用了。

此外，`constructor` (en-US) 屬性還有其他用處。舉例來說，如果你有個物件實例，並要回傳建構子 (本身就是實例) 的名稱，就透過：

```js
instanceName.constructor.name;
```

也可嘗試：

```js
person1.constructor.name;
```

修改原型
----

先看看建構子的 `prototype` 屬性的修改範例：

1. 回到 oojs-class-further-exercises.html 範例，先在本機儲存 1 份原始碼的副本。在現成的 JavaScript 中加入下列程式碼，即是將新函式新增到建構子的 `prototype` 屬性：

```js
Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

2. 儲存程式碼並在瀏覽器中載入頁面，再輸入下列程式碼：

```js
person1.farewell();
```

這時應該會看到警示訊息且內含了建構子所定義的人名。這樣很有用，但如果能動態更新整個繼承鍊，且從建構子分割出來的所有物件實例都能使用此新的函式，就會更有用！

花個 1 分鐘想想，我們的程式碼中定義了建構子，然後根據建構子建立實例物件，接著將新函式添增到建構子的原型：

```js
function Person(first, last, age, gender, interests) {
  // property and method definitions
}

var person1 = new Person("Tammi", "Smith", 32, "neutral", [
  "music",
  "skiing",
  "kickboxing",
]);

Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

但是 `farewell()` 函式仍可用於 `person1` 物件實例，其可用的功能已自動更新過。如此證明了我們之前對原型鍊的說明，也代表瀏覽器會沿著鍊往上找「尚未於物件實例上定義的函式」，而非「複製到實例中的函式」。如此可建構強大且靈活的系統。

**備註：** 如果你在讓此範例運作時感覺有點困難，可參閱 oojs-class-prototype.html 範例 (也可看即時運作的情形)。

你很少會看到在 `prototype` 屬性上定義的屬性，因為照此範例定義的屬性彈性較低，舉例來說，你可新增如下的屬性：

```js
Person.prototype.fullName = "Bob Smith";
```

但因為幾乎不會有人取這名字，所以就沒什麼彈性。最好可以在 `name.first` 與 `name.last 之外建立 fullName：`

```js
Person.prototype.fullName = this.name.first + " " + this.name.last;
```

但因為這樣會參照全域範圍，而非函式範圍，所以也不適用。若呼叫此屬性，則將回傳 `undefined undefined`。這種模式適合我們先前於原型中定義的函式，因為該函式就是在功能範圍之內，且可成功轉移至物件實例的的範圍。因此你可能會在原型中定義常數屬性 (也就是永遠不需更改的屬性)，但一般來說會比較適合在建構子中定義屬性。

事實上，許多物件定義較常見的模式，就是在建構子中定義屬性，而在原型中定義函式。這樣一來，建構子只有屬性定義；函式則切分到不同的區塊，讓整個程式碼較清楚易讀。舉例來說：

```js
// Constructor with property definitions

function Test(a,b,c,d) {
  // property definitions
};

// First method definition

Test.prototype.x = function () { ... }

// Second method definition

Test.prototype.y = function () { ... }

// etc.
```

你可在 Piotr Zalewa 的「school plan app」範例中看到實際運作的範例。

摘要
--

本文說明了 JavaScript 物件原型，包含原型物件鍊是如何讓物件能互相繼承其特性、原型屬性的本質、原型屬性又是如何能將函式新增至建構子，以及其他相關概念。

接著我們將讓你在自己的任 2 個自訂物件之間，實作功能的繼承。

* 前頁
* Overview: Objects
* 次頁


使用 JSON 資料 - 學習該如何開發 Web

使用 JSON 資料
==========

* 前頁
* Overview: Objects
* 次頁

JavaScript Object Notation (JSON) 為將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式，常用於網站上的資料呈現、傳輸 (例如將資料從伺服器送至用戶端，以利顯示網頁)。你應該會常常遇到，因此本文將說明 JavaScript 搭配 JSON 時所應知道的觀念，包含如何在 JSON 物件中存取資料項目，並寫出你自己的 JSON。

|  |  |
| --- | --- |
| 必要條件： | 基礎的計算機素養、了解 HTML 與 CSS 的基本概念、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉) 與 OOJS 基本概念 (參閱〈Introduction to objects (en-US)〉)。 |
| 主旨： | 了解應如何使用 JSON 格式所儲存的資料，建立自己的 JSON 物件。 |

說真的，到底什麼是 JSON？
---------------

JSON 是依照 JavaScript 物件語法的資料格式，經 Douglas Crockford 推廣普及。雖然 JSON 是以 JavaScript 語法為基礎，但可獨立使用，且許多程式設計環境亦可讀取 (剖析) 並產生 JSON。

JSON 可能是物件或字串。當你想從 JSON 中讀取資料時，JSON 可作為物件；當要跨網路傳送 JSON 時，就會是字串。這不是什麼大問題 — JavaScript 提供全域 JSON 物件，其內的函式可進行切換。

JSON 物件可儲存於其自有的檔案中，基本上就是副檔名為 `.json` 的文字檔案，以及 `application/json` 的 MIME type。

### JSON 的架構

我們剛提到「JSON 物件基本上就是 JavaScript 物件」，而這敘述在大多數情況下都對。如同標準的 JavaScript 物件，你當然可在 JSON 之內加入相同的基本資料類型，如字串、數字、陣列、布林值，以及其他物件，接著同樣能再建構出資料繼承，如：

```json
{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}
```

舉例來說，如果將此物件載入至 JavaScript 程式並將之儲存為「`superHeroes`」變數，如同〈JavaScript 物件基本概念〉一文中提過的，接著能以相同的 存取其內部的資料，如下：

```js
superHeroes.hometown;
superHeroes["active"];
```

若要順著繼承往下存取資料，只要將必要的屬性名稱與陣列索引「鍊」在一起即可。舉例來說，如果要存取成員列表中的第二位英雄的第三項超能力，你必須：

```js
superHeroes["members"][1]["powers"][2];
```

1. 首先要有變數名稱 — `superHeroes`。
2. 要在變數中存取 `members` 屬性，所以用 `["members"]`。
3. `members` 包含由物件產生陣列。我們要存取陣列中的第二個物件，所以用 `[1]`。
4. 在此物件中，我們要存取 `powers` 屬性，所以用 `["powers"]`。
5. 在 `powers` 屬性中有 1 個陣列具備所選超級英雄的能力。我們要選第三種能力，所以用 `[2]`。

**備註：** 我們在 JSONText.html 範例 (參閱原始碼) 的變數中，示範上述可用的 JSON。你可在自己瀏覽器的 JavaScript 主控台載入此程式碼，並存取變數中的資料。

### 陣列作為 JSON

我們在上面提過「 JSON 物件基本上就是 JavaScript 物件，而這敘述在大多數情況下都對」。其中「在大多數情況下都對」的理由，就是因為陣列也可以是有效的 JSON 物件，例如：

```json
[
  {
    "name": "Molecule Man",
    "age": 29,
    "secretIdentity": "Dan Jukes",
    "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
  },
  {
    "name": "Madame Uppercut",
    "age": 39,
    "secretIdentity": "Jane Wilson",
    "powers": [
      "Million tonne punch",
      "Damage resistance",
      "Superhuman reflexes"
    ]
  }
]
```

上面程式碼絕對是有效的 JSON。你可用陣列指數為開頭來存取陣列項目，例如 `[0]["powers"][0]`。

### 其他附註

* JSON 是純粹的資料格式 — 僅具備屬性，而無函式。
* JSON 需要雙引號，才能使用＼有效。所以最安全的方法就是以雙引號撰寫之。
* 單一個逗號或冒號放錯位置，也會讓 JSON 檔案出錯而無法運作。你應仔細檢視所有要使用的資料 (只要產生器程式能正確運作，由電腦產生的 JSON 也就不容易出錯)。你可透過如 JSONLint 的應用程式檢驗 JSON。
* 不限於陣列或物件，只要是符合標準 JSON 物件形式的任何資料，都可以夾帶進 JSON 檔案中。因此，單一字串或數字也可能是有效的 JSON 物件，但不一定有用就是了...
主動學習：完成 JSON 範例
---------------

現在就試著在網站上，透過某些 JSON 資料完成範例吧。

### 入門

在開始之前，先複製我們的 heroes.html 與 style.css 到你的本端硬碟中。後者包含某些簡易的 CSS 可塑造網頁風格；前者則提供極簡單主體 HTML：

```html
<header></header>

<section></section>
```

加上 `<script>` 元素，才能納入稍後會在此習題中寫出來的 JavaScript 程式碼。目前只有 2 行程式碼，用以取得 `<header>` (en-US) 與 `<section>` (en-US) 元素的參考，並將之儲存於變數之中：

```js
var header = document.querySelector("header");
var section = document.querySelector("section");
```

你可到 GitHub 上找到此 JSON 資料：https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json。

接著載入到頁面之中，並使用某些有趣的 DOM 操控 (DOM manipulation) 來顯示，如下：

![](/zh-TW/docs/Learn/JavaScript/Objects/JSON/json-superheroes.png)

### 載入我們的 JSON

若要將 JSON 載入至頁面，就要透過 `XMLHttpRequest` API (通常稱為 **XHR**)。此是極好用的 JavaScript 物件，可讓網路請求透過 JavaScript (例如圖片、文字、JSON，甚至 HTML 片段) 來檢索伺幅器的資源，這也代表我們不需載入整個頁面，就能更新小部分的內容。如此可讓網頁反應速度更快；聽起來很棒吧？但可惜本文無法再深入講解更多細節。

1. 一開始，我們先針對要在變數中檢索的 JSON 檔案，將其網址儲存起來。把下列程式碼加到你 JavaScript 程式碼的最下方：

```js
var requestURL =
  "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
```

2. 為了建立請求，我們必須透過 `new` 關鍵字，先從 `XMLHttpRequest` 建構子建立新的請求物件實例。把下列加到最後一行：

```js
var request = new XMLHttpRequest();
```

3. 現在用 `open()` (en-US) 函式開啟新的請求。加入下列程式碼：

```js
request.open("GET", requestURL);
```

這樣就顧到至少 2 個參數。當然也有其他參數可選擇。但這個簡易範例只需要 2 個強制參數：
    * 在設立網路請求時，應使用 HTTP 函式。因為這裡只要檢索簡單的資料，所以用 `GET` 就可以。
    * URL 提供請求目的地 — 這也就是我們剛剛儲存的 JSON 檔案網址。
4. 接著加入下面 2 行程式碼。我們為 JSON 設定了 `responseType` (en-US)，告知伺服器應回傳 JSON 物件，再以 `send()` (en-US) 函式傳送請求：

```js
request.responseType = "json";
request.send();
```

5. 最後就是等待由伺服器所回傳的反應，再接著處理。把下列程式碼加入現有程式碼的最下方：

```js
request.onload = function () {
  var superHeroes = request.response;
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

在這裡，我們將所獲得的響應 (可到 `response` (en-US) 屬性中找到) 儲存到 `superHeroes` 變數之中。此變數現在會納入我們的 JSON。接著再把此 JSON 檔案送到 2 個函式呼叫。第一個函式呼叫會將正確資料填入 <`header>`；第二個函式呼叫則會為團隊中的各個英文建立資訊卡，再插入至 `<section>` 內。

當於請求物件上觸發載入事件時，會執行一個事件處理器。我們就將程式碼包裹至此處理器之中 (參閱 `onload` (en-US)) — 只要成功回傳響應，就會觸發載入事件。之所以這樣做，是為了確保當我們要以 `request.response` 進行某件事時，此 `request.response` 絕對可用。

### 產生標頭

現在檢索過了 JSON 資料，接著就寫出上面參照過的 2 個函式來利用 JSON 資料吧。首先將下列函式定義加到先前的程式碼中：

```js
function populateHeader(jsonObj) {
  var myH1 = document.createElement("h1");
  myH1.textContent = jsonObj["squadName"];
  header.appendChild(myH1);

  var myPara = document.createElement("p");
  myPara.textContent =
    "Hometown: " + jsonObj["homeTown"] + " // Formed: " + jsonObj["formed"];
  header.appendChild(myPara);
}
```

我們已經將參數命名為 `jsonObj`，所以在這個函式之內就要用 jsonObj 呼叫此參數。這裡先以 `createElement()` 建立 1 組 `<h1>` (en-US) 元素、將其 `textContent` (en-US) 指定為 JSON 的 `squadName` 屬性、透過 `appendChild()` (en-US) 將之附加到標頭。接著 `<p>` (en-US) 元素依樣畫葫蘆一遍：建立、設定其文字內容、附加到標頭。唯一不同之處，就是將該文字設定為 1 組串接字串 (Concatenated string)，其內包含 JSON 的 `homeTown` 與 `formed` 屬性。

### 建立超級英雄的資訊卡片

現在將下列函式加到程式碼底端，用以建立並顯示超級英雄的卡片：

```js
function showHeroes(jsonObj) {
  var heroes = jsonObj["members"];

  for (i = 0; i < heroes.length; i++) {
    var myArticle = document.createElement("article");
    var myH2 = document.createElement("h2");
    var myPara1 = document.createElement("p");
    var myPara2 = document.createElement("p");
    var myPara3 = document.createElement("p");
    var myList = document.createElement("ul");

    myH2.textContent = heroes[i].name;
    myPara1.textContent = "Secret identity: " + heroes[i].secretIdentity;
    myPara2.textContent = "Age: " + heroes[i].age;
    myPara3.textContent = "Superpowers:";

    var superPowers = heroes[i].powers;
    for (j = 0; j < superPowers.length; j++) {
      var listItem = document.createElement("li");
      listItem.textContent = superPowers[j];
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}
```

我們先把 JSON 的 `members` 屬性儲存到新的變數中。此陣列所具備的多個物件，均包含了各個超級英雄的資訊。

接著我們以 for 迴圈循環陣列中的各個物件。針對每個物件都會：

1. 建立數個新的元素：1 組 `<article>`、1 組 `<h2>`、3 組 `<p>`、1 組 `<ul>`。
2. 讓 `<h2>` 納入目前超級英雄的 `name`。
3. 接著 3 個段落分別是英雄的 `secretIdentity`、`age`、`Superpowers`，在列表中帶出相關資訊。
4. 另以新變數 `superPowers` 儲存 `powers` 屬性 — 其中包含 1 組陣列以列出目前英雄的超能力。
5. 再用另一個 `for` 迴圈逐一巡過目前英雄的超能力。針對每一項超能力，我們再建立 1 組 `<li>` 元素，把超能力放進該元素之中，再透過 `appendChild()` 把 `listItem` 放入 `<ul>` 元素之內 (`myList`)。
6. 最後就是在 `<article>` (`myArticle`) 之內附加 `<h2>`、`<p>`、`<ul>`；再把 `<article>` 附加於 `<section>` 之內。這附加的順序極為重要，因為這也會是 HTML 中的顯示順序。

**備註：** 如果你無法讓此範例運作，可參閱我們的 heroes-finished.html 原始碼 (亦可看到實際執行情況。)

**備註：** 如果你無法用我們說過的點記法 (dot-)＼括弧記法 (bracket notation) 來存取 JSON，則可用新分頁或自己的文字編輯器開啟 superheroes.json 檔案並參考之。你也可再回去看看 JavaScript 物件基礎概念 ，再次了解點＼括弧記法。

物件與文字交互轉換
---------

上述是存取 JSON 的簡易範例，因為我們設定要回傳響應的 XHR 已經是 JSON 格式。透過：

```js
request.responseType = "json";
```

但有時候沒這麼好運。我們有時會接收到文字字串格式的 JSON 資料，且必須將之轉換為物件。且當我們要以某種訊息傳送 JSON 資料時，也必須將之轉換為字串才能正確運作。還好，這 2 種問題在 Web 開發過程中甚為常見。內建的 JSON 物件很早就新增到瀏覽器之中，且包含下列 2 種函式：

* `parse()`：接收文字字串形式的 JSON 物件作為參數，並回傳對應的物件。
* `stringify()` (en-US)：接收 JSON 物件作為參數，並回傳對等的文字字串形式。

你可到 heroes-finished-json-parse.html 範例 (參閱原始碼) 中看到第一個函式的運作情形。這其實跟我們先前範例所進行的事情一模一樣，不同之處在於我們設定 XHR 要回傳 JSON 為文字，接著再使用 `parse()` 轉換為實際的 JSON 物件。關鍵程式碼片段如下：

```js
request.open("GET", requestURL);
request.responseType = "text"; // now we're getting a string!
request.send();

request.onload = function () {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

你可能會猜 `stringify()` 就是反過來運作了吧？可在瀏覽器的 JavaScript 主控台上輸入下列程式碼，看看其運作方式：

```js
var myJSON = { name: "Chris", age: "38" };
myJSON;
var myString = JSON.stringify(myJSON);
myString;
```

這樣就建立了 JSON 物件了。接著檢查內容物之後，就可透過 `stringify()` 將之轉換為字串。將回傳值儲存到新變數之中，再檢查一次即可。

摘要
--

我們透過本文簡單介紹了該如何在程式中使用 JSON、該如何建立＼剖析 JSON、該如何存取其內的資料。接著就要說明物件導向 JavaScript（OOJS）。

參見
--

* JSON 物件參考頁面
* XMLHttpRequest 物件參考頁面
* 使用 XMLHttpRequest
* HTTP 請求函式
* 前頁
* Overview: Objects
* 次頁


物件建構實作 - 學習該如何開發 Web

物件建構實作
======

* 前頁
* Overview: Objects
* 次頁

我們解說完必要的 JavaScript 物件理論以及語法細節，想先幫你把根紮好。接著就透過實作範例，讓你實際建立自己有趣又多彩的 JavaScript 物件。

|  |  |
| --- | --- |
| 必備條件： | 基礎的計算機素養、了解 HTML 與 CSS 的基本概念、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉) 與 OOJS 基本概念 (參閱〈Introduction to objects (en-US)〉)。 |
| 要點： | 親手實作物件與物件導向 (OO) 技術。 |

弄一些彈跳彩球
-------

本文將帶領你實作經典的「彈跳球」展示網頁，讓你了解物件在 JavaScript 中的用處。這些小球會在畫面上四處彈跳，而且互相碰撞時變換顏色。範例成品如下：

![](/zh-TW/docs/Learn/JavaScript/Objects/Object_building_practice/bouncing-balls.png)

此範例將透過 Canvas API 在畫面上繪製球體，requestAnimationFrame API 則是繪製整個動畫；而且你不需先了解此兩個 API。但我們希望在看完本文之後，能引起大家深入探究此兩個 API 的興趣。整個過程會利用某些花俏的物件，並讓你看到幾項有趣技術，像是球體從牆上回彈，並檢查球體是否互相碰撞 (也就是碰撞偵測)。

著手開始
----

先複製 `index.html``、style.css`、`main.js` 檔案到你的本機磁碟中。這些檔案分別具備下列：

1. 極簡的 HTML 文件，具備 1 個 `<h1>` (en-US) 元素、1 個 `<canvas>` 元素可繪製彩球，以及其他元素可將 CSS 與 JavaScript 套用到 HTML 之上。
2. 一些極簡單的樣式，主要可作為 `<h1>` 的樣式風格與定位之用，並省去網頁邊緣的捲動棒或空白 (看起來更簡約)。
3. 某些 JavaScript 可用以設定 `<canvas>` 元素，另有通用函式可供我們往後使用。

指令碼第一部分就像：

```js
var canvas = document.querySelector("canvas");

var ctx = canvas.getContext("2d");

var width = (canvas.width = window.innerWidth);
var height = (canvas.height = window.innerHeight);
```

此指令碼將為 `<canvas>` 元素提供參照，接著於其上呼叫 `getContext()` (en-US) 函式，藉以提供能開始繪圖的內文 (Context)。所產生的變數 (`ctx`) 也就是物件，將直接呈現 canvas 的繪圖區域，讓我們繪製 2D 圖像。

接著設定 `width` 與 `height` 共 2 個變數，也就是 canvas 元素的寬度與高度 (透過 `canvas.width` 與 `canvas.height` 屬性呈現) 即等於瀏覽器可視區的寬度與高度 (也就是網頁顯示的區域 — 可經由 `Window.innerWidth` (en-US) 與 `Window.innerHeight` (en-US) 屬性得知)。

你會看到我們在這裡串連了多個指定式，以快速設定所有變數，而且運作無虞。

剛開始的指令碼後半部如下：

```js
function random(min, max) {
  var num = Math.floor(Math.random() \* (max - min)) + min;
  return num;
}
```

此函式共有 2 組參數 (argument)，並會回傳此範圍之內的任意值。

在程式中設定球體的模型
-----------

我們的程式會讓一堆彩球在畫面中彈來彈去。因為這些球體的行動方式均相同，所以透過物件呈現這些彩球也合情合理。先在程式碼底部加入下列建構子：

```js
function Ball() {
  this.x = random(0, width);
  this.y = random(0, height);
  this.velX = random(-7, 7);
  this.velY = random(-7, 7);
  this.color =
    "rgb(" + random(0, 255) + "," + random(0, 255) + "," + random(0, 255) + ")";
  this.size = random(10, 20);
}
```

這裡我們要定義某些屬性，以利彩球能在程式中動作：

* `x` 與 `y` 座標 — 每顆彩球都會先有位於畫面中的隨機水平＼垂直座標。範圍從 0 (最左上角) 到瀏覽器可視區的寬＼高度 (最右下角) 為止。
* 水平與垂直速度 (`velX` 與 `velY`) — 每顆彩球均具備隨機的速度值；在我們開始彩球動畫時，這些數值就會加上 `x`/`y` 座標值，以利在各畫格(frame)中移動彩球。
* `color` — 彩球的顏色是隨機產生。
* `size` — 各彩球大小亦為隨機，從 10 到 20 像素不等。

屬性講完了，那函式呢？程式中的彩球要實際運作才行。

### 繪製球體

先將下列 `draw()` 函式加到 `Ball()` 的 `prototype` 之中：

```js
Ball.prototype.draw = function () {
  ctx.beginPath();
  ctx.fillStyle = this.color;
  ctx.arc(this.x, this.y, this.size, 0, 2 \* Math.PI);
  ctx.fill();
};
```

透過此函式，再呼叫我們之前定義在 2D canvas 內文(ctx)中的物件成員，就能讓球體自己在螢幕上畫出自己。此內文就像是白紙一樣，接著就用筆在紙上畫出點東西：

* 首先以 `beginPath()` (en-US) 聲明我們要在紙上畫出來的形狀。
* 接著用 `fillStyle` (en-US) 定義該形狀所要呈現的顏色 — 設定為球體的 `color` 屬性。
* 再用 `arc()` (en-US) 函式在紙上勾勒出弧形。相關參數為：
    + 弧形中心的 `x` 與 `y` 位置 — 我們指定球體的 `x` 與 `y` 屬性。
    + 弧形半徑 — 指定球體的 `size` 屬性。
    + 最後 2 項參數則指定弧形繪製時的圓圈起、終點角度。我們這裡指定 0 度與 `2 * PI` 度，這也等於半徑繞了 360 度 (你必須在半徑中指定，有點煩)。如此構成完整的圓。如果你只設定了 `1 * PI，就會只有半球體` (即 180 度)。
* 最後使用 `fill()` (en-US) 函式，基本上是用來聲稱「完成我們以 `beginPath()` 開始的繪圖路徑，再用 `fillStyle` 中指定的色彩將之填滿」。

你已經可以開始測試自己的物件了。

1. 儲存目前的程式碼，在瀏覽器中載入此 HTML 檔案。
2. 開啟瀏覽器的 JavaScript 主控台，並在主控台開啟時重新整理網頁，讓 canvas 尺寸變更為左側較小型的可視區域。
3. 鍵入下列程式碼以建立新的球體實例：

```js
var testBall = new Ball();
```

4. 再呼叫其成員：

```js
testBall.x;
testBall.size;
testBall.color;
testBall.draw();
```

5. 輸入最後一行之後，應該就能看到 canvas 上出現自己產生的球體。
### 更新球體的資料

現在可以繪製彩球了。但在讓球彈跳之前，我們必須先更新幾個函式。將下列程式碼加到 JavaScript 檔案底端，把 `update()` 函式加到 `Ball()` 的 `prototype` 之中：

```js
Ball.prototype.update = function () {
  if (this.x + this.size >= width) {
    this.velX = -this.velX;
  }

  if (this.x - this.size <= 0) {
    this.velX = -this.velX;
  }

  if (this.y + this.size >= height) {
    this.velY = -this.velY;
  }

  if (this.y - this.size <= 0) {
    this.velY = -this.velY;
  }

  this.x += this.velX;
  this.y += this.velY;
};
```

函式的前 4 個部分負責檢查球體是否碰到 canvas 邊緣。如果球體抵達邊緣，我們就反轉相對加速度的方向，讓球反方向行進。以球體向上 (正向 `velX`) 時為例，接著就會改變水平速度，球體也就反向運動。

在這 4 個情境中，我們：

* 檢查 `x` 座標是否大於 canvas 的寬度 (球體抵達右側邊緣)。
* 檢查 `x` 座標是否小於 0 (球體抵達左側邊緣)。
* 檢查 `y` 座標是否大於 canvas 的高度 (球體抵達底部邊緣)。
* 檢查 `y` 座標是否小於 0 (球體抵達頂部邊緣)。

在各情境中，因為 `x`/`y` 座標為球體的中心，所以我們把球體的 `size` 納入計算，但我們不要球體在回彈之前在半路上就跳出畫面之外。

最後 2 行則是將 `velX` 與 `velY` 值分別加入 `x＼y` 座標之中；每次只要呼叫此函式，球體就會依照應有的效果移動。

到這裡沒有問題的話，就開始弄動畫吧！

球體動起來
-----

接著來玩玩吧。我們要加更多球到 canvas 中並開始動畫效果。

1. 首先要弄個地方儲存所有的彩球。將下方陣列加到現有程式碼底部即可：

```js
var balls = [];
```

所有可提供動畫效果的程式，一般都會採用動畫迴圈，可用以更新程式中的資訊，並接著在動畫的各個畫格上繪製產生的結果。這也是大部分遊戲或類似程式的基礎。
2. 再將下列程式碼加到現有程式碼底部：

```js
function loop() {
  ctx.fillStyle = "rgba(0,0,0,0.25)";
  ctx.fillRect(0, 0, width, height);

  while (balls.length < 25) {
    var ball = new Ball();
    balls.push(ball);
  }

  for (i = 0; i < balls.length; i++) {
    balls[i].draw();
    balls[i].update();
  }

  requestAnimationFrame(loop);
}
```

我們的 `loop()` 函式可進行：
    * 設定 canvas 填滿色彩或是半透明的黑色。接著透過 `fillRect()` (共 4 個參數提供起始座標，以及繪製矩形的高度與寬度)，跨 canvas 的寬度與高度繪製整個矩型的色彩。如此可在繪製下一個畫格之前，先覆蓋前一個已存在的畫格；否則會看到許多隻長長的蛇爬來爬去。填充顏色已設定為半透明狀態：`rgba(0,0,0,0.25)` 可讓先前的畫格微微發亮，製造出球體移動時的小尾巴效果。如果將 0.25 更改為 1，就會完全消除尾巴。你可自己測試不同的數值，找出自己喜歡的效果。
    * 可對 `Ball()` 建立新的實作，接著將之 `push()` 到球體陣列的最後，且彩球數量必須少於 25 個。所以整個畫面最多顯示 25 個球。你可嘗試變更 `balls.length < 25` 中的數值，畫面中的彩球數量也會隨著變化。依你所用電腦＼瀏覽器處理效能的不同，若繪製上千個彩球就會拖慢整個動畫的速度。
    * 迴圈將巡過 `balls` 陣列中的所有彩球，並執行各個彩球的 `draw()` 與 `update()` 函式，以於畫面中逐一繪製，接著對下個畫格的位置與速度執行必要更新。
    * 再以 `requestAnimationFrame()` 函式執行過此函式 — 當此函式持續執行並傳送相同的函式名稱時，就會每秒執行此函式達特定次數，以產生流暢的動畫。接著重複執行此作業，也就是函式每次執行時均會呼叫自身 1 次，進而循環執行。
3. 最後將下列程式碼加入最底端，呼叫函式 1 次讓動畫開始運作。

```js
loop();
```

基本就是這樣了。試著儲存並重新整理檔案，讓你的彩球開始跳動吧！

另增碰撞偵測
------

現在弄點有趣的東西，就把碰撞偵測 (Collision detection) 加進程式裡，讓彩球知道自己碰到其他球了。

1. 首先將下列函式定義加進你自己定義 `update()` 函式中 (例如 `Ball.prototype.update` 區塊)：

```js
Ball.prototype.collisionDetect = function () {
  for (j = 0; j < balls.length; j++) {
    if (
      !(
        this.x === balls[j].x &&
        this.y === balls[j].y &&
        this.velX === balls[j].velX &&
        this.velY === balls[j].velY
      )
    ) {
      var dx = this.x - balls[j].x;
      var dy = this.y - balls[j].y;
      var distance = Math.sqrt(dx \* dx + dy \* dy);

      if (distance < this.size + balls[j].size) {
        balls[j].color = this.color =
          "rgb(" +
          random(0, 255) +
          "," +
          random(0, 255) +
          "," +
          random(0, 255) +
          ")";
      }
    }
  }
};
```

這函式有點複雜，所以現在不瞭解如何運作的也別擔心。解釋如下：
    * 對每個彩球來說，我們必須檢查是否碰撞到其他球。所以要設定另一個 `for` 迴圈以循環檢視 `balls[]` 陣列中的所有彩球。
    * 在我們的 for 迴圈中，我們立刻使用 `if` 陳述式檢查「現正透過迴圈循環檢查中」的彩球，是否即為我們目前檢查中的同一彩球。我們不需要檢查彩球是否碰撞到自己！為了達到此效果，我們檢查彩球目前的 `x`/`y` 座標與速度，是否等同於迴圈檢查的彩球。接著透過「`!`」否定檢查，所以在 if 陳述式中的程式碼，只有在彩球相異時才會執行。
    * 接著使用一般演算法檢查 2 個球體之間的碰撞。我們基本上會檢查任 2 個球體的範圍是否重疊。另將透過〈2D 碰撞偵測 (en-US)〉一文進一步解釋。
    * 如果偵測到碰撞，則隨即執行內部 `if` 陳述式的程式碼。在本範例中，我們剛設定了 2 個球體的 `color` 屬性為新的隨機色彩。但當然可以更複雜點，像是讓彩球更逼真的互相反彈，但這實作起來就更複雜了。對這類的物理模擬，開發者就必須使用如 PhysicsJS、matter.js、Phaser 等的遊戲＼物理函式庫。
2. 你也可以在動畫的每一畫格中呼叫此一函式。在 `balls[i].update();` 這一行下方新增下列程式碼即可：

```js
balls[i].collisionDetect();
```

3. 儲存並重新整理之後，就能看到球體在碰撞時變更其色彩了！

**備註：** 如果你無法讓此範例順利運作，可比較我們的最後版本 (另可參閱實際執行情形)。

摘要
--

希望你喜歡撰寫出隨機彩球碰撞範例，其內並包含我們前面說過的多樣物件與 OO 技術！本文應該已提供你有用的物件實作與絕佳的實際文本。

物件實體就到這裡。接著就是你要磨練自己的物件技術了！

另可參閱
----

* Canvas 線上教學 — 2D canvas 初學者指南
* requestAnimationFrame()
* 2D 碰撞偵測 (en-US)
* 3D 碰撞偵測 (en-US)
* 只使用 JavaScript 的 2D 打磚塊遊戲 — 2D 遊戲開發初學者的絕佳線上教學
* 剖析器 (Phaser) 的 2D 打磚塊遊戲 (en-US) — 以 JavaScript 遊戲函式庫建構 2D 遊戲的基本概念
* 前頁
* Overview: Objects
* 次頁


JavaScript 物件基礎概念 - 學習該如何開發 Web

JavaScript 物件基礎概念
=================

* Overview: Objects
* 次頁

第一篇談到 JavaScript 物件的文章中，我們了解到基本的 JavaScript 物件語法，複習了某些先前提過的 JavaScript 功能，也再次強調你現正使用中的許多功能其實就是物件。

|  |  |
| --- | --- |
| 必要條件： |
 基本的電腦素養、對 HTML 與 CSS 已有初步認識、熟悉 JavaScript 基本概念
 (參閱〈First steps〉與〈Building blocks〉)。
  |
| 主旨： |
 了解「物件導向 (OO)」程式設計背後的基礎理論、其與 JavaScript
 (多屬於物件) 之間的關係、該如何使用 JavaScript 物件進行開發。
  |

物件基礎概念
------

物件是一批相關的數據以及/或者功能（通常包含了幾個變數及函式 — 當它們包含在物件中時被稱做「屬性」（properties）或「函式」（methods）），讓我們用一個範例來看看物件的長相。

在開始之前，請先複製一份 oojs.html 檔案到你自己的本端硬碟中。此檔案內容物不多，就 1 組 `<script>` 元素可寫入我們的原始碼；在繪製頁面時，1 組元素可輸入簡易指令；幾個變數定義；1 組函式可針對輸入至 input 的程式碼，將之輸出到 `<p>` (en-US) 元素。我們將透過此檔案說明基礎的物件語法。

JavaScript 內的大多數東西，均是透過定義並初始設定變數來建立物件。

現在, 請在自己的 oojs.html 檔案中、JavaScript 程式碼中加入下列程式碼，接著儲存並重新整理：

```js
var person = {};
```

然後在瀏覽器中開啟 oojs.html, 再打開瀏覽器的開發者工具, 在 JavaScript 的控制台下, 輸入`person`, 並按下 Enter 鈕，就會得到下列結果：

```js
[object Object]
```

恭喜, 你已經建立了自己的第一個物件。但這仍是空的物件，所以能做的事不多。接下來, 再如下所示, 幫 person 物件更新內容：

```js
var person = {
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  bio: function () {
    alert(
      this.name[0] +
        " " +
        this.name[1] +
        " is " +
        this.age +
        " years old. He likes " +
        this.interests[0] +
        " and " +
        this.interests[1] +
        ".",
    );
  },
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};
```

改完後同樣儲存 oojs.html、重新整理瀏覽器之後，再到控制台輸入 person, 將會看到新的結果：

```js
person.name[0];
person.age;
person.interests[1];
person.bio();
person.greeting();
```

現在你的物件裡面已經有了某些資料與功能，而且能透過某些簡易語法存取之。

**備註：** 如果你無法完成上述步驟，可先和我們的版本比較一下。參閱 oojs-finished.html (或觀看 實際執行)。你最容易犯下的錯誤是在物件中的最後一個成員 (member)末端加上逗號，如此就會造成錯誤。

目前為止發生了什麼事呢？現在這個物件是由多個成員所構成，各個成員均有 1 個名稱 (如上述的 `name` 與 `age`) 以及 1 組數值 (如 `['Bob', 'Smith']` 與 `32`)。由名稱與數值構成的組合均以逗號區隔，而名稱與數值之間則以冒號隔開。語法應如下所示：

```js
var objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};
```

物件成員的數值可能是任何東西，像上述的範例物件就有 1 組字串、1 組數字、2 個陣列、2 組函式。前 4 組項目均為資料項目，可說是該物件的**屬性**。最後 2 組項目的功能則是用以指定物件對該筆資料所應進行的作業，可說是物件的**函式 (Method)**。

類似這種物件即稱為「**實字物件 (Object literal)**」，按照字面上的意思寫出物件內容；與其相對的就是根據「類別」做出的物件實體。我們稍後會再說明。

在傳送一系列結構化的相關資料項目時 (例如傳送請求至伺服器並置入資料庫中)，就常常會透過實字物件的方式建立物件。另與「分別傳送多個項目」相較，送出單一物件當然效率更高，且當你想根據名稱找出各個項目時，更易於搭配陣列。

點記法 (Dot notation)
------------------

你可透過點記法 (Dot notation) 存取物件的屬性與函式。物件名稱 (這裡是 person) 作為**命名空間 (Namespace)** —為了能存取物件所**封裝**的所有東西，這也是必須首先輸入的項目。接著你寫一個「點」以及你所想存取的項目，可能是簡單屬性的名稱、陣列屬性的項目，又或是針對物件函式之一的呼叫。舉例來說：

```js
person.age;
person.interests[1];
person.bio();
```

### 子命名空間

甚至可以將物件成員的數值轉為另一個物件。舉例來說，你可將名稱成員從

```js
name : ['Bob', 'Smith'],
```

改變為

```js
name : {
  first : 'Bob',
  last : 'Smith'
},
```

我們這裡以極高效率建立了子命名空間。看起來複雜但其實不然。若要存取這些項目，你只要透過另一個點，將 onto the end 的額外步驟串連起來即可。如下所示：

```js
person.name.first;
person.name.last;
```

\*\*重要：\*\*現在你必須看過自己的函式碼，將實例

```js
name[0];
name[1];
```

改變為

```js
name.first;
name.last;
```

否則你的函式就不能運作了。

括弧記法 (Bracket notation)
-----------------------

括弧記法 (Bracket notation) 是另個存取物件屬性的方法。之前的：

```js
person.age;
person.name.first;
```

可寫成

```js
person["age"];
person["name"]["first"];
```

這很像在陣列中存取項目的方法。其實基本上是一樣的東西 ─ 但前者是透過指數 (index number) 選擇項目；括弧記法則是透過各成員數值相關的名稱來選擇項目。因此物件有時亦稱作\*\*「相聯陣列 (Associative array)」\*\*；也就是說，其「將字串對應到數值」的方式，與陣列「將數字對應到數值」的方式相同。

設定物件成員
------

到目前為止，我們只說明了檢索 (或**取得**) 物件成員。你也可以簡單宣告你所要設定的成員 (用點或括弧記法均可)，設定 (**更新**) 物件成員的數值，如下：

```js
person.age = 45;
person["name"]["last"] = "Cratchit";
```

試著輸入下列程式碼，再次取得成員之後看看變更的結果：

```js
person.age;
person["name"]["last"];
```

設定成員不只是更新現有屬性與函式的數值，也可以建立全新的成員，如下：

```js
person["eyes"] = "hazel";
person.farewell = function () {
  alert("Bye everybody!");
};
```

現在可以測試自己的新成員了：

```js
person["eyes"];
person.farewell();
```

此外，括弧記法不僅可動態設定成員數值，亦可設定成員名稱。假設使用者可在自己的人事資料中儲存自訂的數值類型，例如鍵入成員名稱與數值為 2 組文字輸入項，就會類似：

```js
var myDataName = nameInput.value;
var myDataValue = nameValue.value;
```

接著可將此新的成員名稱與數值加進 `person` 這個物件：

```js
person[myDataName] = myDataValue;
```

若要測試，可將下列程式碼加進自己的程式碼，加在宣告完 `person` 物件的大括號後：

```js
var myDataName = "height";
var myDataValue = "1.75m";
person[myDataName] = myDataValue;
```

現在儲存並重新整理，將下列輸入你的文字輸入項中：

```js
person.height;
```

因為點記法只接受字母表示的成員名稱，不能是指向名稱的變數值，所以並無法使用。

這個「this」是什麼？
------------

你可能注意到我們函式有怪怪的地方。看看以下範例：

```js
greeting: function() {
  alert('Hi! I\'m ' + this.name.first + '.');
}
```

你可能會想這個「this」是幹嘛用的。「this」是指目前寫入程式碼的物件；所以此範例的 `this` 就等於 `person`。那又為何不寫 `person` 就好呢？如同你在〈初學者的物件導向 JavaScript〉一文中所看過的，當我們開始設定建構子等東西時，有用的「`this`」就可在成員內文改變時 (例如 2 個不同 `person` 物件實例可能具備不同的名稱，但打招呼時仍要使用自己的名稱)，確保仍使用了正確的值。

先用簡化的一對 person 物件說明：

```js
var person1 = {
  name: "Chris",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};

var person2 = {
  name: "Brian",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};
```

此範例中的函式碼雖然完全一樣，但 `person1.greeting()` 將輸出「Hi! I'm Chris.」；`person2.greeting()` 則會呈現「Hi! I'm Brian.」。如我們剛剛說過的，「`this」等於「已於內部放置程式碼」的物件`。如果你是依字面意義寫出物件，那可能沒什麼感覺，但如果你是用動態方式產生物件 (例如使用建構子) 的話，就能明顯感覺到方便之處了。再看下去你更清楚原因。

其實你一直在使用物件
----------

隨著你看完這些範例，你應該會覺得跟自己使用的點記法很類似。這是因為你整個課程都在使用點記法。每次我們透過內建的瀏覽器 API 或 JavaScript 物件寫出範例時，我們就是在用物件；因為這些功能也就是以本文提及完全相同的物件結構所寫成。即便是更複雜的範例也是一樣。

所以當你使用字串函式如下：

```js
myString.split(",");
```

你就是在使用 `String` 類別實例可用的方法。每次只要你在程式碼中建立字串，該字串就會自動建立成為 `String` 的實例，並具備有多個常見的方法與屬性。

若你透過下列程式碼存取文件物件模型 (DOM)：

```js
var myDiv = document.createElement("div");
var myVideo = document.querySelector("video");
```

你也就在使用 `Document` (en-US) 類別實例上的函式。當載入網頁時，就會建立 `Document` 的實例，亦所謂的 `document`，將呈現整個網頁的架構、內容，以及其他功能 (如網址)。同樣的，這代表其上已有多個常見的函式＼屬性。

同理可證，目前你在使用的許多物件＼API (如 `Array``、Math` 等) 也都是類似情形。

另該注意的是，內建的物件＼API 不見得會自動建立物件實例。像以 Notifications API (它可以幫助你使用現代瀏覽器向使用者發送通知 ) 為例，就需要你針對想要觸發的通知，使用建構子逐一建立新的物件實例。試著將下列程式碼丟進你的 JavaScript 主控台：

```js
var myNotification = new Notification("Hello!");
```

我們會在後續文章中說明建構子 (Constructor)。

**備註：** 可思考一下物件「訊息傳遞」的溝通方式。當某個物件需要其他物件執行其他作業時，往往會透過其函式之一傳送訊息給其他物件並等待回應。這也是我們所謂的回傳值。

摘要
--

恭喜你已經快讀完我們第一篇 JS 物件的文章了。你應該已經知道該如何使用 JavaScript 中的物件，並建立自己的簡單物件了。你也應該了解物件在儲存相關資料的好用之處。如果你將 `person` 物件中的所有屬性與函式，當做個別的變數與函式並試著追蹤，肯定吃力不討好；且其他具備相同名稱的變數與函式也可能發生問題。「物件」讓我們能在其封包中安全的與資訊相互區隔。

下一篇文章將說明「物件導向程式設計 (OOP)」理論，並了解相關技術是如何用於 JavaScript 之中。

* Overview: Objects
* 次頁


JavaScript 中的「繼承」 - 學習該如何開發 Web

JavaScript 中的「繼承」
=================

* 前頁
* Overview: Objects
* 次頁

在解釋過大部分的 OOJS 細節之後，本文將說明該如何建立「子」物件類別 (建構子)，並從其「母」類別繼承功能。此外，也將建議開發者應於何時、於何處使用 OOJS。

|  |  |
| --- | --- |
| 必備條件： |
 基本的電腦素養、已了解 HTML 與 CSS 基本概念、熟悉 JavaScript 基礎
 (可參閱〈First steps〉與〈Building blocks〉) 與 OOJS 的基礎 (可參閱〈Introduction to objects (en-US)〉)。
  |
| 主旨： | 了解應如何建構 JavaScript 中的繼承。 |

原型繼承
----

目前為止，我們看過幾個繼承的實作範例：原型鍊的運作方式，以及繼承的成員如何形成原型鍊。但這些大部分都與內建的瀏覽器函式有關。那我們又該如何在 JavaScript 建立物件且由其他物件繼承而來呢？

如稍早的系列文章中所述，某些人認為 JavaScript 並非真正的物件導向 (OO) 語言。在「典型 OO」中，你必須定義特定的類別物件，才能定義哪些類別所要繼承的類別 (可參閱〈C++ inheritance〉了解簡易範例)。JavaScript 則使用不同的系統 —「繼承」的物件並不會一併複製功能過來，而是透過原型鍊連接其所繼承的功能，亦即所謂的原型繼承 (**Prototypal inheritance**)。

就透過範例了解此一概念吧。

入門
--

首先將 oojs-class-inheritance-start.html 檔案 (亦可參考實際執行) 複製到本端磁碟。可在裡面找到本課程一直沿用的 `Person()` 建構子範例，但這裡有些許不同，也就是該建構子只定義了屬性：

```js
function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
}
```

這些函式均已定義於建構子的原型之上，例如：

```js
Person.prototype.greeting = function () {
  alert("Hi! I'm " + this.name.first + ".");
};
```

假設要建立一個像前面 OO 定義中說過的 `Teacher` 類別，且除了繼承 `Person` 的所有成員，還要包含：

1. 新的 `subject` 屬性，可包含老師所傳授的科目。
2. 更新過的 `greeting()` 函式，要比標準的 `greeting()` 函式更正式一點，更適合老師在校對學生使用。
定義 Teacher() 建構子函式
------------------

首先必須建立 `Teacher()` 建構子，將下列程式碼加到現有程式碼之下：

```js
function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}
```

這看起來和 Person 建構子有許多地方類似，但比較奇怪的就是之前沒看過的 `call()` 函式。此函式基本上可讓你呼叫在其他地方定義的函數，而非目前內文 (context) 中定義的函式。當執行函式時，第一個參數用來指定你要使用 `this` 值，其他參數則指定應該傳送到該函式的參數。

**備註：** 我們此範例中建立新的物件實例時，會指定所要繼承的屬性。但必須注意的是，即使實例不需將屬性指定為參數，你還是必須在建構子中將屬性指定為參數 (在建立物件時，你可能獲得設定為隨意值的屬性)。

所以這裡可在 `Teacher()` 建構子函式之內有效執行 `Person()` 建構子函式 (如上述)，藉以在 `Teacher()` 之內定義相同的屬性，但使用的是傳送到 `Teacher()` 的屬性值，而非 `Person()` 的屬性值 (我們將 `this` 值設為簡單的「`this」並`傳送到 `call()`，代表這個 `this` 是 `Teacher()` 的函式)。

建構子的最後一行則定義了新的 `subject` 屬性，代表只有老師具備，一般人不會有。

我們也可以單純這樣做：

```js
function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}
```

但這樣只是重新定義了新的屬性，而不是繼承自 `Person()` 而來，所以無法達到我們預設的目標，也需要更多程式碼才能達成。

設定 Teacher() 的原型與建構子參考
----------------------

到目前為止發現一個問題：我們定義了新的建構子，但預設只有 1 個空白的 `prototype` 屬性。接著要讓 `Teacher()` 繼承 `Person()` 原型中所定義的函式，該怎麼做呢？

1. 繼續在現有程式碼下方加入：

```js
Teacher.prototype = Object.create(Person.prototype);
```

這裡再次用好朋友 `create()` 解救。我們透過 `create()` 並搭配等同於 `Person.prototype` 的原型，建立新的 `prototype` 屬性值 (它本身就是物件，包含屬性與函式) ，並將之設定為 `Teacher.prototype` `的值。也就是說` `Teacher.prototype` 現在會繼承 `Person.prototype` 上的所有可用函式。
2. 此外，基於我們的繼承方式，`Teacher()` `prototype` 的建構子屬性目前設定為 `Person()。可參閱` Stack Overflow post 進一步了解原因。可先儲存自己的程式碼、在瀏覽器中載入頁面，再將下列程式碼輸入至 JavaScript 主控台以驗證：

```js
Teacher.prototype.constructor;
```

3. 這樣可能會產生問題，所以要設定正確。你可回到自己的原始碼並在最下方加入下列程式碼：

```js
Teacher.prototype.constructor = Teacher;
```

4. 如果儲存並重新整理之後，只要輸入 `Teacher.prototype.constructor` 應該就會回傳 `Teacher()`。
給 Teacher() 新的 greeting() 函式
----------------------------

接著必須在 `Teacher()` 建構子上定義新的 `greeting()` 函式。

最簡單的方法就是在 `Teacher()` 的原型上定義此函式。將下列加入程式碼最底部：

```js
Teacher.prototype.greeting = function () {
  var prefix;

  if (
    this.gender === "male" ||
    this.gender === "Male" ||
    this.gender === "m" ||
    this.gender === "M"
  ) {
    prefix = "Mr.";
  } else if (
    this.gender === "female" ||
    this.gender === "Female" ||
    this.gender === "f" ||
    this.gender === "F"
  ) {
    prefix = "Mrs.";
  } else {
    prefix = "Mx.";
  }

  alert(
    "Hello. My name is " +
      prefix +
      " " +
      this.name.last +
      ", and I teach " +
      this.subject +
      ".",
  );
};
```

這樣就會顯示老師的問候語，也會針對老師的性別使用合適的稱呼，可用於條件陳述式。

簡易範例
----

現在你已經輸入所有程式碼了，可以試著用`Teacher()` 建立物件實例。將下列 (或是你想用的類似程式碼) 加入現有程式碼的底部：

```js
var teacher1 = new Teacher(
  "Dave",
  "Griffiths",
  31,
  "male",
  ["football", "cookery"],
  "mathematics",
);
```

儲存並重新整理之後，試著存取新 `teacher1` 物件的屬性語函式，例如：

```js
teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
```

這樣應該運作無虞。前 3 行的存取成員即繼承自一般 `Person()` 建構子 (類別)。最後 2 行的存取成員只能用於特定的 `Teacher()` 建構子 (類別) 之上。

**備註：** 如果你無法進行到現有進度，可比較自己與完整版本 (亦可看實際執行的情形) 的程式碼。

這裡所提的技巧，當然不是在 JavaScript 建立繼承類別的唯一方法，但足以堪用。並可讓你了解應如何於 JavaScript 實作繼承。

你可能也想看看某些新的 ECMAScript 功能，可更簡潔的在 JavaScript 中繼承 (參閱 Classes)。但由於這些功能尚未廣泛支援其他瀏覽器，這裡先略過不提。本系列文章中提到的其他程式碼，均可回溯支援到 IE9 或更早版本。當然還是有辦法支援更舊的版本。

一般方法就是使用 JavaScript 函式庫，且最常見的就是簡單集結可用的功能，更快、更輕鬆的完成繼承。例如 CoffeeScript 即提供了 `class`、`extends` 等等。

進階習題
----

在〈OOP 理論〉段落中，我們也納入了 `Student` 類別並繼承了 `Person` 的所有功能，此外也提供不同的 `greeting()` 函式，且較 `Teacher` 的問候語沒那麼正式。在看了該段落中的學生問候語之後，可試著實作自己的 `Student()` 建構子，並繼承 `Person()`, 的所有功能，再實作不同的 `greeting()` 函式。

**備註：** 如果你無法進行到現有進度，可參考完成版本 (亦可看實際執行的情形)。

物件成員摘要
------

簡單來說，你基本上需要考量 3 種類型的屬性＼函式：

1. 已於建構子函式中定義，會交給物件實體的屬性＼函式。這應該很容易處理。在你自己的程式碼中，就是透過 `this.x = x` 類別行並在建構子中定義的成員；在瀏覽器程式碼中，就是僅限物件實體可用的成員 (一般是透過 `new` 關鍵字並呼叫建構子所建立，例如 `var myInstance = new myConstructor()`)。
2. 直接在建構子上定義，並僅能用於該建構子的屬性＼函式。這類屬性＼函式往往只能用於內建瀏覽器物件之上，並直接「鍊接」至建構子 (而非實例) 以利識別，例如 `Object.keys()`。
3. 定義於建構子原型上的屬性＼函式，交由所有的實例繼承，亦繼承了物件類別。這類屬性＼函式包含建構子原型屬性之上定義的所有成員，例如 `myConstructor.prototype.x()`。

如果你不確定到底屬於上述的哪一個，也別擔心。現在你還在學習階段，往後定會熟能生巧。

在 JavaScript 中使用繼承的時機？
----------------------

看到這裡，你心裡應該覺得很複雜。沒錯。「原型」與「繼承」可說是 JavaScript 最複雜的概念之二，但許多 JavaScript 的強大功能與彈性，均來自於其物件結構與繼承，也值得你了解運作方式。

不論是使用 WebAPI 的多樣功能，或是你在字串、陣列等所呼叫的函式＼屬性 (定義於內建瀏覽器物件之上)，你都可以不斷繼承下去。

在自己的程式碼裡，特別是剛接觸或小型專案時，你用繼承的頻率可能沒那麼高。若沒真正需要，只是「為使用而使用」繼承，老實說只是浪費時間。但隨著程式碼規模越來越大，你就會找到使用的時間。如果你發現自己開始建立類似功能的多個物件時，就可先建立通用的物件類型，來概括所有共用的功能，並在特定物件類型中繼承這些功能，既方便又有效率。

**備註：** 基於 JavaScript 運作的方式 (如原型鍊等)，物件之間的功能共享一般稱為「委託 (Delegation)」，即特定物件將功能委託至通用物件類型。「委託」其實比繼承更精確一點。因為「所繼承的功能」並不會複製到「進行繼承的物件」之上，卻是保留在通用物件之中。

當使用繼承時，建議你不要設定太多層的繼承關係，並時時留意你所定義的函式與屬性。在開始寫程式碼時，你可能會暫時修改內建瀏覽器物件的原型，除非你真的需要，否則儘量別這麼做。太多繼承可能連你自己都搞混，而且一旦需要除錯就會痛苦萬分。

最後，物件可說是另一種形式的程式碼再利用，如同函式或迴圈一般，且有其專屬的角色與優點。如果你正建立一堆相關變數與函式，並要全部一起追蹤、封裝，就可以透過物件。你也能以物件方式傳送整個資料集合。而且上述兩種情況均不需使用建構子或繼承就能夠達成。如果你只需要某一物件的單一實作，那麼單純使用物件就好，完全不需要繼承。

摘要
--

本文為大家溫習了 OOJS 核心理論和語法。現在你應該了解 JavaScript 物件與 OOP 的基本概念、原型及原型繼承、建立類別 (建構子) 與物件實例的方法、為類別新增功能、建立從其他類別繼承而來的子類別。

下篇文章就要來看看該如何搭配 JavaScript Object Notation (JSON)，使用 JavaScript 物件的常見資料交換格式。

另可參閱
----

* ObjectPlayground.com — 互動式學習網站，讓你了解物件。
* Secrets of the JavaScript Ninja 的第六章 — 進階 JavaScript 概念與技術的好書。第六章講述了原型與繼承的概念。此書有紙本與線上版。
* You Don't Know JS: this & Object Prototypes — Kyle Simpson 絕佳 JavaScript 手冊系列的一部分。第五章特別深入講述了原型。我們透過本文為初學者提供簡易概念，但 Kyle 則說明得更深入、更精確。
* 前頁
* Overview: Objects
* 次頁


Ajax - 學習該如何開發 Web

Ajax
====

**非同步 JavaScript 及 XML**（Asynchronous JavaScript and XML，AJAX）並不能稱做是種「技術」，而是 2005 年時由 Jesse James Garrett 所發明的術語，描述一種使用數個既有技術的「新」方法。這些技術包括 HTML 或 XHTML、層疊樣式表、JavaScript、文件物件模型、XML (en-US)、XSLT (en-US) 以及最重要的 `XMLHttpRequest` 物件。當這些技術被結合在 Ajax 模型中，Web 應用程式便能快速、即時更動介面及內容，不需要重新讀取整個網頁，讓程式更快回應使用者的操作。

雖然 X 在 Ajax 中代表 XML，但由於 JSON 的許多優點，如輕量以及其本身就是 JavaScript 的一部分等，讓現今 JSON 比起 XML 被更廣泛的使用。JSON 與 XML 兩者都被用來在 Ajax 模型中包裝資訊。

範例
--

### 第一步 – 如何發出 HTTP 請求

為了使用 JavaScript 向伺服器發送 HTTP 請求，便需要一個能夠提供相關功能的類別實體。Mozilla、Safari 及其他瀏覽器則隨後跟進，實作了 `XMLHttpRequest` 類別，以提供微軟最初的 ActiveX 物件中的方法及屬性。

因此，為了建立能夠跨瀏覽器的物件實體，可以這麼寫：

```js
const httpRequest = new XMLHttpRequest();
```

**備註：** 出於解說上的需要，上述代碼使用最簡方式建立 XMLHTTP 的實體。較貼近實際運用的範例，見第三步。

接下來是要決定伺服器傳回資料後的處理方式，此時你只要以 `onreadystatechange` 這個屬性指明要處理傳回值的 JavaScript 函式名稱即可，例如：

```js
function handler() {
  // Process the server response here.
}

httpRequest.onreadystatechange = handler;
```

注意，指定的函式名稱後不加括號也沒有參數。這只是簡單的賦值，而非真的呼叫函數。除了指定函式名稱外，你也能用 Javascript 即時定義函式的技巧（稱為〝匿名函數〞）來定一個新的處理函式，如下：

```js
httpRequest.onreadystatechange = () => {
  // 做些事
};
```

決定處理方式之後你得確實發出 request，此時需叫用 HTTP request 類別的 `open()` 及 `send()` 方法，如下：

```js
httpRequest.open("GET", "http://www.example.org/some.file", true);
httpRequest.send();
```

* `open()` 的第一個參數是 HTTP request 的方法，也就是從 GET、POST、HEAD 等伺服器支援的方法中擇一使用。為遵循 HTTP 標準，請記得這些方法都是大寫，否則有的瀏覽器（如 Firefox）不會處理這些請求。其他可用的 HTTP request 方法的列表請參考 W3C 規格書。
* 第二個參數是請求頁面的 URL。基於安全考量，你不能叫用同網域以外的網頁。如果網域不同，則叫用 `open()` 時會出現「權限不足，拒絕存取」那類的錯誤。常見的錯誤多為在 domain.tld 的網站下呼叫 www.domain.tld 中的網頁，僅是一點點差別都不行。
* 第三個參數決定此 request 是否不同步進行，如果設定為 `TRUE` 則即使伺服器尚未傳回資料也會繼續執行其餘的程式，這也就是 AJAX 中第一個 A 代表的意義。

`send()` 的參數在以 POST 發出 request 時，可以是任何想傳給伺服器的東西，而資料則以查詢字串的方式列出，例如：
```

"name=value&anothername="+encodeURIComponent(myVar)+"&so=on"
```

不過如果你想要以 POST 方式傳送資料，則必須先將 MIME 型態改好，如下：

```js
httpRequest.setRequestHeader(
  "Content-Type",
  "application/x-www-form-urlencoded",
);
```

否則伺服器就不會理你傳過來的資料了。

### 第二步 – 處理伺服器傳回的資料

傳出 request 時必須提供處理傳回值的函數名稱，這個函數是用來處理伺服器的回應。

```js
httpRequest.onreadystatechange = nameOfTheFunction;
```

那麼來看看這個函數該做些什麼。首先，它必須檢查 request 目前的狀態。如果狀態值為 4 代表伺服器已經傳回所有資訊了，便可以開始解析所得資訊。

```js
if (httpRequest.readyState === XMLHttpRequest.DONE) {
  // 一切 ok, 繼續解析
} else {
  // 還沒完成
}
```

`readyState` 所有可能的值如下：

* 0（還沒開始）
* 1（讀取中）
* 2（已讀取）
* 3（資訊交換中）
* 4（一切完成）

接下來要檢查伺服器傳回的 HTTP 狀態碼。所有狀態碼列表可於 W3C 網站上查到，但我們要管的是 `200 OK` 這種狀態。

```js
if (httpRequest.status === 200) {
  // 萬事具備
} else {
  // 似乎有點問題。
  // 或許伺服器傳回了 404（查無此頁）
  // 或者 500（內部錯誤）什麼的。
}
```

檢查傳回的 HTTP 狀態碼後，要怎麼處理傳回的資料就由你決定了。有兩種存取資料的方式：

* `httpRequest.responseText`——這樣會把傳回值視為字串用
* `httpRequest.responseXML`——這樣會把傳回值視為 `XMLDocument` 物件，而後可用 JavaScript DOM 相關函式處理
### 第三步 – 簡單範例

好，接著就做一次簡單的 HTTP 範例，演示方才的各項技巧。這段 JavaScript 會向伺服器要一份裡頭有「I'm a test.」字樣的 HTML 文件（`test.html`），而後以 `alert()` 將文件內容列出。

```html
<button id="ajaxButton" type="button">Make a request</button>

<script>
 (function () {
 var httpRequest;
 document
 .getElementById("ajaxButton")
 .addEventListener("click", makeRequest);

 function makeRequest() {
 httpRequest = new XMLHttpRequest();

 if (!httpRequest) {
 alert("Giving up :( Cannot create an XMLHTTP instance");
 return false;
 }
 httpRequest.onreadystatechange = alertContents;
 httpRequest.open("GET", "test.html");
 httpRequest.send();
 }

 function alertContents() {
 if (httpRequest.readyState === XMLHttpRequest.DONE) {
 if (httpRequest.status === 200) {
 alert(httpRequest.responseText);
 } else {
 alert("There was a problem with the request.");
 }
 }
 }
 })();
</script>
```

在此範例中：

* 首先使用者按下「Make a request」
* 這麼一來就會呼叫 `makeRequest()` 函式，亦傳入參數值 `test.html`（也就是那份 HTML 檔的名稱，放在同目錄下）
* 接著發出 request，而後會將主導權交給 `onreadystatechange` 指定的 `alertContents()` 函式
* `alertContents()` 檢查回應是否正常，而後以 `alert()` 將 `test.html` 的內容列出

你可以由此測試本例，也可以參考測試檔案。

**備註：** 如果你傳送一個要求到一段代碼，而這段代碼將回應 XML 而非靜態的 HTML 檔，那則必須要設定一個可以在 IE 中運作的 header。如果我們不設定 header `Content-Type: application/xml`，IE 將會在我們試圖運作的 XML 項目行下，回應一個 「Object Expected」 的錯誤。

**備註：** 如果我們沒有設定 header `Cache-Control: no-cache`，那瀏覽器將會藏匿 response 並且不再重新傳送 request，造成除錯上的挑戰。我們也可以增加一個 always-different GET 參數，像是 timestamp 或 random number（詳見避開快取）

**備註：** If the `httpRequest` variable is used globally, competing functions calling `makeRequest()` can overwrite each other, causing a race condition. Declaring the `httpRequest` variable local to a closure (en-US) containing the AJAX functions avoids this.

In the event of a communication error (such as the server going down), an exception will be thrown in the `onreadystatechange` method when accessing the response status. To mitigate this problem, you could wrap your `if...then` statement in a `try...catch`:

```js
function alertContents() {
  try {
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        alert(httpRequest.responseText);
      } else {
        alert("There was a problem with the request.");
      }
    }
  } catch (e) {
    alert("Caught Exception: " + e.description);
  }
}
```

### 第四步 – 運用 XML 資料

前面的例子中，在收到 HTTP 傳回值後我們以物件的 `reponseText` 屬性接收 `test.html` 檔案的內容，接著來試試 `responseXML` 屬性。

首先，我們得做個格式正確的 XML 文件以便稍後取用。文件（`test.xml`）內容如下：

```html
<?xml version="1.0" ?>
<root> I'm a test. </root>
```

在程式中，我們叫用檔案的地方只須略事修改如下：

```js
...
onclick="makeRequest('test.xml')">
...
```

接著在 `alertContents()` 中，我們把 `alert(http_request.responseText);` 改成這樣：

```js
var xmldoc = httpRequest.responseXML;
var root_node = xmldoc.getElementsByTagName("root").item(0);
alert(root_node.firstChild.data);
```

這樣一來我們便可取得 `responseXML` 所傳回的 `XMLDocument` 物件，而後以 DOM 方法取用 XML 文件的內容。你可以參考 `test.xml` 的原始碼以及修改過後的測試程式。

關於 DOM 方法，請參考 Mozilla DOM 文件。

### Step 5 – Working with data

Finally, let's send some data to the server and receive a response. Our JavaScript will request a dynamic page this time, `test.php`, which will take the data we send and return a "computed" string - "Hello, [user data]!" - which we'll `alert().`

First we'll add a text box to our HTML so the user can enter their name:

```html
<label
 >Your name:
  <input type="text" id="ajaxTextbox" />
</label>
<span id="ajaxButton" style="cursor: pointer; text-decoration: underline">
  Make a request
</span>
```

We'll also add a line to our event handler to get the user's data from the text box and send it to the `makeRequest()` function along with the URL of our server-side script:

```js
document.getElementById("ajaxButton").onclick = function () {
  var userName = document.getElementById("ajaxTextbox").value;
  makeRequest("test.php", userName);
};
```

We need to modify `makeRequest()` to accept the user data and pass it along to the server. We'll change the request method from `GET` to `POST`, and include our data as a parameter in the call to `httpRequest.send()`:

```js
  function makeRequest(url, userName) {

    ...

    httpRequest.onreadystatechange = alertContents;
    httpRequest.open('POST', url);
    httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    httpRequest.send('userName=' + encodeURIComponent(userName));
  }
```

The function `alertContents()` can be written the same way it was in Step 3 to alert our computed string, if that's all the server returns. However, let's say the server is going to return both the computed string and the original user data. So if our user typed "Jane" in the text box, the server's response would look like this:

`{"userData":"Jane","computedString":"Hi, Jane!"}`

To use this data within `alertContents()`, we can't just alert the `responseText`, we have to parse it and alert `computedString`, the property we want:

```js
function alertContents() {
  if (httpRequest.readyState === XMLHttpRequest.DONE) {
    if (httpRequest.status === 200) {
      var response = JSON.parse(httpRequest.responseText);
      alert(response.computedString);
    } else {
      alert("There was a problem with the request.");
    }
  }
}
```

The `test.php file should contain the following:`

```php
$name = (isset($\_POST['userName'])) ? $\_POST['userName'] : 'no name';
$computedString = "Hi, " . $name;
$array = ['userName' => $name, 'computedString' => $computedString];
echo json\_encode($array);
```

For more on DOM methods, be sure to check out Document Object Model (DOM).

參見
--

使用 XMLHttpRequest API

`XMLHttpRequest` 是 Ajax 的核心。這篇文章將解釋如何使用一些 Ajax 技術，例如：

* 分析及處理伺服器回應
* 監視請求進度
* 提交表單與上傳二進制檔案——使用*單純的* Ajax，或使用 `FormData` 物件
* 在 Web worker (en-US) 中使用 Ajax

*Pure-Ajax* navigation example (en-US)

This article provides a working (minimalist) example of a *pure-Ajax* website composed only of three pages.

Sending and Receiving Binary Data (en-US)

The `responseType` property of the `XMLHttpRequest` object can be set to change the expected response type from the server. Possible values are the empty string (default), `arraybuffer`, `blob`, `document`, `json`, and `text`. The `response` property will contain the entity body according to `responseType`, as an `ArrayBuffer`, `Blob`, `Document`, `JSON`, or string. This article will show some Ajax I/O techniques.

XML (en-US)

可擴展標記語言（Extensible Markup Language，XML）是 W3C 推薦的用於創建特殊用途標記語言的通用標記語言。它是 SGML 的簡化子集，能夠描述許多不同類型的數據。其主要目的是促進不同系統間的數據共享，特別是通過網際網路連接的系統。

解析和序列化 XML (en-US)

如何從一串字串，一個檔案中透過 Javascript 解析一個 XML 文件 ，以及如何將 XML 檔案序列化成字串、Javascript 物件樹(JXON) 或檔案。

XPath (en-US)

XPath stands for **X**ML **Path** Language, it uses a non-XML syntax that provides a flexible way of addressing (pointing to) different parts of an XML (en-US) document. As well as this, it can also be used to test addressed nodes within a document to determine whether they match a pattern or not.

`FileReader` API

The `FileReader` API lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using `File` or `Blob` objects to specify the file or data to read. File objects may be obtained from a `FileList` object returned as a result of a user selecting files using the `<input>` (en-US) element, or from a drag and drop operation's `DataTransfer` (en-US) object.

HTML in XMLHttpRequest (en-US)

The XMLHttpRequest specification adds HTML parsing support to `XMLHttpRequest`, which originally supported only XML parsing. This feature allows Web apps to obtain an HTML resource as a parsed DOM using `XMLHttpRequest`.

XMLHttpRequest specification

WHATWG Living Standard