# 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。

```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`