---
# System prepended metadata

title: F_忍者2
tags: [javascript]

---

[TOC]


## 1.2 了解瀏覽器
![image](https://hackmd.io/_uploads/S1yr_gAd0.png)


- Document Object Model(DOM)文件物件模型:客戶端Web應用程式使用者界面的結構化表示
- 事件(Event):JS大多數都是事件驅動，例如網路事件、使用者產生事件，例如點擊、滑鼠移動、按下鍵盤等
- 瀏覽器API:存取設備資訊、本地端除存資料或遠端伺服器通訊

## 1.3 Using current best practices使用當前最佳實踐
- Debugging skills(程式除錯技巧)
- Testing (測試)
- Performance analysis (效能分析)



## 2.1 The lifecycle 生命週期
![image](https://hackmd.io/_uploads/S18v_gCOC.png)

1. 輸入網址(或點擊超連結)
2. 瀏覽器發出請求送到伺服器
3. 伺服器處理請求
4. 產生HTML CSS JS所組成的回應頁面
5. 進入一個等待事件發生的迴圈
6. 使用者關閉或離開網頁，結束應用程式的生命週期

## 2.2 The page-building phase 頁面建立階段
瀏覽器接收到頁面代碼開始，會有兩個步驟
- step1:解析HTML代碼並建立DOM結構(如圖2.4)
- step2:執行JS程式碼
```!
- 執行step1解析HTML時，當遇到含有<script>節點時，瀏覽器會暫時停止使用HTML代碼建立DOM，而會開始執行step2
- 瀏覽器根據需要會在這兩個step切換多次
- 執行step2到最後一行程式碼時，瀏覽器會離開JS執行模式，繼續處理剩餘的HTML代碼，建立其他DOM節點
```
`script`包含的JS程式碼都是由瀏覽器的JS引擎執行，例如
- Firefox的Spidermonkey
- Chrome和Opera的V8
- Edge的Chakra

![image](https://hackmd.io/_uploads/BkXduxAuA.png)


```!
* 瀏覽器一次解析HTML，一次處理一個HTML元素，並建立DOM
* 每個HTML元素代表一個節點(node)
```

![image](https://hackmd.io/_uploads/Sk3O_xCdR.png)


JS主要提供頁面的動態行為，瀏覽器藉由全域物件(window物件)提供了一組[API](https://developer.mozilla.org/en-US/docs/Web/API)，任意改變所在頁面DOM結構，例如修改刪除現有元素、更改文字內容、修改屬性項、動態建立和新增新的子元素等

```!
window物件：
- window物件是一個特殊的全域物件，藉由他來存取所有其他全域物件、全域變數及瀏覽器API
- 擁有最重要的屬性"document"
```
```!
補充：API是什麼
Application Programming Interface，中文翻譯"應用程式介面"，作為兩個程式的橋樑
```


## 2.3 Event handling 事件處理

![image](https://hackmd.io/_uploads/HJLKdlROR.png)

**Event handling 事件類型**：
- 瀏覽器事件，例如網頁處於已載入或未載入的狀態
- 網路事件，例如伺服器的回應(Ajax事件、伺服器端事件)
- 使用者事件，例如點擊滑鼠、按下鍵盤等
- 計時器事件，例如等候逾時或間隔性的觸發條件

**event-handler registration 註冊事件處理器**：讓瀏覽器知道對特定事件發生時執行的函式，此兩種方式可用來註冊事件：
- 將函式指定給特殊屬性(不建議，因對某個特定事件只能指派一個事件處理器)
- 使用內建的addEventListener方法

## 3.1 使用函式與否的差異為何
### 3.1.1 Functions as first-class objects 函式作為頭等物件
所有物件可以做的事情，函式都能作到，函式是物件，卻是一個可被呼叫的物件

```
函式型程式設計(function programming):
- 程式風格是透過撰寫函式來解決問題
- 易於測試、擴展和模組化的程式碼
```

### 3.1.2 Callback functions 回呼函式
將函式作為參數傳遞給另一個函式，並透過傳遞參數呼叫該函式

```javascript!
 <script>
    var text = 'Domo arigato!';
    report("Before defining functions");

    function useless(ninjaCallback) {
      report("In useless function");
      return ninjaCallback();
    }

    function getText() {
      report("In getText function");
      return text;
    }
    
    report("Before making all the calls");
    
    assert(useless(getText) === text,
           "The useless function works! " + text);
           
    report("After the calls have been made");           
  </script>
```

補充:[Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)

## 3.2 函式作為物件的有趣之處(可跳過)
```javascript!
var ninja = {};
ninja.name = "hitsuke"; //新增一屬性name並給值
console.log(ninja); // {"name": "hitsuke"}

//函式也可以擁有屬性
var wieldSword = function () {};
wieldSword.swordType = "katana";
console.log(Object.entries(wieldSword)); //[["swordType","katana"]]

//在函式屬性中儲存函式，作為之後引用和呼叫
//使用函式屬性建立暫存memoization，避免不必要的計算
```
- Storing functions(儲存函式)是一個集合，可以管理互有關聯的函式，例如一系列事件的回呼函式==不懂？==
- memoization(暫存)允許記得之前計算得到的值，進而提升後續呼叫的執行效能==不懂？==

### 3.2.1 Storing functions 儲存函式

第九章會使用ES6新增的集合表(set)來處理元素必須為唯一值的集合
```javascript!
var store = {
  nextId: 1,
  cache: {},
  add: function(fn) {
    if (!fn.id) {
      fn.id = this.nextId++; //注意++的位置，如果是++this.nextId結果又會不同
      this.cache[fn.id] = fn;
      return true;
} }
};
function ninja(){}

console.log(store.add(ninja), "Function was safely added.");
console.log(!store.add(ninja), "But it was only added once.");

//用console.dir將物件展開會看到更完整的資訊
//作者寫的assert與console.assert有稍微不同，
//assert(condition, describe);true或false都會印出
//內建console.assert(condition, describe)則第一個是false才印出describe內容，true就沒反應

const a = 1;
console.assert(a === 1, "a等於1");//順利通過不會報錯

const a = 2;
console.assert(a === 1, "a不等於1");//會報錯並終止程式
```

==++在後面印出的結果==
說明：因為fn.id先賦值為當下的this.nextId，也就是1，nextId再++變成2，所以印出時，`cache{1:ninja}`，而`nextId:2`
![image](https://hackmd.io/_uploads/rkP8jdktA.png)

==++在前面的結果==
說明：因為this.nextId先++變成2，再賦值給`fn.id`，也就是fn.id=2，所以印出時，`cache{2:ninja}`，而`nextId:2`
![image](https://hackmd.io/_uploads/BkDSt_yYC.png)


### 3.2.2 Self-memoizing functions 自我記憶函式
指建立一個函式，使其能記住之前的計算值函式計算其結果時，將結果與函式參數一起儲存起來，當使用同一組參數進行另一次呼叫時，可以回傳先前儲存的結果
==程式碼看不懂？==
```javascript!
function isPrime(value) {
  //檢查是否建立用來暫存的answers屬性  
  if (!isPrime.answers){
    isPrime.answers = {};
  }
  //檢查該值的計算結果是否已暫存在answers中  
  if (isPrime.answers[value] !== undefined) {
    return isPrime.answers[value];
  }
    
  var prime = value !== 0 && value !== 1; // 1不是質數

  for (var i = 2; i < value; i++) {
    if (value % i === 0) {
      prime = false;
      break; 
  }
    
  return isPrime.answers[value] = prime;
  }
}

console.log(isPrime(5), "5 is prime!" );
console.log(isPrime.answers[5], "The answer was cached!");
```

## 3.3 定義函式
定義函式分為四類：
- 函式宣告(declaration)和函式表達式(expression)
- 箭頭函式(通常稱為lambda函式)
```javascript!
myArg => myArg*2
```
- 函式建構式(constructor):
```javascript!
new Function('a','b','return a+b')
```
- 生成器(generator):可以在應用程式執行時退出和重新進入它們，同時在這些不同的進入點之間保持他們的變數值
==(不懂???)==
```javascript!
function*myGen(){ yield 1; }
```
### 3.3.1函式宣告和函式表達式
**函式宣告**
```javascript!
//函式名稱是必要的
function myFunctionName(myFirstArg,mySecondArg){
    myStatement1;
    myStatement2;
}
```
**函式表達式**
作為指派表達式的右值，或是另一個函式的參數，稱為函式表達式
```javascript!
//變數指派的右側
var myFunc = function(){}

//或是另一個函式呼叫的參數，或作為函式的回傳值
myFunc(function(){
    return function(){};
});

//函式表達式的函式名稱可有可無
```
```javascript!
//其他方式呼叫
//1.函式表達式指派給一個變數，使用該變數呼叫函式
var doNothing = function(){};
    doNothing();

//2.為另一個函式的參數，藉由參數名稱在函式中呼叫
function doSomething(action) {
    action();
}
```
**立即函式IIFE(immediately invoked function expression)**
立即呼叫這個新建立的函式，這種作法稱為「立即呼叫的函式表達式」，簡稱「立即函式」
```javascript!
(function(){})(3);
```
```!
補充:
把函式表達式放在括號內，通知JS解析器他是一個表達式，而不是一個敘述句

Chris補充：IIFE主要是模組隔離
```
使用一元運算子告訴JS引擎這是一個表達式而不是函式宣告，無須在函式表達式使用括號，一元運算子的結果不會儲存在任何地方
```javascript!
+function(){}();
-function(){}();
!function(){}();
~function(){}();
```

### 3.3.2 箭頭函式(arrow function)

![image](https://hackmd.io/_uploads/SJq0S4AOC.png)

- (param1,param2):0個或多個參數，括號不可省略;只有1個參數，括號可有可無
- `=>`:箭頭運算子
- expression:簡單的函式，可省略花括號`{}`和return
如後面接一段程式區塊，沒有return敘述句，結果為undefined
```javascript!
//接expression
  var greet = name => "Greetings " + name;

//接程式區塊
  var greet = name => {
  var helloString = 'Greetings ';
  return helloString + name;
};
```
> Chris補充：其他語言叫做[匿名函式或是Lambda](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/lambda-expressions)，只有JS叫做箭頭函式

## 3.4 Arguments and function parameters 引數與函式參數
- 參數(parameter):函式定義中所列出的變數
- 引數(argument):呼叫函式時傳遞給它的值
```javascript=
function skulk(ninja) { //ninja為參數
  return performAction(ninja, "skulking"); //ninja和 "skulking"為引數
}
var performAction = function (person, action) { //person, action為參數
  return person + "-" + action;
};
var rule = daimyo => performAction(daimyo, "ruling"); 
skulk("Hattori"); //"Hattori"為引數
rule("oda nobunaga"); //"oda nobunaga"為引數
```

如引數的數量多於參數，多的引數不會被指派給任何參數;
如引數的數量少於參數，參數因沒有相對應的引數，參數會被設為undefined
```javascript!
function practice(ninja, weapon, technique) {
  return `${ninja} + ${weapon} + ${technique}`;
}
practice("a", "b", "c", "d"); //"a + b + c"
practice("e"); //"e + undefined + undefined"
```

### 3.4.1 Rest parameters 不定參數
- 函式最後一個參數才可以是不定參數，如果不是放在參數最後，則會報錯
- 以`...`為開頭
- 不定參數會以==陣列==呈現，可以用array methods

```javascript!
function multiMax(first, ...remainingNumbers) {
  //console.log(remainingNumbers); //[1,2,3]
  var sorted = remainingNumbers.sort((a, b) => b - a); //b-a是decscending降冪
  //console.log(sorted); //[3,2,1]
  return first * sorted[0];
}

multiMax(3, 1, 2, 3); //9

//multiMax函式第一個引數3指派給first
//其餘的引數1,2,3則包裝成陣列，並放在remainingNumbers
```
### 3.4.2 Default parameters 預設參數

在ES6，可以指定給某一個參數預設值，如函式有指定一個值，此預設值就會被覆蓋
```javascript!
function performAction(ninja, action = "skulking") {
  return ninja + " " + action;
}
performAction("Fuma"); //"Fuma skulking"
performAction1("Fuma","sneaking"); //"Fuma sneaking" 指定值覆蓋了預設值


//也可以這樣寫但可讀性差，應避免
function performAction1(
  ninja,
  action = "skulking",
  message = ninja + " " + action
) {
  return message;
}
```
補充flag旗標:指的是0和1這個例子，當作設定configure用
作閉包
特殊用的的布林值



## 4.1 Using implicit function parameters 使用函式隱含引數
呼叫函式時，會傳遞兩個隱含參數**this**和**arguments**，它們可以在函式內存取到

### 4.1.1 arguments 參數
- arguments是傳遞給函式的所有參數集合
- 不定參數的出現，使arguments參數的需求減少，但在處理舊程式碼仍會遇到
- 為類陣列，可以使用length和陣列符號，但無法使用陣列的其他方法

```javascript!
function whatever(a, b, c) {
  console.log(a === 1); //true
  console.log(b === 2); //true
  console.log(c === 3); //true
    
  //可使用length屬性檢查引數數量  
  console.log(arguments.length); //5
  
  //可使用陣列符號存取各個引數
  console.log(arguments[0] === a); //true
  console.log(arguments[1] === b); //true
  console.log(arguments[2] === c); //true
  console.log(arguments[3] === 4); //true
  console.log(arguments[4] === 5); //true
}

whatever(1, 2, 3, 4, 5);
```

```javascript!
//沒有任何參數的函式
function sum() {
  var sum = 0;
  for (var i = 0; i < arguments.length; i++) {
    sum += arguments[i]; //透過arguments存取所有函式引數
  }
  return sum; //6
}
sum(1, 2, 3);


//改成不定參數的寫法
function sum(...a) {
  var sum = 0;
  for (var i = 0; i < a.length; i++) {
    sum += a[i];
  }
  return sum; //6
}
sum(1, 2, 3);
```
arguments可以作為函式參數的別名，例如對arguments[0]設置新值，第一個參數的值也會改變

```javascript!
function infiltrate(person) {
  console.log(person === "gardener"); //true
  console.log(arguments[0] === "gardener"); //true
  
  //更改arguments會影響原本參數值
  arguments[0] = "ninja";
  console.log(person); //"ninja"
}
infiltrate("gardener");
```

```!
* 這樣的作法會有造成混淆的風險，因此JS提供「嚴格模式strict mode」來禁用
```
arguments在**嚴格模式**下無法作為別名
```javascript!
//使用嚴格模式
//程式第一行寫上"use strict"

"use strict"
function infiltrate(person) {
  console.log(person === "gardener"); //true
  console.log(arguments[0] === "gardener"); //true
  
  //修改第一個引數，person參數值沒有改變，仍為"gardener"
  arguments[0] = "ninja";
  console.log(person); //"gardener"
}
infiltrate("gardener");
```

### 4.1.2 this參數:介紹函式背景空間(context)
- 當函式被呼叫，除了代表呼叫時引數值的參數外，還會將
還會將this隱含式參數傳遞給函式
- **this通常被稱為函式背景空間(function context)**
- 呼叫函式的方式，會決定this的值

## 4.2 Invoking functions 呼叫函式
使用四種方式呼叫函式
- **作為函式**：一般呼叫形式，例如：skulk()
- **作為一個方法**：呼叫綁定到一個物件上，以提供物件導向的程式設計方式，例如：ninja.skulk()
- **作為一個建構器函式**：創建新的物件，例如：new Ninja()
- **透過函式的apply或call方法**，例如：skulk.call(ninja)或skulk.apply(ninja)


### 4.2.1 Invocation as a function 作為函式來呼叫
- 一個函式被「作為一個函式」呼叫，與其他的呼叫機制(例如：方法、建構器函式、apple/call)區別開來
- 使用()運算子呼叫函式，便會發生此類型的呼叫
```javascript!
//一.作為函式來呼叫
//在普通模式下，this是window物件
function ninja() {
  return this;
}
ninja();
```
在chrome瀏覽器，普通模式下this是window
![image](https://hackmd.io/_uploads/SJoLW9t9R.png)

```javascript!
function samurai() {
  "use strict";
  return this;
}
samurai();
```
在chrome瀏覽器，嚴格模式下this是undefined
![image](https://hackmd.io/_uploads/HyiYG9F5A.png)

嚴格模式比普通模式更直覺，一般來說嚴格模式避免了許多JS的細微怪癖

### 4.2.2 Invocation as a method 作為方法呼叫
- 函式被指派給物件的屬性，且使用該屬性來呼叫函式，就是作為物件的方法來呼叫
- 在方法所屬的物件，該方法的主體中是可以用this來取得
- 當把一個函式作為物件的方法呼叫時，該物件會成為函式所在的背景空間，並可以透過this參數存取
```javascript!
//二.作為方法呼叫
var ninja = {};
ninja.skulk = function () {
  return 1;
};
ninja.skulk();
```

```javascript!
//函式呼叫與方法呼叫之間的差異
//1.當作函式呼叫
function whatsMyContext() {
  return this;
}
whatsMyContext(); //window

//宣告一個變數getMyThis來取得指向whatsMyContext函式的參照
//透過變數呼叫，並不會建立第二個函式實例，僅僅建立相同函式參照
var getMyThis = whatsMyContext;
getMyThis(); //window


//2.建立物件，有一個getMyThis屬性，是參照到whatsMyContext函式
var ninja1 = {
  getMyThis: whatsMyContext
};
//透過物件的getMyThis方法來呼叫函式，此時函式背景空間為ninja1物件
ninja1.getMyThis(); //{getMyThis: ƒ}

var ninja2 = {
  getMyThis: whatsMyContext
};
ninja2.getMyThis(); //{getMyThis: ƒ}
```
使用相同的函式whatsMyContext，但回傳的函式背景空間卻是**取決whatsMyContext如何被呼叫**。ninja1和ninja2物件能夠使用同一個函式，執行時存取並操作其所屬的個別物件，**不需要為了不同物件建立不同的函式複本**，**這是物件導向程式設計的一項根本原則。**

### 4.2.3 Invocation as a constructor 作為建構器來呼叫
建構器函式的宣告就像任何函式一樣，要作為建構器函式來呼叫函式，須在前面加上關鍵字`new`


- 函式建構器：用動態建立的字串來建立函式
```javascript!
//自己補充的範例
let a = new Function(`return 1 + 2`)();
console.log(a); //3
```
- 建構器函式：用於建立和初始化物件實例(object literal)的函式

一般建構器被呼叫時，會觸發以下步驟：
1. 用new關鍵字呼叫一個函式，會建立一個新的空物件
2. 新的空物件會被當成this參數傳遞給建構器，，成為建構器的函式背景空間(以新的空物件為函式背景空間，也就是this參數所代表的物件)
3. new運算子會回傳新建立的物件(也有例外，後面第七章會提到)

```javascript!
//三.使用建構器函式來建立共用的物件
function Ninja(name) {
  this.name = name;
  this.skulk = function () {
    return this;
  };
}

//new關鍵字來呼叫函式，回傳新建立物件
let ninja1 = new Ninja("ninja1");
let ninja2 = new Ninja("ninja2");
ninja1.skulk(); // Ninja {name: 'ninja1', skulk: ƒ}，this會呼叫自己
ninja2.skulk(); // Ninja {name: 'ninja2', skulk: ƒ}，this會呼叫自己

```

#### 建構器的回傳值
* 回傳基礎型別(primitive value)的建構器函式
* 以特定物件作為回傳值的建構器函式

## 9.1 陣列
### 9.1.1 建立陣列
==260==
建立陣列有兩種基本方式：
1. 內建的Array建構器函式
2. 陣列實值`[]`

```javascript!
const ninjas = ["Kuma", "Hattori", "Yagyu"];

const samurai = new Array("Oda", "Tomoe");
```
![image](https://hackmd.io/_uploads/By3LF94wkx.png)


- 陣列的 length 屬性查詢陣列的大小
```javascript!
ninjas.length === 3; // true
samurai.length === 2; // true
```

- 陣列用索引值存取陣列資料項，第一筆資料項的索引值是`0`，最後一筆是 `array.length - 1`
```javascript!
ninjas[0] === "Kuma"; // true
samurai[samurai.length - 1] === "Tomoe"; // true
```

- 如果讀取超過陣列的邊界資料項，得到 `undefined`，代表並沒有東西存在
```javascript!
ninjas[4] === undefined; // true
```
- 寫入邊界以外的位置，陣列會被擴展
```javascript!
ninjas[4] = "Ishi";
```
現在陣列長這樣：
```javascript!
["Kuma","Hattori","Yagyu",undefined,"Ishi"]
```
length 的值回傳為 `5`

==262==
陣列其實就是物件，所以如存取一個不存在的物件屬性，也會得到`undefined`

- 陣列的length有奇特技能，其值可以手動更改
```javascript!
const array1 = ["a", "b", "c"];
array1.length = 2;
console.log(array1); // ["a","b"]
```


### 9.1.2 在陣列兩端新增或移除資料項
- `push`：陣列最尾端**新增**一筆資料項
- `unshift`：陣列最前端**新增**一筆資料項
- `pop`：陣列最尾端**刪除**一筆資料項
- `shift`：陣列最前端**刪除**一筆資料項

```javascript!
const ninjas = [];

// push 從最尾端新增一筆資料項
ninjas.push("Kuma");
console.log(ninjas[0]); // ["Kuma"]
console.log(ninjas.length); // 1

// push 從最尾端新增一筆資料項
ninjas.push("Hattori");
console.log(ninjas[0]); // "Kuma"
console.log(ninjas[1]); // "Hattori"
console.log(ninjas.length); // 2

// unshift 從最前端新增一筆資料項
ninjas.unshift("Yagyu");
console.log(ninjas); // ["Yagyu","Kuma","Hattori"]
console.log(ninjas.length); // 3

// pop 從最尾端移除一筆資料項
const lastNinja = ninjas.pop();
console.log(lastNinja); // "Hattori"
console.log(ninjas); // ["Yagyu","Kuma"]
console.log(ninjas.length); // 2

// shift 從最前端移除第一筆資料項
const firstNinja = ninjas.shift();
console.log(firstNinja); // "Yagyu"
console.log(ninjas); // ["Kuma"]
console.log(ninjas.length); // 1
```
![image](https://hackmd.io/_uploads/S1n9ahNv1e.png)


效能考量：shift / unshift方法會修改陣列第一筆資料項，代表其他資料項的索引值也會一併調整。所以push和pop執行速度明顯比shift / unshift快很多。

### 9.1.3 在陣列的任何位置新增與移除資料項


```javascript!
const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];

delete ninjas[1];

console.log(ninjas); // ["Yagyu",undefined,"Hattori","Fuma"]

console.log(ninjas.length); // 4
```
用 `delete` 方式雖然刪除了一筆資料項，但陣列仍然為四筆資料項，讓刪除的資料變成 `undefined` 。

---
**[splice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)**
可以在指定的索引值開始，移除與插入資料項
```
splice(start, deleteCount, item1, item2, /* …, */ itemN)
```

```javascript!
const ninjas = ["Yagyu", "Kuma", "Hattori", "Fuma"];

var removedItems = ninjas.splice(1, 1); // 從索引值1開始，移除1筆資料

console.log(removedItems); // ["Kuma"]
console.log(ninjas); // ["Yagyu","Hattori","Fuma"]

// 從索引值1開始，移除2筆資料項，再加入3筆新的資料項
removedItems = ninjas.splice(1, 2, "Mochizuki", "Yoshi", "Momochi");
console.log(removedItems); // ["Hattori","Fuma"]
console.log(ninjas); // ["Yagyu","Mochizuki","Yoshi","Momochi"]
```
- splice方法會回傳被移除的資料項的陣列
- 原陣列的資料項會自動調整位置
- 可以在指定位置上新增資料項


### 9.1.4 常見的陣列操作
1. 用for迴圈檢查陣列每一筆資料項
```javascript!
const ninjas = ["Yagyu", "Kuma", "Hattori"];


for (let i = 0; i < ninjas.length; i++) {
  console.log(ninjas[i]);
}
// "Yagyu"
// "Kuma"
// "Hattori"
```

2. 使用陣列內建 forEach 方法
```javascript!
const ninjas = ["Yagyu", "Kuma", "Hattori"];

ninjas.forEach((ninja) => console.log(ninja));

// "Yagyu"
// "Kuma"
// "Hattori"
```

### 陣列對應
把陣列裡的每一筆資料項對應到新陣列的資料項

- 取出武器陣列的笨方法：
建立一個新陣列，用forEach對ninjas陣列做迭代取每個忍者的武器
```javascript!
const ninjas = [
  { name: "Yagyu", weapon: "shuriken" },
  { name: "Yoshi", weapon: "Katana" },
  { name: "Kuma", weapon: "wakizashi" }
];
const weapons = [];
ninjas.forEach((ninja) => weapons.push(ninja.weapon));
console.log(weapons); // ["shuriken","Katana","wakizashi"]
```
- 陣列對應 - map方法
```javascript!
const weapons = ninjas.map((ninja) => ninja.weapon);

console.log(weapons); // ["shuriken","Katana","wakizashi"]
```
![image](https://hackmd.io/_uploads/HkORzLBPye.png)
map函式為陣列裡的每一筆資料項呼叫「回呼函式」一次
再利用回呼函式的回傳值建立一個新陣列

### 測試陣列資料項
了解全部或部份的資料項是否滿足某種條件。
- every()

```javascript!
const ninjas = [
  { name: "Yagyu", weapon: "shuriken" },
  { name: "Yoshi" },
  { name: "Kuma", weapon: "wakizashi" }
];

// in運算子用來檢查屬性名稱是否存在物件中
const allNinjasAreNamed = ninjas.every((ninja) => "name" in ninja);
console.log(allNinjasAreNamed); // true


const allNinjasAreArmed = ninjas.every((ninja) => "weapon" in ninja);
console.log(allNinjasAreArmed); // false
```
every函式為陣列每一筆資料項呼叫回呼函式一次，每次呼叫都回傳true，則every方法回傳true，反之回傳false

只要有一項回傳false，後續資料項都不須再檢查

---
- some()
```javascript!
const someNinjasAreArmed = ninjas.some((ninja) => "weapon" in ninja);
console.log(someNinjasAreArmed); // true
```
some函式只要陣列裡有一筆資料項呼叫回呼函式時回傳true，就會回傳true

有一項回傳true，後續資料項都不須再檢查

---
- find()：在陣列搜尋資料項
```javascript!
const ninjas = [
  { name: "Yagyu", weapon: "shuriken" },
  { name: "Yoshi" },
  { name: "Kuma", weapon: "wakizashi" }
];

const ninjaWithWakizashi = ninjas.find((ninja) => ninja.weapon === "wakizashi");

console.log(ninjaWithWakizashi); 
// { "name": "Kuma","weapon": "wakizashi"}
```
會搜尋第一筆，讓回呼函式回傳true的資料項


```javascript!
const ninjaWithKatana = ninjas.find((ninja) => ninja.weapon === "Katana");

console.log(ninjaWithKatana); // undefined
```
如果找不到符合的，就會回傳 `undefined`

--- 

- filter()：搜尋符合某個指定條件的多筆資料項
```javascript!
const armedNinjas = ninjas.filter((ninja) => "weapon" in ninja);

console.log(armedNinjas);
// [{"name": "Yagyu","weapon": "shuriken"},{"name": "Kuma","weapon": "wakizashi"}]
```
9.9圖中文版有錯，英文版對

---

搜尋陣列的索引值
- indexOf()
```javascript!
const ninjas = ["Yagyu", "Yoshi", "Kuma", "Yoshi"];

// indexOf:把要找出索引值得資料項傳入
ninjas.indexOf("Yoshi"); // 1
```
- lastIndexOf()
如有多筆相同的資料項，要知道最後一筆符合結果的索引值：
```javascript!
ninjas.lastIndexOf("Yoshi"); // 3
```
- findIndex()
回傳第一筆資料項的索引值
```javascript!
const yoshiIndex = ninjas.findIndex((ninja) => ninja === "Yoshi");

console.log(yoshiIndex); // 1
```

---

### 對陣列做排序

語法
```
array.sort((a, b) => a - b);
```
JS引擎實作排序演算法，唯一須提供一個回呼函式讓排序演算法知道兩筆資料項之間的關係

- 回呼函式回傳一個小於0的值，資料項a在資料項b前面
- 回呼函式回傳0，資料項a和資料項b相等
- 回呼函式回傳一個大於0的值，資料項a在資料項b後面


依照字母順序對陣列做排序：
```javascript!
const ninjas = [{ name: "Yoshi" }, { name: "Yagyu" }, { name: "Kuma" }];

ninjas.sort((ninja1, ninja2) => {
  if (ninja1.name < ninja2.name) {
    return -1;
  }
  if (ninja1.anme > ninja2.name) {
    return 1;
  }

  return 0;
});

console.log(ninjas);

/* 
{
  "name": "Kuma"
},
{
  "name": "Yagyu"
},
{
  "name": "Yoshi"
}]
*/
```
回傳-1，代表ninja1在ninja2之前
回傳1，代表ninja1在ninja2後面
如果兩者相等，則會回傳0

### 對陣列進行彙整計算


```javascript!
const numbers = [1, 2, 3, 4];
let sum = 0;

numbers.forEach((number) => (sum += number));
console.log(sum); // 10
```

- reduce()

語法
```
array.reduce(callback, initialValue)
```
```javascript!
const numbers = [1, 2, 3, 4];

const sum = numbers.reduce((aggregated, number) => aggregated + number, 0);

console.log(sum); // 10
```
reduce方法會先取得初始值0，並對陣列每一筆資料項呼叫回呼函式，呼叫時是以上次呼叫的結果，以及當前陣列資料項為引數，最後結果為單獨一個值


## 9.1.5 重複使用內建的陣列函式

摹擬陣列方法：把Array上的方法用在普通物件上
```javascript!
const elems = {
  length: 0,
  add: function (elem) {
    Array.prototype.push.call(this, elem);
  },
  gather: function (id) {
    this.add(document.getElementById(id));
  },
  find: function (callback) {
    return Array.prototype.find.call(this, callback);
  }
};

elems.gather("first");
console.log(elems.length); // 1
console.log(elems[0].nodeType); // 1，回傳1表示是element
console.log(elems.length === 1 && elems[0].nodeType,"verify that we have an element in our stash");
// 1 "verify that we have an element in our stash"

elems.gather("second");
console.log(elems.length); // 2
console.log(elems[1].nodeType); //1
console.log(elems.length === 2 && elems[1].nodeType,"Verify the other insertion");
// 1 "Verify the other insertion"

const findElement = elems.find((elem) => elem.id === "second");
console.log(findElement); // <input id="second">
```
`length`:用來計算陣列的資料項數目
`add`:用來將一筆資料項加到摹擬陣列的最後面
`gather`:根據id來找到元素，並儲存起來
`find`:在自訂物件elems搜尋任意的資料項

**執行`elems.gather("first")`並印出`elems`：**
![image](https://hackmd.io/_uploads/ryXX0lLPJx.png)


**執行`elems.gather("second")`並印出`elems`：**
![image](https://hackmd.io/_uploads/Bkj7AeUDJl.png)


[nodeType](https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType)
[push() - Calling push() on non-array objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push#calling_push_on_non-array_objects)

