# Day04 - Array Cardio Day1 <!-- .element: class="fragment" data-fragment-index="1" --> <!-- .slide: data-background="#1A237E" --> > 所有的範例在[jsfiddle](https://jsfiddle.net/)上,練習請記得按下F12開啟console。 ## 預先學習 這個session主要是做Javascript Array的練習,因此在開始之前先孰悉一下陣列(Array)。 ### **陣列宣告** 首先來看陣列的宣告如下例: ``` javascript var a = []; //建立一個空陣列 //使用Array建構 var a = new Array(); //等於var a =[];空陣列 var a = new Array(10); //指定陣列長度 a.length會是10元素是空的 var a = new Array(1,2,3,4,5,"test"); //直接將元素放入 ``` ### **陣列的範圍** 陣列是一種可以裝載任何型別的容器,裝載的內容值稱為元素(element),每個元素都有它的排序位置稱為索引(Index)。索引由0起算,索引是使用32位元的數字為索引範圍,第一個元素索引為0最後一個索引4294967294(2^32-2),最大陣列元素為4294967295個。如下例: 範例:[陣列範圍](http://jsfiddle.net/ctu95x2v/) ``` javascript //宣告陣列 var arr = []; //設定最大元素 arr.length=4294967295; console.log(arr.length); //多設定一個元素出現錯誤"Uncaught RangeError: Invalid array length" arr.length+=1; ``` <!-- <iframe width="100%" height="300" src="//jsfiddle.net/ctu95x2v/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> --> 額外一提的範例:[Invalid array length](https://jsfiddle.net/uwtvdehh/) ``` javascript var n = 9007199254740992; //javascript的數字型態是浮點數範圍是2^53與-2^53間 console.log(n); var a = new Array(n); //陣列只接受32位元所以會爆 ``` ### **陣列的長度** Javascript的陣列是稀疏陣列,一般來說陣列的length的屬性是指陣列有內含幾個元素,但Javascript 陣列是稀疏陣列,因此length屬性則為索引的最大數,非元素個數。Javascript保證陣列的length屬性會比陣列內任何一個元素的索引值來得大,如你增大length則陣列的索引範圍會變大,如果縮減則超過length的元素會被刪除。如下例: [陣列變大變小](https://jsfiddle.net/2j668ouo/) ``` javascript var a = [1,2,3,4,5]; //有5個數字元素的陣列 a.length = 3; //a現在是[1,2,3] a.length = 0; //a現在是空陣列[] a.length = 5; //a現在是[,,,,] ``` 如果length的可變屬性造成困擾,可以使用Object.defineProperty()把陣列設定為唯讀: [陣列length屬性唯讀](https://jsfiddle.net/2j668ouo/1/) ``` javascript var a = [1,2,3,4,5]; //有5個數字元素的陣列 Object.defineProperty(a,"length",{writable:false}); a.length = 3; console.log(a); //有設定唯讀元素不變length時然是(5) [1, 2, 3, 4, 5] ``` ### **新增或刪除陣列** 只要把值指定給新的索引就可以新增陣列元素了 ``` javascript var a = []; //建立陣列 a[0] = "test1"; //新增元素字串"test1",索引0 a[1] = "test2"; //新增元素字串"test2",索引1 ``` 也可以使用push()方法,新增元素 ``` javascript a = []; //建立陣列 a.push("test1"); //尾端增加元素 a.push("test2","test3"); //新增兩個元素 ``` 使用delete刪除,就像物件刪除特性一樣 [範例](https:////jsfiddle.net/3L41uoyt) ``` javascript var a = [1,2,3]; console.log(a[1]); //a陣列索引1的值是2 delete a[1]; //刪除索引1 console.log(a[1]); //印出索引1會是undefined console.log(1 in a); //索引1undefined所以會是false console.log(a.length); //刪除不影響陣列長度 ``` <!-- <iframe width="100%" height="300" src="//jsfiddle.net/3L41uoyt/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> --> ### **多維陣列** Javascript不支援多維陣列,但可以使用陣列內存陣列來模擬。[多維陣列模擬範例](https://jsfiddle.net/Lnctoy7q/) ``` javascript var table = new Array(5); //建立5列(row) for(var i=0;i<table.length;i++){ table[i]=new Array(5); //每列5欄(columns) } //存取多維陣列 var temp = table[0][1]; ``` ### **陣列[迭代](https://zh.wikipedia.org/wiki/%E8%BF%AD%E4%BB%A3)** 逐一瀏覽陣列元素的方式最常用for迴圈:[for迭代範例](https://jsfiddle.net/uu99pyj8/) <!-- https://jsfiddle.net/85j08k8n/ --> ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; for(var i=0;i<a.length;i++){ console.log(a[i]); } ``` 上述範例我們有內含空元素與null和undefined,要排除這些元素可以這樣寫 [排除範例](https://jsfiddle.net/uu99pyj8/1/) ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; for(var i=0;i<a.length;i++){ if(!a[i]) continue; console.log(a[i]); } } ``` 若只想跳過未定義(undefined)可以這樣寫 [排除未定義範例](https://jsfiddle.net/uu99pyj8/2/) ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; for(var i=0;i<a.length;i++){ if(a[i]===undefined) continue; console.log(a[i]); } ``` 若只想跳過空元素可以這樣做 [排除空元素範例](https://jsfiddle.net/uu99pyj8/3/) ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; for(var i=0;i<a.length;i++){ if(!(i in a)) continue; console.log(a[i]); } ``` 或是使用for/in,空元素不會被迭代 [for/in範例](https://jsfiddle.net/uu99pyj8/4/) ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; for(var j in a){ console.log(a[j]); } ``` 最後介紹forEach(),它以索引的順序將每個元素傳給你定義的函式。[forEach範例](https://jsfiddle.net/uu99pyj8/5/) ``` javascript var student = {name:'Kevin',age:'?'}; var a = [undefined,1,,2,,3,null,4,,5,,student]; a.forEach(function(x){ console.log(x); }); ``` ### **陣列的方法** 陣列方法詳細說明請參考[Array](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array#方法) <!-- **ES3** join() reverse() sort() concat() slice() splice() push()pop() unshift()shift() **ES5** forEach() map() filter() every()some() reduce()reduceRight() indexOf()lastIndexOf() --> ## 進入解題 解題部份使用[console.table](https://developer.mozilla.org/en-US/docs/Web/API/Console/table)顯示,注意M系列瀏覽器還未實作。 ### 1. 篩選16世紀出生的發明家 使用的陣列方法是[filter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter),它會依照你所下的條件篩選陣列,並傳回一個篩選後的新陣列。請參考下列實作:[範例](http://jsfiddle.net/g45dv9hj) ``` javascript const inventors = [ { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 }, { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 }, { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 }, { first: 'Johannes', last: 'Kepler', year: 1571 , passed: 1630 }, { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 }, { first: 'Max', last: 'Planck', year: 1858, passed: 1947 }, { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 }, { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 }, { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 }, { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 }, { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 } ]; //篩選16世紀出生的發明家 //使用ES6的寫法 const fifteen = inventors.filter(inventor => (inventor.year >= 1500 && inventor.year < 1600)); //使用function的寫法 const fifteen1 = inventors.filter(function(inventor){ if(inventor.year >= 1500 && inventor.year < 1600){ return true; }else { return false; } }); //使用三元運算子 /*const fifteen1 = inventors.filter(function(inventor){ return (inventor.year >= 1500 && inventor.year < 1600) ? true : false; });*/ //ES6寫法篩選傳回一個全新陣列 console.table(fifteen); //使用function寫法篩選後傳回一個全新陣列 console.table(fifteen1); //原本的陣列不受影響 console.table(inventors); ``` <!-- <iframe width="100%" height="300" src="//jsfiddle.net/g45dv9hj/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> --> 這個練習除了陣列練習外你會發現ES6的寫法使用了[箭頭函數](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions)或稱[Lambda](http://www.jollen.org/blog/2013/10/javascript-lambda.html),它是一個匿名的函式跟第二個例子基本上是一樣的。 ``` javascript var x = inventor => (inventor.year >= 1500 && inventor.year < 1600) var x1 = function(inventor){ return (inventor.year >= 1500 && inventor.year < 1600); } ``` 宣告陣列的部分使用了[const](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/const)關鍵字,代表是常數不可變動,使用變數時若要變動會得到Assignment to constant variable.錯誤。[範例](https://jsfiddle.net/bj5Lnr09/) ``` javascript const vue = 'test'; console.log(vue); //嘗試變動常數內容,會得到Assignment to constant variable.錯誤 vue = 'test'; console.log(vue); ``` 在陣列使用const變動陣列元素會有錯嗎?基本上不會有誤const指的是變數的參考位置,因此當你修改了參考位置才會發生錯誤。[範例](https://jsfiddle.net/8sdjrpfk/) ``` javascript const vue = [1,2,3]; //修改陣列index[0]的元素內容 vue[0]=5; console.table([vue]); //指派另一個陣列給const變數,會得到Assignment to constant variable.錯誤 vue = [4,5,6]; ``` ### 2. 列出發明家的姓和名 這個練習主要使用的陣列方法是[map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map),map可以轉換陣列內的元素,它會將每個元素傳入你指定的函式,然後傳回一個由函式回傳值所構成的陣列。如下例:[範例](https://jsfiddle.net/832sp83L) ``` javascript const inventors = [ { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 }, { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 }, { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 }, { first: 'Johannes', last: 'Kepler', year: 1571 , passed: 1630 }, { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 }, { first: 'Max', last: 'Planck', year: 1858, passed: 1947 }, { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 }, { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 }, { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 }, { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 }, { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 } ]; //const fullNames = inventors.filter(inventor => `${inventor.first} ${inventor.last}`); //console.log(fullNames); //console.log(Array.isArray(fullNames)); //map使用匿名函數處理每一個元素,回傳一個函式處理後回傳值的陣列 const fullNames1 = inventors.map(inventor => `${inventor.first} ${inventor.last}`); //經過姓名串接理後的陣列 console.table([fullNames1]); ``` <!-- <iframe width="100%" height="300" src="//jsfiddle.net/832sp83L/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> --> 這個練習除了map練習外,使用[範本字串(Template literals)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)來串接字串,傳統使用+運算子來處理字串串接,ES6加入了範本字串來處理字串串接。 ### 3. 將發明家的生日由大到小排序 這個練習主要使用陣列方法是[sort](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)。它會在原陣列上將元素排序並傳回排序後的陣列參考(不會回傳一個新的陣列),預設的排序是使用Unicode來排列,因此如果需要會暫時將陣列元素轉為字串來做比較。若需要預設排序以外的方法來排序陣列,必須傳入一個比較函式給```sort(a,b)```作為引數,這個函式具有兩個引數,若第一個引數排序要在第二個引數之前須傳回一個小於0的數字反之則傳一個大於0的數字,無關排序則傳回0。[範例](https://jsfiddle.net/eq65u9ks/) ``` javascript var num = [110,1011,22,333]; //使用default Unicode排序,會將數字轉文字 num.sort(); console.table([num]); //使用 num.sort(function(a,b){ //a-b小於0 a排序在b之前 //a-b大於0 a排序在b之後 //a-b等於0 無關排序 return a-b; //反轉可以寫成 //return b-a; }); //ES6可以使用 //num.sort((a, b) => b - a); console.table([num]); ``` 這個練習是將發明家的出生年來排序,出生年是物件屬性此練習使用屬性來作為排序的結果,如底下作答所示[範例](https://jsfiddle.net/Lv09hrjc/) ``` javascript const inventors = [ { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 }, { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 }, { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 }, { first: 'Johannes', last: 'Kepler', year: 1571 , passed: 1630 }, { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 }, { first: 'Max', last: 'Planck', year: 1858, passed: 1947 }, { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 }, { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 }, { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 }, { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 }, { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 } ]; const ordered = inventors.sort(function(a,b){ return a.year-b.year; }); //使用ES6語法 //const ordered = inventors.sort((a, b) => a.year - b.year); console.table(ordered); //作者的答案,效果相同,且沒有等於0的問題 //const ordered = inventors.sort((a, b) => a.year > b.year ? 1 : -1); //你會發現因為是參考所以列印的結果是相同的(ordered與inventors都是參考到同一個陣列位置) //console.table(inventors); ``` ### 4. 所有的發明家共活了多少歲數 這個練習主要使用陣列方法是[Reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce),reduce方法是根據你指定的函式由左至右累加處理陣列內的元素來產生回傳值。reduce方法有兩個引數,第一個就是你指定執行動作的callback函式,第二個引數是要給第一個引數的callback函式的初始值。第二個引數你可以忽略,它會使用第一個元素當作初始值。callback函式也有4個引數分別是1.累加器()2.正在處理的元素3.正在處理的元素的索引4.reduce array。參考下例:[範例](https://jsfiddle.net/uko7jqL1/) ``` javascript var arr = [1, 2, 3, 4]; let sum = arr.reduce(function(acc, val) { console.log(acc); return acc + val; },5); //初始值5 //5+1=6 //6+2=8 //8+3=11 //11+4=15 //sum=15 console.log(sum); //不設定初始值 console.log('不設定初始值---'); let sum2 = arr.reduce(function(acc, val) { console.log(acc); return acc + val; }); //初始值使用第一個元素1 //1+2=3 //3+3=6 //6+4=10 //sum=10 console.log(sum2); ``` 本題解答使用Reduce加總發明家年齡:[範例](https://jsfiddle.net/gzfs7hdh/) ``` javascript const totalYears = inventors.reduce((total, inventor) => { return total + (inventor.passed - inventor.year); }, 0); console.log(totalYears); ``` 同場加映陣列方法[ReduceRight()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight) Reduce是由左至右累加處理陣列,ReduceRight的意思就很明顯啦是由右至左的蕾加處理陣列 ### 5. 依照發明家的年齡排序 本題先把年齡計算出來再排序。 [範例](https://jsfiddle.net/afmug8am/) ``` javascript /*inventors.forEach(function(a){ a.age = a.passed - a.year; }); const oldest = inventors.sort(function(a, b) { return a.age > b.age ? -1 : 1; });*/ //作者解題 const oldest = inventors.sort(function(a, b) { const lastInventor = a.passed - a.year; const nextInventor = b.passed - b.year; return lastInventor > nextInventor ? -1 : 1; }); console.table(oldest); ``` ### 6. 列出巴黎所有名字中包含'de'的路名 必須使用[線上網站](https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris)來做這個練習。首先使用Javascript的選擇器[querySelector()](https://developer.mozilla.org/zh-TW/docs/Web/API/Document/querySelector)來選擇網頁的Element(節點)```document.querySelector('.mw-category')``` 是選擇css class為mw-category的元素,然後使用```category.querySelectorAll('a')``` 選取所有的a子元素並使用[Array.from](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/from)傳回一個陣列,在使用map將a元素的text值取出後用filter篩選出含有de的路名。如下: > 前往[線上網站](https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris)開啟console貼上程式碼實作: ``` javascript const category = document.querySelector('.mw-category'); //使用選擇器選取class="mw-category"元素 const links = Array.from(category.querySelectorAll('a')); //選取.mw-category下所有的a子元素 const de = links .map(link => link.textContent) //使用map將a元素的text內容取出 .filter(streetName => streetName.includes('de')); //使用filter篩選text內含有de的元素 console.log(de); console.table([de]); ``` ### 7. 排序練習,按照姓氏字母排序人物 這個練習使用了split來分割字串回傳一個陣列,有趣的是作者使用了[Destructuring assignment(暫譯:分離指派式)](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)來接陣列值並進行比對排序 ``` javascript const alpha = people.sort((lastOne, nextOne) => { //var a = lastOne.split(', '); //var b = nextOne.split(', '); //return a[0] > b[0] ? 1 : -1; const [aLast, aFirst] = lastOne.split(', '); const [bLast, bFirst] = nextOne.split(', '); return aLast > bLast ? 1 : -1; }); console.log(alpha); ``` ### 8. Reduce 練習,匯總統計data內的相同元素數量 這個練習主要是為了讓你更孰悉reduce,作者使用一個物件作為初始值並判斷物件內是否有與陣列內的字串相同的屬性,藉此累積統計該屬性的數量。如下[範例](https://jsfiddle.net/gg9raobr/): ``` javascript const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck', 'pogostick']; const transportation = data.reduce(function(obj, item) { //obj為初始物件,判斷物件內是否有陣列元素名稱相同屬性 if (!obj[item]) { //若無初始為數字0 obj[item] = 0; } //有陣列元素名稱相同屬性則+1 obj[item]++; return obj; }, {}); console.log(transportation.car); ``` > **參考資料:** > 1. [給開發者的網頁技術文件JavaScript](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript) > 2. Javascript 大全 > 3. Javascript 學習手冊 > **本文僅做Happy Javascript社群使用,內容若有版權侵犯煩請告知**