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