# JavaScript & Jquery
[TOC]
>[color=green]https://ithelp.ithome.com.tw/articles/10238074
## 比較
- **`==`** vs **`===`**:**`==`** 會對被判別的變數做轉換型別的動作(coercion又稱為implicit type conversion),轉換型別後值一樣就可以。**`===`** 則是要求型別也要一樣
- **`undefined`** vs **`null`** 的差異:**`undefined`** 代表的是未定義,比如變數無賦予值的話該變數預設就會是 **`undefined`** 。**`null`** 的意義是空值,是讓開發者用來宣告變數沒有值。
## Ajax
### Jquery Ajax
```javascript
```
### Fetch
```javascript
let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
let body = {
'account' : account,
'password' : password
}
fetch(url, {
method: 'post',
headers: headers,
body: JSON.stringify(body)
})
.then(response => response.json()) // 取得響應
.then((data) => {
document.getElementById('msg-alert').innerHTML = data.message;
}) // 取得body資料
.catch((error) => {
console.log(error);
});// 出錯時處理
```
### XmlHttpRequest
```javascript
let xhr = new XMLHttpRequest();
xhr.open('post', url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("X-CSRF-TOKEN", document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
xhr.send(JSON.stringify(body));
xhr.onload = function() {
// do after get reponse
let data = JSON.parse(this.responseText);
};
```
## Call By Value/Rference/Sharing
>[color=green] 1. **https://ithelp.ithome.com.tw/articles/10275866**
> 2. **https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/**
## Array.forEach 中斷
>[color=green] **https://dotblogs.com.tw/supershowwei/2020/10/19/094424**
## 檔案分割上傳
>[color=green] **https://dotblogs.com.tw/Bruse_Note_Everything/2018/01/19/155625**
1. **將檔案分割成塊**
2. **將各個塊個別上傳**
3. **clien 端每個塊上傳完成後記數,最後一個上傳完成後再 call 處理的 url**
```javascript
// 分割檔案以上傳
let file = csvFile.files[0];
let chunkSize = 6000000; // 字節,約5MB
let count = 0;
let num = 0; // 第 N 個塊
let total = (file.size / chunkSize).toFixed(0); // 總分割塊數
if( file.size % chunkSize !== 0 ) total++; // 最後一塊未滿
for (let start = 0; start <= file.size; start += chunkSize) {
let chunk = file.slice(start, start + chunkSize + 1); // 切塊
// 發送json & file
let formData = new FormData();
formData.append('file', chunk); // 檔案塊
fetch('upload', {
method: 'post',
headers: headers,
body: formData
})
.then(response => response.json())
.then((data) => {
console.log(data);
if( data.status === 'success' ) count++; // 下載完成的分割檔計數器
// 所有分割檔下載完成call後端處理
if( count === total ) {
let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
};
fetch(`upload_finished`, {
method: 'get',
headers: headers,
})
.then(response => response.json())
.then((data) => {
console.log(data);
})
.catch(error => console.error(error));
}
})
.catch(error => console.error(error));
num++;
}
```
## 迴圈
>[color=green]https://kanboo.github.io/2018/01/30/JS-for-of-forin/
>**非同步搭配:** https://realdennis.medium.com/%E5%93%A5-%E6%88%91%E5%A5%BD%E6%83%B3%E5%9C%A8-array-%E7%9A%84%E9%AB%98%E9%9A%8E%E5%87%BD%E6%95%B8%E5%89%8D%E9%9D%A2-await%E5%96%94-%E8%A9%B2%E6%80%8E%E9%BA%BC%E5%81%9A%E5%91%A2-2d75e07e3adb
- **for...in**
- 輸出的是<span style="color: red">屬性名稱(key)</span>
- **for...of**
- 輸出的是<span style="color: red">值(value)</span>
- 不能迭代物件,需要透過和 Object.keys() 搭配使用
```javascript
let iterable = [3, 5, 7];
// 回傳「key」
for (let i in iterable) {
console.log(i); // "0", "1", "2"
}
// 回傳「value」
for (let i of iterable) {
console.log(i); // 3, 5, 7
}
```
```javascript
// for..of 迭代 物件(object)
var student={
name:'kanboo',
age:16,
locate:{
country:'tw',
city:'taipei',
school:'CCC'
}
}
for(var key of Object.keys(student)){
//使用Object.keys()方法取得物件的Key的陣列
console.log(key+": "+student[key]);
}
// "name: kanboo"
// "age: 16"
// "locate: [object Object]"
```
:::info
迭代<span style="color: red">物件屬性</span>時,使用 **`for...in`**;在迭代<span style="color: red">陣列</span>時,使用 **`for...of`**
:::
## IIFE
>[color=green]https://ithelp.ithome.com.tw/articles/10193313
優點:立即函式的除了可以立即執行程式碼,省略多餘的呼叫,還可以用來避免汙染全域執行環境的東西,減少開發時因相同命名相衝的bug。
ES6+版本盡量使用 **`let`**、**`const`**
**`Functions Expressions`** 函式表示式
```javascript
var hello = function(name){
console.log('Hello ' + name);
};
hello(); // Hello undefined
// 呼叫它
```
目前沒有傳值進去,所以函式印出 `Hello undefined`
若把`hello()`這句刪掉,把程式碼改成這樣:
```javascript
var hello = function(name) {
console.log('Hello ' + name);
}(); // Hello undefined
// 無須呼叫會直接執行
```
電腦在函式表示式後面讀到`()`,就知道要立刻呼叫這個函式,這種立刻執行的函式寫法就稱為 **`IIFE`**
若要傳值進去可以加參數在最後面的`()`
```javascript
var hello = function(name) {
console.log('Hello ' + name);
}('Simon');
```
<h4>另一個例子</h4>
- 一般的函式表示式
```javascript
var hello2 = function(name) {
return 'Hello ' + name
};
console.log(hello2);
```

函式結尾的`}`後面沒有 **`()`** 表示立即執行,所以 **`console.log`** 印出hello2時,是印出hello2 **指向的函式**
- IIFE
```javascript
var hello2 = function(name) {
return 'Hello ' + name
}();
console.log(hello2)
```

函式結尾的`}`後面有 **`()`** 表示立即執行,當 `console.log` 印出hello2時,是印出hello2 **指向函式立即執行的結果值**。
透過變數指向立即執行函式,要呼叫它只要透過 **變數名** hello2就好,不用像一般的Functions Expressions寫成hello2(),那現在如果寫成這樣會發生什麼事呢?
```javascript
var hello2 = function(name) {
return 'Hello ' + name
}('Simon');
console.log(hello2());
```

hello2乍看指向立即執行函式,其實可視為**指向立即執行函式的返回值**
- 不使用變數
```javascript
function(food){
console.log('大俠愛吃' + food)
}('漢堡包');
```
因為沒有變數指向它(不是 **`Functions Expressions`** ),它也沒有名子(不是 **`Function Statement`** ),所以語法解析器認為這段程式寫錯了,自然報錯。
```javascript
// 解決方法(兩者皆可)
// 1.
(function(food) {
console.log('大俠愛吃' + food)
}('漢堡包'));
// 2.
(function(food) {
console.log('大俠愛吃' + food)
})('漢堡包');
```
- 區域變數
ES5版本的JS有全域執行環境(作用域)、函式執行環境(作用域)兩種,直到ES6版才出現塊級作用域,在ES6出來前,為了避免設定太多的全域變數,開發者往往會將變數設定在函式中,使其成為區域變數,尤其是設定在IIFE中,確保不會汙染到全域環境的變數。
```javascript
var seafood = '波士頓龍蝦';
(function(name) {
var seafood = '北海道帝王蟹';
console.log(name + '好~想吃' + seafood);
}('Simon'));
```

因為執行環境的不同,'北海道帝王蟹'只存在IIFE裡,不會影響到外部環境的變數。我們可以將程式碼包在被()包住的IIFE裡,當IIFE和全域環境有宣告相同變數名稱時,IIFE不會蓋掉全域的變數。
若全域和IIFE內都有重複的變數名,那這些框架、套件該如何取用全域的變數呢?
```javascript
var food = '水牛城雞翅';
console.log(food);
(function(global) { // 代入外部變數
var food = '雞塊';
global.food = '麥脆雞'; // 改變外部變數
console.log(food); // 印出當前變數
})(window);
console.log(food);
```

為避免無意義的外部查找,將window當參數傳入,使其成為這個IIFE的區域物件,確保在IIFE內的程式能故意取用到全域的特定變數。
## 同步/異步
### callback
透過 ajax function 去回傳值,可直接把異步關掉 (async: false),但比較不建議,通常利用**回調方式 (callback)**
ajax function
```javascript
//取得所有資料表
function getTableName(callback)
{
var tables;
$.ajax({
type: "GET",
url: "url",
dataType: "json",
//async: false,
success: function(result) {
callback(result);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert('error');
},
beforeSend: function() {
},
complete: function() {
},
});
return tables
}
```
main function
```javascript
//資料表導覽列
function setTableMenu() {
var tables;
getTableName(function(tables){
//code...
});
}
```
### promise
```javascript
// data = [{ status: string, message: string | data: [Object] }]
function call_backend() {
return new Promise(function(resolve, reject) {
fetch('tables_name', {
method : 'get',
})
.then(response => response.json())
.then((data) => {
if(data.status === 'success') {
resolve(data.data);
} else {
reject(data.message);
}
})
.catch(error => console.error(error));
});
}
```
main function
```javascript
call_backend().then((data) => {
// do something...
let d = data;
})
.catch((error) => {
// 僅在 then 出錯才會執行;function 內的錯誤會在 function 內就 catch
});
```
### async/await
```javascript
async function getTotal(data) {
const a = await getA(data);
const b = await getB(a);
const c = await getC(a, b);
return(a + b + c);
}
async function getA(data) {
return await fetch("https://example.com.tw/a", {
method: 'POST',
"headers": {
"accept": "application/json",
},
body: JSON.stringify({
key : data,
})
})
.then((response) => response.json())
.then((res) => {
return res.a;
})
}
async function getB(a) {
return await fetch("https://example.com.tw/b?a=a", {
method: "GET",
})
.then((response) => response.json())
.then((res) => {
return res.b
});
}
function getC(a, b) {
return c = a + b;
}
```
main function
```javascript
getTotal(1);
```
## 解構賦值
>[color=green]https://zh.javascript.info/destructuring-assignment
"解構"不代表"破壞"
這種語法稱為**解構賦值**,是因為它**拆開**了數組或對象,將其中的各元素複製給一些變數。 原來的陣列或物件本身沒有被修改。 換句話說解構賦值只是寫起來簡潔一點。
### 數組解構
```javascript
// 解构赋值
let arr = ["John", "Smith"]
let [firstName, surname] = arr; // 等價於 firstName = arr[0]; surname = arr[1];
alert(firstName); // John
alert(surname); // Smith
//=====================================================
// 忽略使用逗號的元素
// 不需要第二个元素
let [firstName, , title] = ["Julius", "Caesar", "Consul", "Roman Republic"];
alert( title ); // Consul
//=====================================================
// 可以是任何可迭代對象
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
//=====================================================
//交換變量值
let guest = "Jane";
let admin = "Pete";
[guest, admin] = [admin, guest]; // 使 guest = Pete,admin = Jane
//=====================================================
```
- **其餘值**
通常,如果數組比左邊的列表長,那麼"其餘"的數組項會被省略。
例如,這裡只取了兩項,其餘的就被忽略了:
```javascript
let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// 其余数组项未被分配到任何地方
```
如果我們還想收集其餘的陣列項 —— 我們可以使用 **`...`** 來再加一個參數以取得其餘陣列項
```javascript
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// rest 是包含从第三项开始的其余数组项的数组
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
```
- **預設值**
如果數組比左邊的變數列表短,這裡不會出現報錯。 缺少對應值的變數都會被賦 **`undefined`**:
```javascript
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
```
如果我們想要一個「預設」值給未賦值的變量,我們可以使用 **`=`** 來提供:
```javascript
// 默认值
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius(来自数组的值)
alert(surname); // Anonymous(默认值被使用了)
```
### 對象解構
```javascript
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
// 变量的顺序并不重要,下面这个代码也是等价的:
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
```
等號左側的模式(pattern)可以更加複雜,指定屬性和變數之間的映射關係。
如果我們想把一個屬性賦值給另一個名字的變量,例如把 `options.width` 屬性賦值給名為 `w` 的變量,那麼我們可以使用冒號來設定變數名稱:
```javascript
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
//====================================================
// 将冒号和等号结合起来:
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
```
- **其餘參數**
```javascript
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = 名为 title 的属性
// rest = 存有剩余属性的对象
let {title, ...rest} = options;
// 现在 title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
```
:::danger
:bulb: 不使用 **`let`** 时的陷阱
```javascript
let title, width, height;
// 这一行发生了错误
{title, width, height} = {title: "Menu", width: 200, height: 100};
```
JavaScript 的 **`{...}`** 可以被解析為對象,因此需要把整個賦值表達式用括號 **`(...)`** 包起來:
```javascript
let title, width, height;
// 现在就可以了
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
```
:::
### 嵌套解構
```javascript
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// 为了清晰起见,解构赋值语句被写成多行的形式
let {
size: { // 把 size 赋值到这里
width,
height
},
items: [item1, item2], // 把 items 赋值到这里
title = "Menu" // 在对象中不存在(使用默认值)
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
```
### 智慧函數參數
可以用一個物件來傳遞所有參數,而函數負責把這個物件解構成各個參數:
```javascript
// 我们传递一个对象给函数
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// ……然后函数马上把对象解构成变量
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – 提取于 options,
// width, height – 使用默认值
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
```
```javascript
function({
incomingProperty: varName = defaultValue
...
})
```
對於參數對象,屬性 `incomingProperty` 對應的變數是 `varName`,預設值是 `defaultValue`。
請注意,這種解構假定了 `showMenu()` 函數確實存在參數。如果我們想要讓所有的參數都使用預設值,那我們應該傳遞一個空物件
```javascript
showMenu({}); // 所有值都取默认值
showMenu(); // 这样会导致错误 =>
// 解決方式:賦予整個物件預設值,整個參數物件的預設是"{}",因此總是會有內容可以用來解構。
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200
```
### 總結
- 解構數組
屬性 `prop` 會被賦值給變數 `varName`,如果沒有這個屬性的話,就會使用預設值 `default`。
沒有對應映射的物件屬性會被複製到 `rest` 物件。
```javascript
let [item1 = default, item2, ...rest] = [] // array
```
- 解構物件
數組的第一個元素被賦值給 `item1`,第二個元素被賦值給 `item2`,剩下的所有元素被複製到另一個數組 `rest`。
```javascript
let {prop : varName = default, ...rest} = {} // object
```
- 解構賦值可以簡潔地將一個物件或陣列拆開賦值到多個變數上。
- 從嵌套數組/物件中提取資料也是可以的,此時等號左側必須和等號右側有**相同**的結構。
## Module
### import/export
- 一個模組只能有一個 **`default`**, **`import`** 時不可包含在 **`{}`** 內
```javascript
// export.js
function a() {}
function b() {}
function c() {}
export { a as default, b, c };
// import.js
import a, { b, c } from "/export.js"
```
## 動態 function name
以下假設 html 有個元素為 `<div data-function="do"></div>`
- 全域
```js
div.addEventListener("click", (event) => {
let f = event.target.dataset.function;
window[f](); // 字串轉 function name 並執行
});
let f = event.target.dataset.function
window[f].functionMapping[f]()
```
- 非全域 (function 須做一個映射對象,參見 https://hackmd.io/0Exzae9BRGuWFwK0IxS8RQ#%E6%B3%A8%E5%85%A5-js)
```js
div.addEventListener("click", (event) => {
let f = event.target.dataset.function;
functionMapping[f]();
});
```
## <script> 屬性
