---
# System prepended metadata

title: JavaScript & Jquery
tags: [JavaScript, Jquery]

---

# 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);
```
![](https://hackmd.io/_uploads/rk_i8nAyp.png)
函式結尾的`}`後面沒有 **`()`** 表示立即執行，所以 **`console.log`** 印出hello2時，是印出hello2 **指向的函式**
- IIFE
```javascript
var hello2 = function(name) {
	return 'Hello ' + name
}();
console.log(hello2)
```
![](https://hackmd.io/_uploads/rJYGw3AJa.png)
函式結尾的`}`後面有 **`()`** 表示立即執行，當 `console.log` 印出hello2時，是印出hello2 **指向函式立即執行的結果值**。

透過變數指向立即執行函式，要呼叫它只要透過 **變數名** hello2就好，不用像一般的Functions Expressions寫成hello2()，那現在如果寫成這樣會發生什麼事呢？
```javascript
var hello2 = function(name) {
	return 'Hello ' + name
}('Simon');
console.log(hello2());
```
![](https://hackmd.io/_uploads/Sy1_w2R1T.png)

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'));
```
![](https://hackmd.io/_uploads/B124rACJa.png)
因為執行環境的不同，'北海道帝王蟹'只存在IIFE裡，不會影響到外部環境的變數。我們可以將程式碼包在被()包住的IIFE裡，當IIFE和全域環境有宣告相同變數名稱時，IIFE不會蓋掉全域的變數。

若全域和IIFE內都有重複的變數名，那這些框架、套件該如何取用全域的變數呢？
```javascript
var food = '水牛城雞翅';
console.log(food);
(function(global) { // 代入外部變數
    var food = '雞塊';
	global.food = '麥脆雞'; // 改變外部變數
    console.log(food); // 印出當前變數
})(window);
console.log(food);
```
![](https://hackmd.io/_uploads/Hkq9rACJa.png)
為避免無意義的外部查找，將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> 屬性
![image](https://hackmd.io/_uploads/H1wISpKV6.png)