# Introduction to JavaScript ###### tags: `程式導師` `Web` `JavaScript` JavaScript 是種依賴性很重的程式語言,無法獨立運行,在網頁上必須依賴瀏覽器、在本地端必須依賴 node.js(一種執行環境),才能以 command line 操作,實務開發中,在瀏覽器的執行環境主要在做 debug。 > **ESlint** > 是一種檢查程式碼語法的工具,ESlint 就是檢查 ECMA(JavaScript)語法的工具。 > 在作業專案內,已有先設定好 ESlint,只要 `npm install` 就能啟用 git hooks。 > 需要注意的是,如果都已將錯誤更正,但卻要求一定得使用變數,不然不給過,可以在檔案的程式碼第一行,用註解寫 `/* eslint-disable no-unused-vars*/`。 > > **npm** > node package management,管理 JS 套件的系統。裝 node.js 的同時也會一並自動安裝。 > - `npm init`:啟用,會在專案資料夾內新增一個 package.json 檔 > - `npm install <套件名稱>`:安裝套件,安裝完成的套件會統一放在 node_modules 資料夾內。 > - `var <變數名稱> = require("<套件名稱>")`:在檔案內引入套件來使用 > - [JS101 綜合練習 v1](https://hackmd.io/@laiyenju/js-practice-1) - [程式導師 Week2 作業](https://hackmd.io/@laiyenju/bootcamp-w2-js-hw) ## 基本語法 ### Hello, JS! 新建立一個 hello.js 檔案,寫下 `console.log("Hello")` - 在 command line 環境下執行:`node hello.js` - 在瀏覽器環境下執行:打開瀏覽器 -> 在瀏覽器開啟 hello.js (結果會在 inspect 視窗內印出) :::info 如果不想要新建檔案,直接在終端機內執行也行。 開啟終端機 -> 輸入 `node` -> (進入 node 模式)-> 輸入`console.log("Hello")` ::: ### 做點運算 - `+`、`-`、`*`、`/`、`%`(取餘數) - 邏輯運算 - `||`:OR - `&&`:AND - `!`:NOT - 位移運算 - `>>`:右移 - `<<`:左移 以 2 進位制為基礎作位移運算,為計算方便,右移等於總數除以 2,左移等於總數乘以 2。 如果以此方式做運算會更快嗎?可能會,因為電腦本來就是使用 2 進位處理資料。 `10 << 1` 就是 10*2 = 20 `10 << 3` 就是 10*2*2*2 = 80 `1 << 10` 就是 1*2^10 = 1024 `20 >> 1` 就是 20/2 = 10 `20 >> 2` 就是 20/2/2 = 5 - 位元運算 以 2 進位制為基礎做的位元運算,跟前面講到的 AND、OR 不同。 - `&`: and - `|`:or - `not`:not - `^`:xor `10 & 15` 結果是 10,因為會先將 10 跟 15 轉為 2 進位,分別是 0101 跟 1111,結合邏輯運算中 OR 的概念,0101 跟 1111 的每個位數都會比較,將兩者相加,當都是 1 的話,才會等於 1,成為 1010,若把 1010 寫成我們看得懂的數字,就是 10。(因為$2^0*0+2^1*1+2^2*0+2^3*1=10$) `10 | 15` 概念跟上面相同,只要比較的位數有一個是 1,就等於 1。因此 0101 or 1111,結果是 1111,也就是 15。 `10 ^ 15` 假想成是兩個圓圈部份重疊,xor 要找出的是沒有重疊的部分。所以當兩個數字相同,就等於 0,不同則等於 1,所以 0101 xor 1111 結果是 1010,也就是 10。 `~10` 反轉的概念,將 1 改成 0、0 改成 1,因此 not 0101 結果是 1010。 ```javascript true || true //true,在 OR 的情況下,只要左右兩邊有一情形是 true,結果就是 true true || false //true 3 || 10 //3,只要不是 null、0 這種空白值,就會回傳該數值,後面的數字就不用看了,因為肯定符合其中之一為 true 的條件。 false || 10 //10 false || 0 //0 true && false //false,在 AND 情況下,要左右兩邊都是 true,結果才會是 true true && true //true 3 && 10 //10,一定要兩邊都是 true,所以會回傳最後一個值 true && false //false true && true //true false && 3 //false !ture //false,就是 NOT true !false //true,就是 NOT false ``` ## 變數 ### 變數宣告 - `var box = 1` - `let box = 1` - `const box = 1` ```javascript //沒有賦值,會印出 undefined var box console.log(box) //undefine //沒有宣告,會印出 not defined console.log(circle) //circle is not defined ``` ```javascript //a++ 與 ++a 的差別:處理的先後順序 var a = 0 console.log(a++ && 30) //0 //會先處理 a && 30,這時 a = 0,再執行 a + 1 = a。 === var a = 0 console.log(++a && 30) //30 //會先處理 a = a + 1,這時 a = 1,再執行 a && 30。 ``` ### 變數型態 - 主要有三種:boolean、number、string - `typeof()` 可查看變數型態 - 額外:object、undefined、function - typeof null 會回傳 object,小 bug ### 變數運算的陷阱 - 注意變數型態,需要轉換成數字 ```javascript //轉換方式 1:Number() var a = '10' var b = 20 console.log(Number(a) + b) //轉換方式 2:parseInt() console.log(parseInt(a, 10) + b) //10 指的是十進位 ``` - 浮點數誤差 ```javascript var a = 0.1 + 0.2 console.log(a) console.log(a == 0.3) //false ``` - 永遠使用 `===` 作為等號,會比對變數型態,在 debug 可以快速知道型態是否要調整。 ## Array 陣列 - [MDN Array documents](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) - 陣列的變數型態是 object - 陣列內放的資料,資料型態可以不同,string、number 等都能共存 - `.pop()` and `.push()` 對陣列的最後一個項目動手腳 - `.shift()` and `.unshift()` 對陣列的第一個項目動手腳 - `.join()` , `.concat()`, `.slice()` - slice(要的第一個物件, 要的最後一個物件+1) - 字串也是陣列的一種,可以用陣列的方式處理字串,例如 join() ```javascript var score = [] var index = 1 score[0] = 40 score[1] = 90 score[2] = 70 console.log(score) //[40, 90, 70] console.log(score[index]) //90 console.log(score.length) //3,獲得陣列長度 ``` **Nested Array** ```javascript let nestedArr = [0, 1, 2, [1, 2]]; console.log(nestedArr[3]) //[1, 2] console.log(nestedArr[3][0]) // 1 console.log(nestedArr[3][1]) // 2 ``` ## Object 物件 ```javascript var peter = { name: "Peter", scores: [69, 44, 56, 90], address: "Taipei", father: { name: 'Nick', phone: '344543', } } console.log(peter.name) //Peter console.log(peter['name']) //Peter,用此方式的好處是,可接受自定義變數 var key = 'name' console.log(peter.key) //undefined console.log(peter[key]) //Peter ``` ## 判斷式 ### if/else ```javascript if (為 true 的條件) { 執行事項 } === if (為 true 的條件) { 執行事項 } else { 執行事項 } === if (為 true 的條件) { 執行事項 } else if (為 true 的條件){ 執行事項 } else { 執行事項 } ``` ### switch case ```javascript switch(variable) { case 條件1: 執行事項 break case 條件2: 執行事項 break case 條件3: 執行事項 break case 條件4: 執行事項 break default: 在未給定變數值的情況下,預設執行的事項 } === let atheletePosition = 'first place' switch (atheletePosition) { case 'first place': console.log('You got the gold medal!'); break; case 'second place': console.log('You got the silver medal'); break; case 'third place': console.log('You got the bronze medal'); break; //以陣列輸出也有同樣效果 var month = 3 var months = ['June', 'July', 'September'] console.log(month[month - 2]) //July ``` ### Ternary Operator ```javascript 條件 ? 為true時的執行事項:為false時的執行事項 === let isNightTime = true; if (isNightTime) { console.log('Turn on the lights!'); } else { console.log('Turn off the lights!'); } // write in simple ternary isNightTime ? console.log('Turn on the lights!') : console.log('Turn off the lights!'); ``` ### Truthy and Falsy Assignment ```javascript let defaultName; if (username) { defaultName = username; } else { defaultName = 'Stranger'; } // write in simple Truthy and Falsy statement for default value let defaultName = username || 'Stranger'; ``` ## 迴圈 Loop - 有終止條件的迴圈 - 無終止條件的迴圈:無窮迴圈,通常是因為終止條件設定錯誤,導致無法達成終止條件,所以系統就會一直在迴圈中。 - **重要:在程式碼內放入 `debugger`,可在瀏覽器中以手動的方式一步一步執行程式碼,查看跑的過程。** ```html= <script> debugger <!-- 要 debug 的程式碼 --> </script> ``` :::info ### :bulb: JavaScript 沒有的 label 跟 goto 迴圈 label 跟 goto 的概念,等同迴圈,在某些程式語言可見到,但 JavaScript 沒有,以下程式碼只是假設 JS 使用 label 跟 goto 的運作原理。 ```javascript var i = 1 label: console.log(i) i++ if (i<=100) { goto label } console.log('i=', i) ``` 會從上往下依序執行 1. 印出 i 2. 將 i + 1 3. 檢查 i 是否小於等於 100 4. 如果 i 小於等於 100,就回到 label 開頭,再執行一次 1~3 步驟;不然就印出 i= 101 ::: ### do...while... 借用 label 跟 goto 的使用方式,可比較兩者語法。 ```javascript var i = 1 //當 i小於等於 100 時,就執行 do 內的事項;否則就印出 i=101 do { console.log(i) i++ } while(i<=100) console.log('i=', i) === //將終止條件寫在 do 內 do { console.log(i) i++ if (i<=100){ break } } while(true) console.log('i=', i) ``` ### While 迴圈 與 do...while...差別在,while 會先檢查條件,再執行事項。 ```javascript while (條件) { 符合條件的話,執行事項 } ``` ### for 迴圈 何時使用 for?何時使用 while? 當知道該跑起趟迴圈時,使用 for。 ```javascript for (初始值; 條件; i 在每次迴圈要做的事) { 執行事項 } for (var i=1; i<=100; i++){ console.log(i) } //與 while 迴圈比對 var i = 1 //初始值 while(i<=100){ //條件 console.log(i) //執行事項 i++ //i在每次迴圈要做的事 } ``` ## 函式 function 就像是數學的函數 $f(a, b, c) = a + 2b + 3c$ - $f(1, 2, 3) = 1 + 2*2 + 3*3 = 14$ - $f(1, 1, 1) = 1 + 2*1 + 3*1 = 6$ 若將上述數學函式以 JS 函式表示,寫法如下 ```javascript function 函式名稱(參數) { 算式/執行事項 } function abc(a, b, c) { return a + 2*b + 3*c } console.log(f(1, 2, 3)) console.log(f(1, 1, 1)) ``` ### 宣告函式的方法 ```javascript //方法1 function hello() { console.log('hello') } //方法2 var hello = function(){ console.log('hello') } //方法3 const hello=() => { } ``` ### 函式中的函式 函式也能被當作變數處理。以下示範如何用不同的規範轉換陣列。 ```javascript function transform(arr, transformFunction) { var result = [] for(var i=0; i<arr.length; i++){ result.push(transformFunction(arr[i])) } return result } //印出偶數陣列,將 [1, 2, 3] 變成 [2, 4, 6] function double(x) { return x*2 } console.log( transform([1, 2, 3], double) ) === //以匿名函式來處理 function transform(arr, transformFunction) { var result = [] for(var i=0; i<arr.length; i++){ result.push(transformFunction(arr[i])) } return result } console.log( transform([1, 2, 3], function(x) { return x*2 }) ) ``` 解讀一下程式碼: 1. 將參數帶入 transform 函式 -> `function transform([1, 2, 3], double(x))` 2. 設定一個空的新陣列 `[]` 3. 透過 for 迴圈對 [1, 2, 3] 中的每數字做處理,處理方式是: 1. `result.push(double(arr[0]))` 2. `result.push(double(1))`,double 的參數就是 1 3. `result.push(1*2)`,運算結果是 2 4. 將 2 放到新陣列內 5. 新陣列現在是 `[2]` 6. `result.push(double(arr[1]))` 7. `result.push(double(2))`,double 的參數就是 2 8. `result.push(2*2)`,運算結果是 4 9. 將 4 放到新陣列內 10. 新陣列現在是 `[2, 4]` 11. `result.push(double(arr[2]))` 12. `result.push(double(3))`,double 的參數就是 3 13. `result.push(3*2)`,運算結果是 6 14. 將 6 放到新陣列內 15. 新陣列現在是 `[2, 4, 6]` 4. 回傳新陣列 `[2, 4, 6]` ### 引數(Argument)與參數(Parameter) - `function add(a, b){ return a + b}`,a 跟 b 都是參數,就像設定好該使用的模板。 - `add(1, 2)` 1, 2 是引數字,指的是實際要傳入 function 的數值 - `arguments` 的資料型態長得像 array 的物件(類陣列 array like) ```javascript= //印出 argument function add(a, b){ console.log(arguments[0]) //印出第一個引數:2 return a+b } console.log(add(2, 5)) //檢查 argument 資料型態 function add(a, b){ console.log(arguments) //{ '0': 2, '1': 5} return a+b } console.log(add(2, 5)) ``` ### 內建函式 #### [Number 類型的內建函式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) - `Number()`:字串轉數字 - `parseInt(變數, 進位制)`:字串轉整數 - `parseFloat(變數)`:字串轉浮點數 - `.toString()`:數字轉字串,`a + ''` 也有相同效果 - `Number.MAX_VALUE`:得知可儲存的最大值 - `Math.ceil()`:無條件進位,`Math.ceil(10.9)` = 11 - `Math.floor()`:無條件捨去,`Math.floor(10.9)` = 10 - `Math.round()`:四捨五入,`Math.round(10.9)` = 11 - `Math.sqrt()`:開根號,`Math.sqrt(9)` = 3 - `Math.pow()`:次方,`Math.pow(2.10)` 等於 $2^10$ - `Math.random()`:從 0 ~ 1(不包含 1)產生隨機數 #### [String 類型的內建函式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) - `.toUpperCase()`:轉大寫 - `.toLowerCase()`:轉小寫 - `.charCodeAt()`:查詢字串的 ASCII code,例如想知道 `a = 'dkf'` 中的 d ASCII code,就用 `a.charCodeAt(0)` - `String.fromCharCode()`:查詢 ASCII code 代表的字串 - 字串可以直接比大小,就能知道大小寫,大寫數字大、小寫數字小 - `.indexOf('詞')`:查詢詞在字串中的位置,若回傳值小於零,代表詞不存在字串內 - `.replace('要被替換的詞', `替換他人的詞`)`:取代字串中的某一個詞,只會找位置最前面的替換,其他的不會變。例如 `'hey hello'.replace('h', '!!!')`,只會變成 `!!!ey hello` - `.split()`:切割字串,`.split(' ')` 以空格分割、`.split(',')` 以逗號分割 - `.strim()`:消除空格 - 正規表達式 #### [Array 類型的內建函式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 注意:`join()` 回傳的結果是字串,`filter()`回傳的結果是 array - `.join()`:在陣列中插入某元素,變成字串。`[1, 2, 4].join('!')` 結果是 `1!2!3` - `map(變數)`:將變數帶入陣列中每個元素,而且可以多次操作。 ```javasvript var arr = [1, 2, 4] console.log( arr .map(function (x) { return x*-1 }) .map(function (x) { return x*2 }) ) ``` - `.filter()`:過濾,true 會留下、false 會不見 ```javasvript var arr = [1, 2, 4] console.log( arr .map(function (x) { return x*-1 }) .filter(function (x) { return x > 0 //正數會留下,負數被過濾掉 }) ) ``` - `.slice()`:切陣列,不會改動到原有陣列。以陣列 a = [1, 2, 3, 4, 5] - `a.slice(2)` = [3, 4, 5] - `a.slice(2, 3)` = [3] - `a.slice(2, 4)` = [3, 4] - `.splice()`:切陣列,會改動原陣列。以陣列 a = [1, 2, 3, 4, 5] - `a.splice(1, 0, '100')` = [1, 100, 2, 3, 4, 5],在 a[1] 位置,刪除 0 個元素,插入 'me' - `a.splice(4, 1, '100')` = [1, 2, 3, 4, 100],在 a[4] 位置,刪除 1 個元素,插入 'me' - `.sort()`:排序,預設值依照「字母順序」排列 - 陣列是 months = ['March', 'Jan', 'Feb'],`months.sort()` = ['Feb', 'Jan', 'March'] - 陣列是 num = [1, 30, 4, 21],`num.sort()` = [1, 21, 30, 4] :::info 若要依照數字順序排列陣列元素,可以用此方式 ```javascript= var arr = [1, 30, 4, 21] //要由小排到大 arr.sort(function(a, b){ //想像成 a 是排在前面的數字、b 是排在後面的數字 if (a===b) return 0 if (b>a) return -1 //-1 代表不換位置 return 1 //1 代表 }) console.log(arr) === //要由大排到小 arr.sort(function(a, b){ //想像成 a 是排在前面的數字、b 是排在後面的數字 if (a===b) return 0 if (a > b) return -1 //-1 代表不換位置 return 1 //1 代表 a b 互換位置 }) //其他寫法1 arr.sort(function(a, b){ //想像成 a 是排在前面的數字、b 是排在後面的數字 if (a===b) return 0 return a > b ? -1:1 }) //其他寫法2 arr.sort(function(a, b){ //想像成 a 是排在前面的數字、b 是排在後面的數字 return b - a //結果是 0 ,代表兩者相等;結果是 1 代表 b > a,兩者互換位置;結果是 -1,代表 b < a,兩者不換位置。 }) ``` ::: ### 使用 function 該注意的事情 - return 的話,會直接跳出 function,不會再執行 return 後面的事項 - 在 function 引入外部變數時,可能會有以下狀況:([深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/)) - pass by value - pass by sharing - pass by reference(JS 不會有此狀況) 最簡單理解的狀況,就是當外部變數是數字或字串時,JS 是先複製外部變數,再將外部變數引到 function 內。但當外部變數是 function,JS 就不會複製一份,導致外部變數的 function 數值因此被更改。 - 文字內變數,必須要將 `''` 改為 ` `` ` - 預設變數 ```javascript //文字內變數 function sayThank(name) { console.log('Thank you, ' + name + '.'); } function sayThank(name) { console.log(`Thank you, ${name}.`); } ``` ```javascript //預設變數:milk, bread, eggs function makeShoppingList(item1 = 'milk', item2 = 'bread', item3 = 'eggs'){ console.log(`Remember to buy ${item1}`); console.log(`Remember to buy ${item2}`); console.log(`Remember to buy ${item3}`); } ``` ## 關鍵概念 ### Imutable 不可變 除了物件以外,基本上其他東西都是不可變的(記憶體位置不可變),不會改變到原本的數值。 - array.splice() 不會改動到原本的 array - array.slice() 會改動到原本的 array ```javascript var a = "hello" a.toUpperCase() console.log(a) //hello,因為沒有提供變數或重新宣告變數一次,就沒有新的記憶體位置工儲存 === a = a.toUpperCase() console.log(a) //Hello //Array 是物件,所以是 mutable var arr = [1, 2, 3] arr.push(4) console.log(arr) //1, 2, 3, 4 === arr = arr.push(4) console.log(arr) //4,回傳 arr 的長度 ``` ### Scope 處理宣告變數的有效範圍,通常偏好在 function block 內宣告變數,避免在 function block 外的 global scope 導致衝突。 ### Iterator - `.forEach()` - `.map()` - `.filter()` - `.findIndex()` - `.reduce()` **forEach()** forEach() 內是個 function,總共有二種處理方式:一行式塞入 function、先寫 function 再 callback。 ![](https://i.imgur.com/oSRUdb6.png) ```javascript //function1 groceries.forEach(groceryItem => console.log(groceryItem)); //function2 function printGrocery(element){ console.log(element); } groceries.forEach(printGrocery); //function3 froceries.forEach(function(groceryItem){ console.log(groceryItem); }); ``` ```javascript const fruits = ['mango', 'papaya', 'pineapple', 'apple']; // Iterate over fruits below //function1,用於多種 element fruits.forEach(function(element){ console.log(`I want to eat a ${element}.`); }) //function2 fruits.forEach(element => console.log(`function2: I want to eat a ${element}.`)); //function3 const printResult = (element) => { console.log(`function3: I want to eat a ${element}.`); } fruits.forEach(printResult); ``` **Map()** - Map() 與 forEach() 類似,差別在於 Map() 會回傳新的 array,建立一個新 array。 - map() 內的變數名稱等同 array 名稱,不需另外為 array 內元素定義新的變數名稱。map() 會自行在 array 內尋找對應的元素位置。 ```javascript const animals = ['Hen', 'elephant', 'llama', 'leopard', 'ostrich', 'Whale', 'octopus', 'rabbit', 'lion', 'dog']; // Create the secretMessage array below const secretMessage = animals.map(animals => { return(animals[0]); }); console.log(secretMessage.join('')); //HelloWorld const bigNumbers = [100, 200, 300, 400, 500]; // Create the smallNumbers array below const smallNumbers = bigNumbers.map(bigNumbers => { return(bigNumbers/100); }) console.log(smallNumbers). //[ 1, 2, 3, 4, 5 ] ``` **filter()** ```javascript const randomNumbers = [375, 200, 3.14, 7, 13, 852]; // Call .filter() on randomNumbers below const favoriteWords = ['nostalgia', 'hyperbole', 'fervent', 'esoteric', 'serene']; // Call .filter() on favoriteWords below const smallNumbers = randomNumbers.filter(number =>{ if (number < 250){ return number; } }); const longFavoriteWords = favoriteWords.filter(word => { if (word.length > 7){ return word; }; }); ``` **findIndex()** ```javascript const animals = ['hippo', 'tiger', 'lion', 'seal', 'cheetah', 'monkey', 'salamander', 'elephant']; //找項目是 elephant const foundAnimal = animals.findIndex(animal =>{ if (animal == 'elephant'){ return animal; }; }); //找開頭是 s 的項目 const startsWithS = animals.findIndex(animal => { if (animal[0] === 's'){ return animal; }; }) ``` ### Iterator's Difference - `.forEach()` is used to execute the same code on every element in an array but does not change the array and returns undefined. - `.map()` executes the same code on every element in an array and returns a new array with the updated elements. - `.filter()` checks every element in an array to see if it meets certain criteria and returns a new array with the elements that return truthy for the criteria. - `.findIndex()` returns the index of the first element of an array which satisfies a condition in the callback function. It returns -1 if none of the elements in the array satisfies the condition. - `.reduce()` iterates through an array and takes the values of the elements and returns a single value. - All iterator methods takes a callback function that can be pre-defined, or a function expression, or an arrow function. ```javascript const cities = ['Orlando', 'Dubai', 'Edinburgh', 'Chennai', 'Accra', 'Denver', 'Eskisehir', 'Medellin', 'Yokohama']; const nums = [1, 50, 75, 200, 350, 525, 1000]; // Choose a method that will return undefined cities.forEach(city => console.log('Have you visited ' + city + '?')); // Choose a method that will return a new array const longCities = cities.filter(city => city.length > 7); // Choose a method that will return a single value const word = cities.reduce((acc, currVal) => { return acc + currVal[0] }, "C"); console.log(word) // Choose a method that will return a new array const smallerNums = nums.map(num => num - 5); // Choose a method that will return a boolean value nums.every(num => num < 0); // OR nums.some(num => num < 0); ``` ## 容易踩雷的地方 1. 若是字串跟數字相加,`"1" + 3 = 13` 2. 若是字串跟數字相乘,會自動全轉為數字 `"1"*3=3`