網頁程式語法-done
-
JavaScript基礎
在撰寫網頁的時候,HTML主要是決定了網頁內容及文件架構,透過 CSS 來決定文件的外觀,最後還可以使用 JavaScript 來與使用者互動,比如說在瀏覽器端動態改變網頁的內容,當使用者觸發某些事件的時候,變更元素的所套用的所套用的CSS 類別來變更其外觀,又或者在使用者送出表單前先驗證表單欄位所填的內容是否符合需求等。
JavaScript 雖然名字當中含有 Java,但它跟 Java 語言一點關係也沒有。它是一種直譯式(執行時不需經過預先編譯)的腳本語言,瀏覽器已內建 JavaScript 的直譯器,因此不需要另行安裝其它程式。
JavaScript 是內嵌於 HTML 網頁文件中的腳本語言,可以在網頁前端就能夠與使用者透過瀏覽器程式來達到改變網頁的文字或背景顏色,或是進一步切換網頁等功能。
Lesson1
引用方式
在網頁中要引用 Javascript 程式碼來執行有 2 種方式:內嵌或引用外部檔案。
內嵌
將程式碼寫在
<script>
與</script>
標籤之間,而script
標籤可放在<head></head>
內或放在<body></body>
內均可。例如:範例
<!DOCTYPE html> <html> <head> <title>網站設計專題</title> <script> alert("在 head 內的測試訊息"); </script> </head> <body> ...這段文字會出現在「 head 測試訊息 」確認之後... <script> alert("在 body 內的測試訊息"); </script> ...這段文字會出現在「 body 測試訊息 」確認之後... </body> </html>
- ++第 5-7 行++與++第 11-13 行++,`alert()`是 Javascript 內建的函式,用來顯示一個彈出式的警告視窗
執行結果
在網頁文件中出現的訊息視窗
在網頁文件中出現的訊息視窗
在網頁文件中出現的文字內容
何時嵌入 Javascript 比較好?
瀏覽器在載入網頁的時候,會依照 HTML 原始碼標籤出現的順序進行解譯,因此 JavaScript 程式碼出現的位置也會影響到它被執行的時間。
在網頁上的 JavaScript 程式主要目的是為了與使用者互動,一般來說為了提升網頁顯示的效能,會建議盡量將 JavaScript 程式碼置於 HTML 原始碼末段,讓瀏覽器將網頁的其它內容元素繪製完成後,再來執行 JavaScript 程式碼。
引用外部檔案
可用文字編輯器將 JavasSript 程式碼寫在副檔名為 .js 的檔案中,再透過
<script src="引用檔案.js"></script>
引用外部檔案來執行。例如:/* demo.js */ document.write("這是用 JavasSript 產生的內容");
範例
<!DOCTYPE html> <html> <head> <title>網站設計專題</title> </head> <body> <script src="demo.js"></script> </body> </html>
雖然在 HTML 檔案的
body
標籤內沒有包含其它內容,僅透過script
標籤引入了外部的demo.js
來執行,該檔案內的document.write()
可用來動態在網頁中插入內容,因此最終頁面上顯示了「這是用 JavaScript 產生的內容」。執行結果
注意用來引用外部 JavaScript 腳本程式的
script
標籤,不可再內嵌其它程式碼。如需另外內嵌程式碼,需使用另外一組script
標籤。例如:<script src="demo.js"></script> <script> var myTitle = document.querySelector('h1'); myTitle.textContent = '新標題'; </script>
Lesson2
基礎語法
範例
先來看一個範例,請準備 3 個檔案,檔名及內容如下:
- 首先是 HTML 文件 `JSexer1.html`:
<!DOCTYPE html> <html> <head> <title>網站設計專題</title> <link rel="stylesheet" type="text/css" media="screen" href="JSexer1.css" /> </head> <body> <div class="container"> <h1 id="title">歡迎光臨</h1> <div id="main"></div> </div> <script src="JSexer1.js"></script> </body> </html>
- ++第 5 行++,引用外部樣式檔 `JSexer1.css`
- ++第 12 行++,引用外部 Javascript 腳本檔 `JSexer1.js`
目前樣式檔( JSexer1.css )與腳本檔( JSexer1.js )都還沒準備好,因此以瀏覽器開啟
JSexer1.html
的話,頁面看起來大概像這樣:
- 在同一個資料夾下新增樣式檔 `JSexer1.css`:.container { width: 90%; background-color: skyblue; margin: 5pt auto; padding: 5pt; } #title { font-family: "微軟正黑體"; margin: 5pt; } #main { min-height: 100px; margin: 5pt; background-color: snow; }
- ++第 2 行++,指定 `container` 類別的元素寬度為其親代元素寬度的 `90%`,另外由於++第 4 行++,將左右邊界指定為 `auto`,會讓套用 `container` 類別的元素產生水平置中的效果。
- 因為目前 `#main` 元素的內容是空的,若不指定最小高度的話,它的高度會是 0,在頁面上會看不到。因此我們在++第 14 行++,指定高度至少要保留 100 像數。
給定一些簡單的樣式(JSexer1.css)規則後,讓瀏覽器重新載入網頁:
- 在相同資料夾新增 Javascript 腳本程式 `JSexer1.js`:var myTitle = document.querySelector('h1'); var mainContent = document.querySelector('#main'); myTitle.textContent = "Hello, world"; mainContent.textContent = '這一行內容是用 Javascript 產生的噢!';
重新載入網頁後,頁面看起來應該會像這樣:
咦?原本 `JSexer1.html` 的內容被改掉了耶!發生什麼事了?- ++第 1 行++,`<!DOCTYPE html>` 標示了該行其後的內容皆為網頁文件,是給瀏覽器看的說明訊息,不影響程式的執行結果。
在 `JSexer1.js` 的內容中,
- ++第 1 行++,宣告一個名為
myTitle
的變數,而它的初始值被指定為=
右邊的運算結果。document
是一個內建的物件(Object),代表目前這個網頁文件,而querySelector()
則是document
這個物件提供的一個操作方法(Method)。要使用物件提供的操作方法時,必須以.
這個符號來連接物件實例(Instance)以及欲操作的方法名稱。document.querySelector()
方法是用來在網頁文件中取得指定的元素,在使用需要提供它額外的資訊讓它進行搜尋,這些額外提供的資訊叫做「參數」或「引數」,需要被置於方法名稱後的小括號內,例:document.querySelector('h1')
對
document.querySelector()
來說,它的參數是用來指定篩選/搜尋條件用的,我們可以使用前一堂課 CSS 基礎裡提到的 CSS 篩選規則來篩選出之後欲操作的網頁元素。以本行來說,document.querySelector('h1')
的意思就是在網頁文件中找到<h1>
元素,並傳回該元素的實例(物件)。若篩選出來的元素超過 1 個,就只傳回第一個被指到的元素。總結一下,這行程式碼的意思就是宣告一個名為
myTitle
的變數,用它來接收(記住)從文件中找到的第一個<h1>
元素。補充
若想要取得所有符合指定條件的元素的話,可以使用
document
物件提供的另一個方法 `querySelectorAll()`,它會回傳一個陣列,陣列內容即是所有符合條件的網頁元素。
- ++第 1 行++,myTitle
變數現在就代表了前面搜尋到的那個<h1>
元素,這類網頁元素都包含了一個名為textContent
的屬性,用來代表該元素內含的文字資料。同樣的,若我們想要存取某物件的屬性,要使用 `.` 符號來連接物件以及屬性名稱。=
這個運算符號的意思是將右邊的運算結果指定給左方的項目,而雙引號""
包夾起來的這一串連續文字資料叫做「字串」,其內容不包含頭尾的雙引號。整句敘述的意思是將
myTitle
的textContent
屬性的內容設定為Hello, world
,也就是將myTitle
所代表的<h1>
元素的文字內容變更為Hello, world
,因此原本<h1>
標籤裡的「歡迎光臨」就被更改為「Hello, world」。變數及資料型別
除了前述採用內嵌以及引用外部檔案較正式的 2 種引用方式讓瀏覽器來執行 JavaScript 程式碼之外,如果只是要先測試程式碼片段的話,最便利的方式就是透過網頁瀏覽器開發者工具中的網頁主控台,將程式碼直接鍵入主控台來執行,透過觀察執行結果來驗證程式碼。
網頁主控台
現在比較新版的網頁瀏覽器都會內建開發者工具,讓網頁開發者在開發階段可以對網頁進行測試,例如:在頁面上暫時修改 CSS 規則來觀察該規則的效果,或對網頁的 javaScript 程式碼進行測試與偵錯,查看網路存取狀況...等等。網頁主控台是開發者工具裡的其中一個模組,我們可以在這邊鍵入 JavaScript 程式碼來測試執行結果。以下為開啟網頁主控台的方法:
Google Chrome
按下網址列最右方的點點點按鈕開啟選單,找到「更多工具」,再點選「開發人員工具」,或者按Ctrl+Shift+I
組合鍵也可以啟用它:
開發人員工具的「Console」頁籤即為 JavaScript 主控臺:Lesson3
基本資料型別
JavaScript 的基本(Primitive)資料型別有以下幾種:
1. 數值(Number)
JavaScript 內的數值資料,並不像其他程式語言有整數與浮點數的分別,不管有小數點或沒有小數點的數值,在 JavaScript 中是屬於同一種資料型別。
在撰寫整數數值時,預設是 10 進位,若在數值前加上
0x
前綴字串,則表示 16 進位,例如:var num = 100; var color_value = 0x3AF068;
若要表示浮點數值時,可使用科學記號表示法,例如:
var pi = 3.1415; var fp1 = 9.8251E92; /* 9.8251 * 10^92 // 9.8251 乘 10 的 92 次方 */ var sm = 1.8E-26; /* 1.8 * 10^-26 // 1.8 乘 10 的 -26 次方 */
數值常用的運算如下:
- 加法
+
- 減法-
- 乘法*
- 除法/
- 取餘數%
2. 字串(String)
字串是前後都用雙引號 `"` 或是單引號 `'` 包夾的一串連續文字的集合,只要前後使用相同的引號即可。例如:
'這是字串' "這也是字串"
另外,在 Javascript 中並沒有「字元」這種資料型別。
字串常用的操作如下:
- 取得字串長度
.length
var mystr = '範例字串 2019/02'; var len = mystr.length;
len
變數的值會被設定為12
,因為字串裡總共包含 12 個符號。- 取得某位置的符號
[位置]
或.charAt(位置)
位置由
0
起算,第一個符號所在的位置是0
號。例如:var c3 = mystr[3]; // c3 的內容會被設為 '串' var c6 = mystr.charAt(6); // c6 會被設為 '0'
- 附加字串
+
附加字串 或.concat
(附加字串)var s2 = mystr + '!!!'; // s2 的內容被設為 '範例字串 2019/02!!!' var s3 = 'Hello '.concat(mystr); // 'Hello 範例字串 2019/02'
- 是否包含子字串
.includes
(子字串)如果子串內包含指定的子字串,會傳回
true
,否則傳回false
。例如:var hasI = mystr.includes('範例'); // true var hasII = mystr.includes('我好帥'); // false
- 取得子字串出現位置
.indexOf
(子字串) 或.search
(子字串)若指定的子字串有在字串中出現,則傳回子字串第一次開始出現的位置,位置編號由
0
起算;若子字串不存在,則傳回-1
。例如:var i1 = mystr.indexOf('你好'); // -1 var i2 = mystr.search('2019'); // 5
- 取出指定位置的子字串
.substr
(起始位置, 長度)- 若省略第 2 個參數「長度」,表示取出從起始位置開始,一直到字串結尾為止的子字串,例如:
var ss1 = mystr.substr(3); // '串 2019/02'
從
3
號位置開始,一直取到字串結尾為止。- 若由起始位置開始,加上指定長度之後會超過字串長度,則僅取到字串結尾為止,例如:
var ss2 = mystr.substr(8, 8); // '9/02'
從
8
號位置(也就是第9
個符號)開始,最多取出8
個符號。- 若起始位置為負值,表示由字串尾端倒數,最後一個符號的位置編號為
-1
。例如:var ss3 = mystr.substr(-5, 2); // '19'
從倒數第
5
個符號開始,最多取2
個符號。- 以指定符號切分字串
.split
(分隔符號)以分隔符號來將子串進行切割,分割後的字串部件不包含指定的分割符號,結果會依序放在一個陣列內回傳。例如:
var timestr = '12:49:37'; var tokens = timestr.split(':'); // ['12', '49', '37']
如果傳入一個空的分隔符號,會將字串內每個字元分割出來。例如:
var t2 = '你我他大家好'.split(''); // ['你', '我', '他', '大', '家', '好']
- 將字串內容全轉為小寫
.toLowerCase()
-將字串內的所有英文字母轉換為小寫後,回傳新的結果字串,不會更動原字串。
var str = 'Hello! How are you?'; var s2 = str.toLowerCase(); // 'hello! how are you?'
- 將字串內容轉為大寫
.toUpperCase()
將字串內所有英文字母轉大寫並傳回新的結果字串,原字串不會被變更。
var str = 'Hello! How are you?'; var s2 = str.toLowerCase(); // 'HELLO! HOW ARE YOU?'
3. 布林值(Boolean)
只有 2 種值:
true
或false
,通常是關係運算式的結果,表示關係成立或不成立。var n = 196; var isPrime = true; var below100 = n < 100; // false
- 空值(null)
用來表示空值的特殊關鍵字
null
。- 未定義(undefined)
用來表示某個識別字尚未被定義的特殊關鍵字
undefined
。若宣告了一個變數,但尚未為其賦值,此時該變數就會是undefined
。var variable; // 變數 variable 的值為 undefined
陣列
陣列是 Javascript 中基本開資料結構,用來表示一群資料的集合,其內的每一筆資料彼此之間以半形逗號 `,` 隔開。要宣告陣列,可以使用
array()
物件形式,或以[]
形式宣告,例如:var prime_list = array(2, 3, 5, 7, 11, 13, 17, 19); var fib = [1, 1, 2, 3, 5, 8];
陣列內的每筆資料可以是不同的資料型態,例如:
var arr = [ 2, '最小的質數', 3.14159265358, 'pi', [1, 1, 2, 3, 5, 8], {'name': 'Dino', 'score': 95}, ];
陣列裡每筆資料會依其所在位置自動給定編號,由
0
起算,第1
筆資料是0
號,第2
筆資料是1
號,餘此類推。要存取其陣列中某筆資料,則需同時使用陣列名稱加上[位置編號]
,例如:var pi = arr[2]; // 將 pi 指定為 arr 陣列中 2 號位置的內容 arr[3] = '圓周率'; // 將 arr 陣列中的 3 號位置的內容指定為字串 '圓周率'
陣列的基本操作如下:
1. 取得陣列內的元素個素(資料筆數)
.length
var arr = [6, 3, 5, 1, 10, 7, 2, 4, 9, 8]; var len = arr.length; // len 的值被指定為 10
2. 合併陣列
.concat()
.concat()
方法可以用來合併多個陣列,這個方法不會變更原始的陣列,合併完成後會回傳一個新陣列。var odds = [1, 3, 5, 7]; var evens = [2, 4, 6, 8]; var alphas = ['a', 'b', 'c']; var nums = odds.concat(evens); // [1, 3, 5, 7, 2, 4, 6, 8] var alnums = alphas.concat(evens, odds); // ['a', 'b', 'c', 2, 4, 6, 8, 1, 3, 5, 7]
3. 測試是否陣列內所有元素皆符合指定條件
.every()
在使用
.every()
方法時需提供一個回呼函式(callback function)當做參數,用來當做檢測元素是否符合條件的依據,例如:/* 測試 num 是否為偶數 */ function test_is_even(num) { return num % 2 == 0; /* 若是偶數的話,它除以 2 的餘數應為 */ } /* 測試 num 是否小於 10 */ function test_below_ten(num) { return num < 10; } var arr = [2, 4, 6, 8, 1, 3, 9, 7, 5]; var is_all_even = arr.every(test_is_even); /* false */ var is_all_below_ten = arr.every(test_below_ten); /* true */
這個方法會將陣列中的每一個元素,一一交由回呼函式進行測試,若陣列中所有元素都通過測試,
every()
方法會傳回true
;反之,只要有任何一個元素無法通過檢測,則會傳回false
。.every()
方法的回呼函式最多可接受 3 個引數:*
item
(必要的)代表目前正被處理的元素。
*index
(選用),代表目前正被處理的元素的索引值(位置編號)。
*array
(選用),代表目前呼叫 `.every()` 方法的陣列。舉例來說,若我們要檢查索引值為奇數的元素值也為奇數的話,回呼函式可以這樣定義:
function idx_val_both_odd(val, idx) { if (idx % 2 == 1) { /* 如果索引值為奇數 */ return val % 2 == 1; /* 傳回元素值是否為奇數 */ } /* 索引值為偶數時不論奇偶都可以,所以直接回傳 true */ return true; }
另外,回呼函式也可以直接以匿名函式(`Anonymous Function`)形式提供給
.every()
方法,例如:var is_all_even = arr.every(function(val) { return val % 2 == 0; });
Lesson4
匿名函式(Anonymous Function)
通常在定義函式的時候,會為函式命名,以便在需要的時候可以重覆使用該函式,而不需重覆定義。匿名函式便是在定義函式時,僅省略了函式的名稱,但其餘部份皆必須保留。
匿名函式的使用時機,通常是該函式僅在一處被引用到,沒有多處共用的需求時使用。
在 JavaScript 中,需要回呼函式的時候,通常是為了要處理某個特定的情況,少有共用相同處理過程的需求,因此習慣上多會將回呼函式以匿名函式的形式來撰寫。
1. 將指定值填入陣列
.fill()
.fill()
方法的做用是以指定的值填滿陣列,它可接受 3 個參數:*
value
(必要的),代表要填入陣列的值。
*from
(選用),代表要從哪個索引值開始填入值,若省略則預設為0
。
*end
(選用),代表終止填值的索引值,預設為陣列的長度。在此編號前的位置都會被填入指定的值,不包含此位置。var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.fill('*', 3, 5); // [0, 1, 2, '*', '*', 5, 6, 7, 8, 9] arr.fill('a', 7); // [0, 1, 2, '*', '*', 5, 6, 'a', 'a', 'a'] arr.fill(9); // [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
2. 篩選陣列中符合條件的元素
.filter()
使用
.filter()
方法時,需提供一個回呼函式當做篩選元素的依據,這個方法會為陣列中通過回呼函式篩選的元素集合另外建立一個新陣列再回傳,不會變更原陣列的內容。回呼函式可接受
3
個參數,意義同.every()
方法的回呼函式。var num = [8, 1, 5, 9, 4, 2, 7]; // 將偶數挑出來組成新的陣列 var num_even = num.filter(function(val) { return val % 2 == 0; }); /* num_even = [8, 4, 2]; */ /* 挑出 num 陣列中所有大於 5 的元素 */ var big5 = num.filter(function(val) { return val > 5; }); /* big5 = [8, 9, 7]; */
3. 傳回第一個滿足條件的元素
.find()
使用
.find()
方法時,需提供回呼函式當成尋找的依據,此方法會傳回第一個可通過回呼函式檢測的元素。若都沒有任何一個元素可通過檢測,則傳回undefined
。此方法所需的回呼函式的說明與
.every()
方法相同。var arr = [49, 33, 121, 66, 81]; var first = arr.find(function(item, idx) { return item >= 60; }); /* first = 121 */
4. 傳回第一個滿足條件的元素編號
.findIndex()
與
.find()
方法相似,差別在於 `.find()` 方法會傳回第一個滿足條件的元素本身,而.findIndex()
方法則是傳回該元素的索引值。若找不到任何一個滿足條件的元素,則傳回undefined
。var arr = [49, 33, 121, 66, 81]; var first = arr.findIndex(function(item, idx) { return item >= 60; }); /* first = 2 */
5. 將每個元素交由指定的回呼函式處理
.forEach()
使用
.forEach()
方法時,需提供一個回呼函式用來處理陣列中的每一個元素,這個回呼函式會將陣列中的每一個元素當作參數,每個元素各執行一次回呼函式。.forEach()
方法的回呼函式可接受 `3` 個參數,意思與.every()
方法相同:*
item
(必要的),代表目前正被處理的元素。
*index
(選用),代表目前正被處理的元素的索引值(位置編號)。
*array
(選用),代表目前呼叫.every()
方法的陣列。var num = [1, 2, 3, 4, 5, 6, 7]; num.forEach(function(item, index) { console.log(index + ' 號元素為 ' +item+', 其平方為 '+item*item); });
6. 在主控臺上列出
num
陣列中每個元素的內容7. 判斷陣列是否包含指定元素
.includes()
此方法可接受 2 個參數:
*
item
(必要),表示要搜尋的元素。
*from
(選用),表示開始搜尋的索引編號,若為負值,表示該索引由陣列尾端倒數。若省略時預設值為0
。若由
from
指定的索引位置開始搜尋,有找到與item
相等的元素則此方法會回傳true
,否則回傳false
。var brands = ['HTC', 'Apple', 'Asus', 'Acer', 'Nokia', 'LG']; var t1 = brands.includes('Apple'); // true var t2 = brands.includes('Apple', 3); // false
8. 傳回指定元素值在陣列中初次出現的位置編號
.indexOf()
此方法可接受 2 個參數:
*
item
(必要),表示要搜尋的元素。
*from
(選用),表示開始搜尋的索引編號,若為負值,表示該索引由陣列尾端倒數。若省略時預設值為0
。由
from
指定的索引位置開始「__往後__」搜尋,若有找到與item
相等的元素則此方法會回傳第一個找到該元素的索引值,否則傳回-1
。var brands = ['HTC', 'Apple', 'Asus', 'Acer', 'Nokia', 'LG']; var t1 = brands.indexOf('Asus'); // 2 var t2 = brands.indexOf('Apple', 3); // -1
9. 將陣列內容合併為單一字串.join()
.join()
方法會將陣列中的所有元素依序串接、合併成一個字串後回傳。陣列中所有的元素會先被轉換成字串後再串接合併。如果陣列內沒有任何元素(陣列的長度為0
),會回傳空字串''
。它可接受 1 個參數:
*
sep
(選用),用來表示串接元素時的分隔符號,未指定時預設以半形逗號,
隔開元素。若分隔符號設定為空字串,則合併後的結果字串在元素間不會有任何符號。var arr = [2019,'Mar',2]; var dstr = arr.join(); // '2019,Mar,2' var dstr2 = arr.join('-'); // '2019-Mar-2' var dstr3 = arr.join('/'); // '2019/Mar/2' var dstr4 = arr.join(''); // '2019Mar2' var dstr5 = arr.join('-$$$-'); // '2019-$$$-Mar-$$$-2' var empty_arr = []; var estr = empty_arr.join('@@@@@'); // ''
10. 傳回指定元素值在陣列中最後一次出現的位置編號
.lastIndexOf()
作用類似
.indexOf()
方法,只是.lastIndexOf()
方法是改由後往前搜尋。此方法與
.indexOf()
一樣,可接受 2 個參數:*
item
(必要),表示要搜尋的元素。
*from
(選用),表示開始搜尋的索引編號,若為負值,表示該索引由陣列尾端倒數。若省略時預設值為陣列長度-1
,也就是最後一個元素的索引值。由
from
指定的索引位置開始「__往前__」搜尋,若有找到與item
相等的元素則此方法會回傳第一個找到該元素的索引值,否則傳回-1
。var arr = ['b', 'a', 'n', 'a', 'n', 'a']; var a1 = arr.lastIndexOf('a'); // 5 var a2 = arr.lastIndexOf('a', 2); // 1 var a3 = arr.lastIndexOf('a', -2); // 3 var a4 = arr.lastIndexOf('c'); // -1
11. 依指定方法將陣列元素一一轉換 `.map()`
.map()
需一個回呼函式當做轉換元素的依據,這個方法會透過回呼函式一一轉換陣列內的每一個元素,並為轉換後的結果建立一個新陣列再回傳,不會變更原陣列的內容。回呼函式可接受
3
個參數,意義同.every()
方法的回呼函式。var arr_strs = ['apple', 'banana', 'cherry']; var arr_lens = arr_strs.map(function(item, index) { return item.length; }); /* [5, 6, 6] */ var arr_caps = arr_strs.map(function(item) { return item.toUpperCase(); }); /* ['APPLE', 'BANANA', 'CHERRY'] */ var arr_nums = [1, 2, 3, 4, 5, 6]; var arr_square = arr_nums.map(function(item) { return item*item; }); /* [1, 4, 9, 16, 25, 36] */
12. 將資料加入陣列尾端
.push()
.push()
方法可將一或多筆資料附加至陣列尾端,此方法會回傳變更後的陣列長度。此方法會變更陣列的內容。var primes = [2, 3]; var l1 = primes.push(5); /* 傳回 3, 陣列內容變為 [2, 3, 5] */ var l2 = primes.push(7, 11, 13, 17); . /* 傳回 7, 陣列內容變為 [2, 3, 5, 7, 11, 13, 17] */
13. 取得最後一筆資料,並將其移除
.pop()
.pop()
方法用來傳回陣列最後一個元素,並將其自陣列移除。此方法會變更陣列內容。若陣列長度為0
,則會回傳undefined
。var arr = ['this', 'is', 'a', 'book']; var word = arr.pop(); // 'book' var empty_arr = []; var estr = empty_arr.pop(); // undefined
14. 依指定方式將陣列轉化為單一值
.reduce()
.reduce()
方法可接受 2 個參數:*
callback
(必要),回呼函式,用來處理每個元素如何加到累加器之中。此回呼函式可接受 4 個參數:
1.
accu
(必要),累加器(Accumulator),用來累積處理結果。為上次回呼函式被呼叫後所回傳結果。
2.item
(必要),目前準備要處理的陣列元素。
3.index
(選用),目前正待處理的陣列元素的索引值。
4.array
(選用),目前正在處理的陣列,也就是正在呼叫.reduce()
方法的陣列。*
initVal
(選用),累加器的初始值。用文字有點難說明清楚,直接看範例可能比較容易懂:
var arr = [2, 5, 3, 3, 4, 0, 1, 7]; /* 計算 arr 元素的總和 */ var sum = arr.reduce(function(acc, item) { return acc + item; // 每拿到一個 item,加入上次的累加結果 acc }); /* sum = 25 */ /* 用 .reduce() 來模擬 .filter() 方法,找出所有偶數 */ var even1 = arr.reduce(function(acc, item) { if (item % 2 == 0) acc.push(item); return acc; }, []); /* 注意這裡用到 .reduce() 方法的第 2 個參數,將累加器的初值指定為空陣列 */ /* even1 = [2, 4, 0]; */
15. 將陣列元素的順序反轉
.reverse()
此方法會將陣列內的元素順序反轉,原本的第一個元素,會變最後一個,第二個元素會變成倒數第二個元素,餘此類推。完成後回傳陣列本身。
var arr = ['一', '二', '三', '四']; arr.reverse(); // ['四', '三', '二', '一']
16. 移除並回傳陣列的第 1 個元素
.shift()
類似
.pop()
方法,但是移除並回傳的元素是陣列中第 1 個元素。若陣列本來就是空的(長度為0
),則回傳undefined
。var arr = ['一', '二', '三', '四']; var first = arr.shift(); // 傳回 '一',而 arr 陣列內容變為 ['二', '三', '四']
17. 回傳陣列的一部份
.slice()
此方法可回傳一個新陣列,其內容為原陣列指定起點與終點間的元素。
它可接受 2 個參數:
*
from
(選用),表示要從哪個索引位置開始擷取元素。若省略時,預設值為0
。
*end
(選用),擷取至哪個索引位置停止,擷取時不包含此位置的元素。因此最後一個被擷取的元素為此位置前一位。省略未指定時,預設值為陣列長度。from
與end
的索引值若為負整數,表示由尾端倒數。var arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]; var s1 = arr.slice(5, 8); /* [9, 2, 6] */ var s2 = arr.slice(-5, 5); /* [] */ var s3 = arr.slice(3, -3); /* [1, 5, 9, 2, 6, 5] */
18. 測試陣列是否至少有一個元素可通過指定條件的測試
.some()
類似
.every()
方法,但只要陣列中有任何一個元素可通過回呼函式的檢測,此方法就會回傳true
;若所有元素的檢測皆失敗,則回傳false
。var arr = [4, 9, 33, 16, 21, 7]; /* 測試 arr 陣列中是否包含 11 的倍數 */ var t1 = arr.some(function(item) { return item % 11 == 0; }); /* true */ /* 測試 arr 陣列中是否有超過 60 的數 */ var t2 = arr.some(function(item) { return item > 60; }); /* false */
19. 排序陣列元素
.sort()
此方法可對陣列內所有元素由小到大進行排序,並回傳此陣列。它可接受一個回呼函式當作決定元素排序先後次序的依據,使用時若未提供回呼函式,預設的排序順序是將元素轉為字串後,依據字串的 Unicode 編碼位置(code points)決定。此方法會變更陣列的內容。
在 Mozilla 的 MDN 網站上提供了一段程式碼:
var months = ['March', 'Jan', 'Feb', 'Dec']; months.sort(); /* ['Dec', 'Feb', 'Jan', 'March'] */ var arr1 = [1, 30, 4, 21, 100000]; arr1.sort(); /* [1, 100000, 21, 30, 4] */
如上述範例所示,預設的排序方式與大部份人預期的不一樣,因此建議提供自訂的回呼函式供此方法使用。此方法的回呼函式需接受 2 個參數,分別代以表排序過程中
.sort()
方法揀選的任意 2 個元素,為了方便說明,假設回呼函式的第一個參數為a
,第二個參數為b
。回呼函式的傳回值代表以下意義:- 若回傳值小於
0
,表示a
元素小於b
元素,應該把a
元素排在b
元素的前面。
- 若回傳值為0
,表示a
元素與b
元素是一樣大的。它們的順序無法決定。
- 若回傳值大於0
,表示a
元素大於b
元素,應該把b
元素排在a
元素的前面。var arr1 = [1, 30, 4, 21, 100000]; /* 依數值由小到大排序 */ arr1.sort(function(a, b) { return a - b; }); /* [1, 4, 21, 30, 100000] */ /* 依數值由大到小排序 */ arr1.sort(function(a, b) { return b - a; }); /* [100000, 30, 21, 4, 1] */
20. 刪除並插入新元素
.splice()
此方法可接受 1 個以上的參數,說明如下:
*
from
(必要),表示由哪個索引位置開始刪除元素。
*delCount
(選用),表示要從指定位置刪除掉的元素個素。若省略了此參數,或給定的值超過由from
算起的剩餘元素數量,則會刪除自from
位置起至陣列最末端的所有元素。
*item1
,item2
, ... (選用),第 3 個參數起,表示要從from
位置加入陣列的新元素。若沒有指定任何元素,則此方法僅會刪除,並不會新增任何元素到陣列中。此方法會修改原陣列的內容,並回傳被刪除的陣列元素所組成的新陣列。
var arr = ['Apple', 'banana', 'cherry']; var darr = arr.splice(1, 1); /* 從 1 號位置起刪除 1 個元素 */ /* 傳回 ['banana'] 而 arr 變更為 ['Apple', 'cherry'] */ var barr = arr.splice(1, 0, 'Banana'); /* 從 1 號位置起不刪除元素,並插入 'Banana' */ /* 傳回 [] 而 arr 變更為 ['Apple', 'Banana', 'cherry'] */ var carr = arr.splice(0, 2, 'Coconut', 'Orange', 'Pear'); /* 從 0 號位置起刪除 2 個元素,並插入 'Coconut', 'Orange', 'Pear' */ /* 傳回 ['Apple', 'Banana'] */ /* arr 陣列變更為 ['Coconut', 'Orange', 'Pear', 'cherry'] */
21. 新增元素到陣列的開頭
.unshift()
類似
.push()
方法,只是將元素新增到陣列的開頭,並回傳變更後的陣列長度。var arr = [1, 2, 3]; var l1 = arr.unshift(4, 5); /* l1 為 5,而 arr 被變更為 [4, 5, 1, 2, 3] */
Lesson5
物件 object
物件是將相關的數值以及相關功能或操作打包/封裝的一個資料實體,舉例來說,假設現在有兩個學生的資料要記錄:
var mary_name = 'Mary', mary_age = 16, mary_classid = 105, mary_seatno = 3, mary_scores = [98, 99, 95]; var john_name = 'John', john_age = 17, john_classid = 109, john_seatno = 26, john_scores = [89, 92, 91];
若改用物件的方式將相關資料封裝在一起,用一對大括號 `{}` 表示要封裝物件,然後以 `資料名稱: 值` 為一組的方式將欲封裝在一起的資料一項一項列在大括號的範圍內,若有多組資料,其間以半形逗號 `,` 隔開。因此,上面的範例可以改寫為這樣:
var mary = { name: 'Mary', age: 16, classid: 105, seatno: 3, scores: [98, 99, 95] }; var john = { name: 'John', age: 17, classid: 109, seatno: 26, scores: [89, 92, 91] };
物件內每一筆資料列表 `資料名稱: 值` 中的 `資料名稱` 的部份,稱之為物件的 `屬性`,而其所對應的值為 `屬性值`。要存取物件內的屬性的方式有 2 種:
1. 點記法(Dot notation)
以
物件.屬性
的格式來標示欲存取的物件屬性。例如:var stuname = mary.name; // 取得 mary 的 name 屬性的值 mary.seatno = 5; // 將 mary 的 seatno 屬性值變更為 5 mary.scores[0] = 100; // 將 mary 的 scores 屬性的第一個元素改為 100
2. 括號記法(Bracket notation)
以
物件['屬性']
的格式來標示欲存取的物件屬性。例如:var chinese = john['score'][0]; // 取得 john 的 scores 屬性的第一個元素 john['age'] += 1; // 將 john 的 age 屬性值遞增 1
如果要存取的屬性是固定的,而且屬性名稱非數值的話,點記法或括號記法都可以。但若要存取的屬性名稱被暫存在另一個變數內,而在執行的過程中才能決定的話,必須採用括號記法;另一個必須使用括號記法的時機為屬性名稱為數值的情況下。例:
var shape = { type: 'Rectangle', left: 10, top: 20, right: 280, bottom: 120, // 以下 2 個屬性為數值 0: 'rect', 100: ['left', 'top', 'right', 'bottom'] }; var attr = 'type'; var brief_type = shape[0]; // 取得 shape 的 0 屬性值 var t1 = shape[attr]; // 取得 shape 的 type 屬性值 attr = 'top'; var t2 = shape[attr]; // 取得 shape 的 top 屬性值 shape[2019] = '中華民國108年'; // 在 shape 新增屬性 2019 的值為 '中華民國108年'
物件的屬性除了可以是單純的資料型態之外,也可以是之後會提到的函式,這種函式形式的屬性,一般會被將其稱為該物件的
方法(method)
,例如:var stu = { name: 'Tom', age: 17, sayHi: function() { console.log('Hi!'); }, }; stu.sayHi(); // 呼叫 stu 物件的 sayHi 方法
在瀏覽器的網頁主控台上應該會看到
Hi!
訊息。在物件定義的方法內要存取物件自身的屬性或方法時,要透過
this
關鍵字來指涉該物件本身。例如:var stu = { name: 'Tom', age: 17, sayHi: function() { console.log(name, "says Hi!"); }, sayMessage: function(msg) { console.log(this.name, "says", msg); } } stu.sayHi(); stu.sayMessage('Hello!');
會印出
says Hi!
Tom says Hello!- ++第 12 行++的 `stu.sayHi()` 在主控台上印出了
says Hi!
,但是在++第 5 行++sayHi()
方法要印出"says Hi!"
字串前原本想要印出name
的內容,印出來的訊息卻沒有包含name
的內容?
- ++第 13 行++的stu.sayMessage('Hello!')
卻可以將該物件name
屬性的值印出來。重點在++第 8 行++,name
屬性的前面加上了this.
,用來表示要取用當前這個物件(this
)的name
屬性。指定變數值的注意事項
Javascript 中在將變數指定給另一個變數的時候,來源變數的資料型別若不是基本資料型別,在賦值的時候,取得的會是原物件的參考(Reference),而非原物件的值(value)的副本(copy)。
var a_int = 10, b_int = a_int; console.log("before:", a_int, b_int); b = 20; console.log("after:", a_int, b_int);
會印出:
before: 10 10
after: 10 20但若是將一個陣列變數或物件變數指定給另一個變數的話,當對其中一個變數修改陣列或物件的內容,也會反映在另一個變數。例如:
var arr_a = [1, 2, 3], // 將 arr_a 指定為一個陣列物件,其元素內容包依序為 1, 2, 3 arr_b = arr_a; // 將 arr_b 指定給 arr_a console.log("before:", arr_a, arr_b); arr_b.push(4); // 在 arr_b 尾端加入 4 console.log("after arr_b.push(4):", arr_a, arr_b); arr_a[1] = "Two"; // 將 arr_a 第 2 個元素的內容改為 "Two" console.log('after arr_a[1] = "Two":', arr_a, arr_b);
在主控臺中會印出:
before: (3) [1, 2, 3] (3) [1, 2, 3]
after arr_b.push(4): (4) [1, 2, 3, 4] (4) [1, 2, 3, 4]
after arr_a[1] = "Two": (4) [1, "Two", 3, 4] (4) [1, "Two", 3, 4]不管是修改
arr_a
或arr_b
,所有的變更都會在兩個變數中呈現,改了arr_a
就等於修改arr_b
,反之亦然。因為
arr_b = arr_a
的意思,並不是將arr_a
這個陣列的內容 **複製(Copy)** 一份給arr_b
,而是將arr_a
指涉的陣列指定給arr_b
,讓arr_b
與arr_a
指涉同一個對象,換句話說,arr_b
與arr_a
是同一個陣列。另一個例子:
var car1 = {brand: "Toyota", model: "Yaris", nickname: "小Y"}; var car2 = car1; car2.m_year = 2018; // 指定 car2 的 m_year 屬性值為 2018 console.log(car1);
把
car1
印出來看看,結果如下:Object {brand: "Toyota", model: "Yaris", nickname: "小Y", m_year: 2018}
car1
原本沒有m_year
這個屬性,但是在指定 `car2 的 `m_year` 屬性之後,連car1
也出現了相同的屬性與值。所以++第 2 行++將car2
設定為car1
就是讓car2
與car1
指涉同一個物件。以生活上的例子來說,每個人在出生的時候,父母會幫我們命名,但隨著時間流逝,在成長的過程中,可能還會有家人間稱呼用的小名,求學期間同學互相取的綽號,上英文課會取一個英文名字 ... 等。雖然有好幾個不同的稱呼,但是實際上代表的人都是同一個。
之後講到 Javascript 的函式的時候,把某個變數當成參數傳給函式處理時,也是套用相同的觀念。
Lesson6
控制結構
在指定的狀況下才要進行某些動作的話,就需要使用到控制結構。在描述狀況成立或不成立,通常是在比較某兩個東西之間的關係,例如:分數不到 60 分的話,比較是的「成績」的值以與「60」這個值;如果下雨的話,比較的是「天氣狀況」與「下雨」這個值。
要比較兩個東西的關係,會使用到「關係運算子」。
關係運算子 範例|說明 <
|a < b
a
是否小於b
<=
|a <= b
a
是否小於或等於b
>
|a > b
a
是否大於b
>=
|a >= b
a
是否大於或等於b
==
|a == b
a
與b
是否相等!=
|a != b
a
與b
是否不相等===
|a === b
a
與b
是否相等(除了值相等,兩者的資料型態也要相同)| !==
|a !== b
a
與b
是否不相等(值不相等,或者型態不相同)例如:
var score = 62; console.log(score < 60); // false console.log(score >= 60); // true var str = "banana"; console.log(str < "apple"); // false console.log(str == "banana"); // true console.log("200" < "30"); // true
兩個字串相比較的時候是由前往後依序比對相同位置的符號,如果符號相同,則繼續比對下一組,直到同一位置的符號不同,或已經比到字串結尾了。因此上面的最後一行
"200" < "30"
的結果為 `true` 是因為'2'
這個符號的編碼值小於'3'
的符號編碼值(回憶一下計算機概論裡的 ASCII 編碼表,概念上一樣,但是實際上用的碼表大多了)。如果關係運算子前後的兩個運算元的資料型態不相同,Javascript 會試著將它們自動轉型再進行比較,例:
console.log(200 < "30"); // false,將字串 "30" 轉為數值 30 再進行比較
如果需要判斷的條件比較複雜,需要由幾個簡單條件組合而成,這時會使用邏輯運算子來組合條件運算式。
邏輯運算子 範例|說明 A && B
運算式 A
與運算式B
的結果要同時成立,整個結果才會成立A || B
運算式 A
或運算式B
的結果至少有一個成立,整個結果才會成立!A
將運算式 A
的結果反相,若原本為true
則傳回false
;原本是false
就傳回true
例如:要判斷變數
year
中所儲存的西元年是否為閏年的方式,只要滿足以下 2 個條件其中任何一條即可:1. 西元年份可以被 4 整除,但是不被 100 整除
2. 西元年份可以被 400 整除判斷式可以這樣寫:
var isLeap = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
- 第 1 種狀況,可以被 4 整除(
year % 4 == 0
)但是不被 100 整除(year % 100 != 0
),這是一個複合條件,前後都要成立才行,因此以&&
來連接:year % 4 == 0 && year % 100 != 0
- 第 2 種狀況,可以被 400 整除(
year % 400 == 0
)因為滿足第 1 種狀況或第 2 種狀況的西元年份都會是閏年,所以用
||
連接兩種狀況的判斷條件:(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
加小括號
()
用來表示year % 4 == 0 && year % 100 != 0
要先運算完,再跟後面的year % 400 == 0
組合計算。由於&&
與||
兩個運算子的計算優先權是相同的,在這個例子中,即使不加()
,運算的順序會是由左而右依序計算,最後的運算結果也會與原本有加()
的運算式相同。但若調換一下 2 個條件的順序:year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
在這邊的
()
若省略的話,會先計算year % 400 == 0 || year % 4 == 0
,再將運算結果與year % 100 != 0
做&&
運算,這樣一來會與原本的運算式的結果不相同。因此建議要優先計算的運算式還是加上()
來指定優先順序,另外,還可以提升程式碼的可讀性,讓日後觀看或維護程式碼的人員可以很明確地得知運算式的優先順序,而不是靠猜測。Lesson7
條件判斷 if
- **語法1: 單純 if,條件成立時才要做**
if (條件運算式) 條件成立時要執行的動作;
說明:
- 條件運算式一般說來是用關係運算子與邏輯運算子組合出來的運算式,但實際上,任何的運算式,只要運算的結果不為 0,也會視為條件成立。
if (1+2) { /* 因為條件運算式的結果永遠是3,所以這個區塊的內容永遠都會被執行到 */ }
-
if
控制的範圍只有緊跟著它的第一個敘述,如果條件成立時要執行的動作不只一件,可以使用一對大括號{}
將它們打包成一個執行區塊:if (條件運算式) { 條件成立時要執行的動作1; 條件成立時要執行的動作2; 條件成立時要執行的動作3; /* ... */ }
if
需連同要執行的敘述才算完整,所以不要在條件運算式的括號後面就加分號;
if (條件運算式) ; 欲執行的動作; // 這一個敘述不受 if 控制,所以永遠都會執行
如果直接在條件運算式的括號後面就加上分號,表示條件成立的時候要做的動作是**空白**。
舉個例子來說,假設現在我們有一個成績列表的網頁(`chi_score.html`)如下:
<!DOCTYPE html> <html> <head> <title>網站設計專題</title> <style> .pass {color: green} .fail {color: red} </style> </head> <body> <table> <tr><td>王大頭</td><td class="score">88</td></tr> <tr><td>林美美</td><td class="score">92</td></tr> <tr><td>陳天才</td><td class="score">56</td></tr> <tr><td>孫小芸</td><td class="score">75</td></tr> <tr><td>錢多多</td><td class="score">48</td></tr> <tr><td>李加進</td><td class="score">95</td></tr> </table> </body> </html>
表格中代表成績的欄位已全部加上 `score` 類別,另外也在
<style></style>
標籤內定義了pass
與fail
兩個 css 類別。現在利用 javaScript 將低於60
分的欄位的字體顏色改為紅色:var scores = document.querySelectorAll('.score'); scores.forEach(function(item) { if (item.textContent < 60) item.classList.add('fail'); });
- ++第 1 行++,取得所有套用
score
類別的網頁元素,並將其暫存在scores
這個變數之中。document.querySelectorAll()
可使用 CSS 的篩選規則在網頁中篩選出符合規則的網頁元素,這個方法會回傳NodeList
類型的物件,將符合篩選規則的網頁元素透過這個物件回傳。- ++第 2 行++,使用
NodeList
類型的.forEach()
方法對scores
變數中所有元素進行處理。NodeList
類型的.forEach()
方法與陣列的.forEach()
方法相同,它接受一個回呼函式,用來對每一個元素進行處理。在這個匿名回呼函式中,我們只使用的第一個必要的參數
item
,由於NodeList
裡的內容會是符合規則的網頁元素,因此回呼函式每次所接收到的參數item
也會是一個網頁元素類型的變數,也就是像<td class="score">88</td>
這樣一組的標籤元素。- ++第 3 - 4 行++,如果該元素的文字內容小於 60,則為該元素額外套用 `fail` 這個 CSS 類別。
網頁元素的
textContent
屬性指的是它的起始與結尾標籤間的文字內容(不包含 HTML 標籤),以<td class="score">88</td>
這個網頁元素來說,它的textContent
屬性的值就是88
。網頁元素的
classList
屬性指的是該元素所套用的 CSS 類別列表(清單),這個屬性有一個.add()
方法可以新增要套用的 CSS 類別。執行結果:
如何測試上面的程式碼?
- 以瀏覽器開啟成績列表的網頁後,再啟用瀏覽器的網頁主控臺,將上方的 javascript 程式碼鍵入主控臺來測試執行
- 或在網頁原始碼的
</table>
結尾標籤之後加入一組<script></script>
標籤,再將上面的 javascript 程式碼貼入<script>
...</script>
之間,讓網頁在載入時就自動執行。範例
在網頁文件中撰寫以下的範例:
<!DOCTYPE html> <html> <head> <title>網站設計專題</title> <style> .pass {color: green} .fail {color: red} </style> </head> <body> <table> <tr><td>王大頭</td><td class="score">88</td></tr> <tr><td>林美美</td><td class="score">92</td></tr> <tr><td>陳天才</td><td class="score">56</td></tr> <tr><td>孫小芸</td><td class="score">75</td></tr> <tr><td>錢多多</td><td class="score">48</td></tr> <tr><td>李加進</td><td class="score">95</td></tr> </table> <script> var scores = document.querySelectorAll('.score'); scores.forEach(function(item) { if (item.textContent < 60) item.classList.add('fail'); }); </script> </body> </html>
- **語法2: if-else,條件成立或不成立,分別做不同的事情**
if (條件運算式) 成立時欲執行的動作; else 不成立時欲執行的動作;
說明:
- 同 **語法1**
- 條件不成立時,執行else
後的敘述。控制範圍同樣也只有緊跟於else
後的第一個敘述,若條件不成立時要執行 1 個以上的敘述,也需要用一對大括號 `{}`加以打包。當然如果怕搞混,不管幾個敘述都加大括號也可以。if (條件運算式) { 成立時欲執行的動作1; 成立時欲執行的動作2; 成立時欲執行的動作3; } else { 不成立時欲執行的動作1; 不成立時欲執行的動作2; 不成立時欲執行的動作3; }
延續 **語法1** 的範例,假設除了不及格的分數要標成紅色之外,將及格的成績標成綠色(套用
pass
類別):var scores = document.querySelectorAll('.score'); scores.forEach(function(item) { if (item.textContent < 60) item.classList.add('fail'); else item.classList.add('pass'); });
**語法2** 適用在 2 選 1 的情況,也就是有 2 個不同的處理方式,一定會套用其中 1 種。像上面的例子就是,登載成績的這些格子,不是套用fail
類別,就是套用pass
類別。Lesson8
條件式迴圈 while
if
敘述在執行的時候,只針對條件運算式進行單次判斷。如果需要在特定條件下,反覆進行某些動作,一直到該條件不成立時才終止,此時就需要使用迴圈結構while
。語法如下:
while (執行條件運算式) 條件成立時欲反覆執行的動作;
當執行條件運算式的結果為
true
時,會執行一次迴圈內容,執行結束後,再回頭判斷執行條件是否成立,若成立,則再執行一次迴圈內容,接著再重新驗證執行條件 ... 以此類推,一直到迴圈執行條件的運算結果為false
才結束while
迴圈。如果一開始執行條件的運算結果就是false
,則迴圈內容也不會被執行到。var arr = []; while (arr.length > 2) console.log('1 > 2'); // 這行不會被執行到!!!
再以前面
if
的成績列表範例,這次不使用NodeList
類別提供的.forEach()
方法,而是以while
迴圈來將不及格的成績標上紅色:var scores = document.querySelectorAll('.score'); var i = 0; while (i < scores.length) { if (scores[i].textContent < 60) { scores[i].classList.add('fail'); } i++; }
- ++第 2 行++,宣告一個變數
i
,用它來標示要存取scores
清單中哪一號的元素,一開始的初值設為0
- ++第 4 - 9 行++,將
scores
清單中的每個網頁元素拿出來處理。- ++第 4 行++,
NodeList
類型的物件有一個length
屬性,它的值表示由這個物件所記住的清單中總共有幾個網頁元素。若其中有 `n` 個元素的話,這些元素的編號為0
,1
,2
, ...,n-1
。- ++第 5 行++,
scores[i]
表示scores
這個清單物件中,編號為i
號的網頁元素。若該元素的文字內容小於 60 的話
- ++第 6 行++,將該元素的套用類別清單新增fail
這個 CSS 類別
- ++第 8 行++,將i
加1
,表示準備處理下一號的元素。注意
迴圈的內容一定要有機會可以讓執行條件變成不成立的狀況,否則,一但進入迴圈,就出不去了。在這個例子中,若沒加上第 8 行來修改變數 `i` 的值,在迴圈內會永遠檢查 `scores[0]` 這個網頁元素的文字內容,然後永遠不會結束迴圈的執行。
計次式迴圈 for
- **語法1**
JavaScript 的
for
迴圈有幾種形式,第一種形式跟 C, C++, Java, PHP 之類的語法很相似:for (初值設定; 執行條件; 回合收尾) 重覆執行內容;
這個形式的
for
迴圈包含 3 個欄位:- **初值設定**:通常用來初始化計數器變數。只在迴圈一開始被執行一次。
- **執行條件**:在每次執行迴圈內容前,都要先評估執行條件運算式,如果執行條件運算的結果為
true
,才會執行「重覆執行內容」的敘述;若執行條件運算的結果為false
,則跳離for
迴圈的控制結構,由該控制結構後的第一個敘述開始執行。執行條件這個欄位的運算式是可以省略的,在省略的情況之下,表示無條件執行(每次評估執行條件的運算結果都為
true
)。- **回合收尾**:
當滿足執行條件的要求,執行了「重覆執行內容」的敘述後,在下一次評估執行條件前,會先執行一次回合收尾的敘述。通常被用來更新計數器的值。
例如下面這段用來計算
1
至10
的總和:var sum = 0; for (var i = 1; i <= 10; i++) { sum += i; } console.log(sum);
上面這個範例中,
for
迴圈的 3 個欄位分別為:- **初值設定**:
var i = 1
,宣告一個變數i
,並先將其初始值設定為1
- **執行條件**:i <= 10
,在i
小於或等於10
的情況之下要重覆執行迴圈內容
- **回合收尾**:i++
,每次執行完迴圈內容之後,將i
的值遞增1
當這個
for
迴圈執行的時候的步驟如下:1. 先將變數 `i` 的值設定為
1
2. 接著檢查執行條件1 <= 10
3. 執行迴圈內容sum += 1
4. 進行回合收尾,將i
的值加1
,讓i
的值變成2
5. 再重新評估迴圈執行條件2 <= 10
6. 執行迴圈內容sum += 2
7. 回合收尾,遞增i
讓i
變成3
8. 重新評估迴圈執行條件3 <= 10
9. 執行迴圈內容sum += 3
10. 執行回合收尾,變數i
的值遞增為4
11. 重新評估執行條件4 <= 10
12. ...以此類推,直到回合收尾之後,i
的值變為11
13. 重新評估執行條件11 <= 10
,條件失敗,結束迴圈的執行
14. 執行for
迴圈結構後的第一個敘述,在主控臺上印出sum
的內容這個形式的
for
迴圈可以跟while
迴圈相互轉換:/* 初值設定; */ while (執行條件) { 重覆執行內容; 回合收尾; }
因此也可以用這個形式的
for
迴圈來改寫while
迴圈中的範例,將不及格的成績標為紅色:var scores = document.querySelectorAll('.score'); for (var i = 0; i < scores.length; i++) { if (scores[i].textContent < 60) { scores[i].classList.add('fail'); } }
- **語法2:
for in
**這個形式的
for
迴圈用來在物件的**可列舉(enumerable)**的**屬性名稱**間進行迭代,語法如下:for (變數 in 物件) { 迴圈內容; }
- **變數**:每迭代一次,這個變數的內容會被設定為不同的屬性名稱。
- **物件**:欲列舉屬性名稱的物件。先來看一個範例:
var arr = ['Hello', 'How', 'are', 'you', 2019]; for (var p in arr) { console.log(p, arr[p]); }
在網頁主控臺 ***可能*** 會印出:
0 Hello 1 How 2 are 3 you 4 2019
注意
何時不應該使用
for in
迴圈來處理陣列元素如果你的應用情境會依賴元素的順序的話,建議不要使用
for in
迴圈來處理陣列的元素。由於for in
取得的可列舉屬性除了數字之外,也會包含非數字型態的屬性名稱,因此for in
並不確保依據某種特定的次序來取得索引值。for in
迴圈會在僅保證會在物件可列舉的屬性間迭代,但是迭代的次序就得看各家瀏覽器的實作方式,透過for in
來迭代陣列的索引值,可能不會有一個一致性的順序。若處理次序對你的應用來說是重要的情況之下,建議用for
迴圈的第一種形式,直接以計數器依指定順序來處理陣列元素,或是採用陣列的.forEach()
方法。
再來看另外一個例子:
var stu = {name: 'Dino', seatno: 3, score: 98}; for (var p in stu) { console.log(p, stu[p]); }
在網頁主控臺上會印出:
name Dino seatno 3 score 98
回到成績列表的範例,我們也可以用
for in
迴圈來改寫:var scores = document.querySelectorAll('.score'); for (var i in scores) { if (scores[i].textContent < 60) { scores[i].classList.add('fail'); } }
- **語法3:
for of
**這個形式的
for
迴圈用來在**可迭代物件**(iterable object)的可列舉**屬性值**間進行迭代,語法如下:for (變數 of 物件) { 迴圈內容; }
可迭代物件(iterable object)?
可迭代物件包含了 Javascript 內建的字串(
String
)、陣列(Array
)、類陣列(Array-like
)的物件(像函式的參數arguments
物件,網頁裡的NodeList
物件等),還有TypedArray
、Set
、Map
這些比較進階一點的資料結構等。自訂的一般物件並不屬於可迭代物件,所以底下這樣的寫法,執行時會發生錯誤:
var stu = {name: 'Dino', seatno: 3, score: 98}; for (var val of stu) { console.log(val); }
如果要讓自訂物件也變成可迭代物件,讓
for of
迴圈可以操作的話,就要為該物件定義Symbol.iterator
方法,讓 Javascript 知道該如何在該物件進行迭代,每次迭代要如何取值。例:var stu = {name: 'Dino', seatno: 3, score: 98}; stu[Symbol.iterator] = function *() { for (var attr in this) yield this[attr]; }; for (var val of stu) { console.log(val); }
- ++第 1 行++,為
stu
物件定義Symbol.iterator
方法。- ++第 2 行++,
stu
的Symbol.iterator
是一個匿名函式function *()
。注意一下function
關鍵字與小括號()
之間的星號*
,那個星號表示該函式是一個產生器(Generator)。- ++第 4 行++,以
yield
來回傳每回合佚代的結果。注意,產生器(Generator)這種特殊型態的函式,不能以return
來回傳結果,它僅能使用yield
將結果回傳,並**暫停**產生器的執行,等待下一次被呼叫的時候,再恢復暫停時的狀態繼續執行,直到下一次遇到yield
指令,或者將產生器函式內的程式碼都執行完。- 產生器是比較進階的主題,有興趣的話,可以再去查找相關資料。
為
stu
物件定義Symbol.iterator
之後,Javascript 就可以透過這個產生器函式來取得每一回合迭代的結果。跟
for in
迴圈有點類似,但是for in
迴圈每次迭代取得的是**屬性名稱(或索引)**,而for of
迴圈每次迭代則是取得**屬性值**。var arr = ['Hello', 'How', 'are', 'you', 2019]; for (var val of arr) { console.log(val); }
會印出
arr
陣列中每個元素的**內容(值)**:Hello How are you 2019
若對同一個陣列,改以
for in
迴圈來操作:for (var val in arr) { console.log(val); }
則會印出 `arr` 陣列中每個元素的**索引(鍵值)**:
0 1 2 3 4
Lesson9
函式(function)
若有一段程式碼需要被使用到好幾次,可以考慮將它獨立成一個自訂函式,然後在需要用到的地方採用函式呼叫即可。定義一個函式的語法:
function 函式名(參數列表) { 函式內容; /* ... */ return 傳回值; }
- **函式名** 可自行命名,建議取與該函式的功能或作用相關的名字
- **參數列表** 為函式執行時,傳給函式的額外資訊。若無可省略不寫,若有一個以上的參數,則參數間需以逗號
,
隔開- **函式內容** 就是這個函式要進行的作業的步驟有哪些
- **傳回值** 如果需要回傳處理結果的話,可以透過關鍵字
return
將結果回傳假設測試某個整數是否為質數這件事需要在程式裡被用到好幾次,那我們可以這樣試著定義一個函式:
function is_prime(n) { for (var i = 2; i < n; i++) { // 以 2, 3, ..., n-1 來測試 n 可否被整除 if (n % i == 0) // 若 n 可被 i 整除,表示不是質數,直接回傳 false return false; } return true; // 通過所有測試,沒有數字將 n 整除,回傳 true }
這裡先以質數最原始的定義來實作:除了
1
與n
自身之外,沒有其它數可以整除n
,此時稱n
為質數。因此我們拿2
至n-1
之間的所有數字來測看看能否將n
整除,若過程中發生整除,則回傳false
,若測試範圍內所有的數字都無法整除n
,則n
為質數。定義完函式後,在需要這個功能的地方就可以直接使用函式呼叫來取得結果,例如:
if (is_prime(337)) console.log('337 是質數');
- 呼叫
is_prime()
函式,並將337
當成參數傳遞給函式。在is_prime()
函式中,參數 `n` 一開始取得的值就是傳遞過來的337
,因此此函式會測試2
到336
之間是否有任何一個數可以整除337
。函式執行時取得的參數的值,與被呼叫時提供的參數的順序有關,例:
function mytest(a, b, c, d) { console.log("a = ", a, ", b = ", b, ", c = ", c, ", d = ", d); } mytest(2, 7, 1, 4);
在網頁主控臺上會印出:
a = 2 , b = 7 , c = 1 , d = 4
先前提過的回呼函式(Callback function)也是函式噢!