[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)