# JS 技術筆記
持續更新中
[TOC]
<!--
## Data flow流程圖
- 規劃UX時: 用 [vscode-drawio]( https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)
- 寫流程圖以看懂程式碼時: 用AI把code 用mermaid語法轉成flowchart
-->
<!-- ## 還不熟的面試題
- 作用域鏈(scope chain)

在原本的作用域裡沒有指定的變數,於是繼續往父層尋找,直到找到為止,這就是scope chain
- 什麼是 hoisting?
- hoisting的特性會出現在函式或變數上,讓他們的宣告提升到作用域頂端。在變數提升的特性中,如果在宣告var變數之前使用,會出現undefined,但如果在宣告前使用let和const會因為TDZ的關係報錯。在函式提升的特性中,函式表達式的提升行為會與它宣告變數的提升行為一樣。
什麼是 event loop?
- 事件循環 (Event loop) 的組成
- 事件循環不存在 JavaScript 本身,而是由 JavaScript 的執行環境 (瀏覽器或 Node.js) 來實現的,其中包含幾個概念:
1. 堆 (Heap):堆是一種數據結構,拿來儲存物件
1. 棧 (Stack):採用後進先出(LIFO)的規則,像是洋芋罐的結構,當函式執行時,會被添加到棧的頂部,當執行完成時,就會從頂部移出,直到棧被清空
1. 隊列 (Queue):也是一種數據結構,特性是先進先出 (FIFO),像在餐廳排隊一樣。
1. 事件循環 (Event loop):event loop會不斷地去查看先等stack是否空了,再把queue中等待執行的任務放進stack處理
- [補充閱讀](https://www.explainthis.io/zh-hant/interview-guides/javascript/what-is-event-loop)
哪些JS方法會回傳新的陣列?
map 與 filter 會回傳新的陣列
如果是在 React 等要求寫不可變 (immutable) 的程式碼時,經常需要用到這兩個方法。
範例:
- for...of 和 for...in 的差別?
for...of 遍歷的是陣列中的元素值 (value);而 for...in 是遍歷的是鍵值 (key),換句話說 for... in 是遍歷陣列中的索引 (Index)
解釋解構賦值
- 解構賦值用途是取出單一或部分值,而展開運算子經常是用來淺層複製
- 用在Array
```javascript
let introduction = ["Hello", "I", "am", "Sarah"];
let [greeting, pronoun] = introduction;
console.log(greeting); // "Hello"
console.log(pronoun); // "I"
```
- 用在Object
```javascript!
const obj = { a: 1, b: 2 };
const { a, b } = obj;
console.log(a); // 1
console.log(b); // 2
```
❓challenge
```javascript
let a = 5
let b = a
let c = []
let d = c
function modifyArray(arr) {
arr = {}
}
a = 10
c.push(1)
modifyArray(d)
console.log(a,b,c,d)
```
✨solution
- d=[1] c=[1] b=5 a=10
- 基本型別為call by value而物件則是call by reference
- 所以就算a改變成10,b指向和a不同,不會受影響
- d被pass進去modifyArray裡面,然後重新assign arr變數為空物件,但這不會影響外部的變數d
- 但若在函式裡寫arr.push(2)就會變更變數d和c的值,因為沒有重新賦值造成的指向變更。原本的程式碼中,我們重新賦值一個新物件{}給arr變數,這將arr的指向位址轉向了新的物件。**總之,一旦你選擇重新賦值傳進來的參數,傳進來的參數與原始的資料就完全沒有任何關係了**
如何檢查數組中是否存在某個元素?
可以使用indexOf()方法檢查數組中是否存在某個元素。如果未找到該元素,indexOf() 返回 -1。例如:
let arr = [1, 2, 3];
if (arr.indexOf(2) !== -1) {
console.log('找到元素');
} 別的 {
console.log('未找到元素');
}
在不同情況下 this 指向物件會是什麼?
- this 的值是動態的,通常會由被呼叫的函式來決定它的值
- 最常見的調用方法(物件方法調用)
this 與函式如何宣告沒有關聯性,僅與呼叫方法有關,所以物件的方法調用時,只需要關注是在哪一個物件下呼叫
**範例1**
**範例2**
- 全局範圍下:
在瀏覽器預設中this 指向全局物件 window。
- 函式調用
當一個函式不屬於一個物件中的方法時,直接作為函式來調用時,this 會指向全域物件,在瀏覽器中,默認為 Window
```javascript
var name = 'John';
function callThis() {
console.log(this);
console.log(this.name);
}
callThis();
// Window
// John
```
- 事件處理函式:
在事件處理函式中,this通常指向觸發這個事件的元素。
- 箭頭函式中的 this
箭頭函式的 this 會從他的外在函式繼承,若沒有外在函式就是全域物件window

user 物件的 arrow 方法中。雖然該方法是在物件內部定義的,但是使用了箭頭函式的語法,所以 this 仍然是在箭頭函式被宣告的環境中,即全域環境(global context),瀏覽器默認是window。但是這範例如果用函式宣告式,就能順利輸出user物件的name property
實作real web project challenge練習(能否用在既有網站的物件上?)
什麼是原型鍊(prototype chain)?
- 每一個物件都有原型,物件可以使用原型中的屬性 & 方法,原型鏈的意義就是能讓物件一層一層的依序繼承。
非同步與同步操作混用時的輸出順序
- JS 是屬於同步的程式語言,當遇到非同步事件時,他會先執行所有的同步事件,等所有同步事件處理完畢後才會執行那些非同步事件,JS 的非同步事件有setTimeout或 AJAX 事件等。
Array.forEach、Array.map、Array.filter、Array.reduce 的差別?
- forEach和map都是針對陣列裡每個元素做處理,差別在forEach不會產生新的陣列,map會;filter用來過濾並且留下符合條件的值,reduce是用來從左到右將元素做處理,可以用來算累加的數字或字串
Promise 是什麼
假設需要在確保非同步事件完成後才繼續執行其他程式碼,這時候可以用promise。promise 的特性是他可以通過.then()跟.catch() 語法去處理發送請求成功或失敗結果。finally(),如果有加上 finally,那 Promise 狀態不論是 fulfilled 還是 rejected 都會需要執行 finally 方法。finally 是 Promise 處理流程中一個非常有用的方法,它可以幫助我們在不管 Promise 是否成功的狀態下,執行一定必要的操作。
使用情境例如,一進入頁面要先從伺服器 fetch 資料,等待的同時會顯示 loading 的畫面,而最後不論是否有拿到資料,我們都需要把 loader 關閉。這時候,關閉 loader 的邏輯,就很適合放在 finally 中。如下方程式碼:
```javascript
fetch('https://explainthis.com/data')
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log('close loader');
});
```
它通常會處在三種狀態的其中一種
1. pending 初始狀態,還在等待promise回傳結果
2. fulfilled 表示操作成功,執行resolve函式
3. rejected 表示操作失敗,執行reject函式
async await 要解決的是什麼問題?
目的:他們被用來解決JavaScript中的非同步問題。它們提供了一種比使用callback function更好讀的方式來撰寫非同步程式碼。
原理:async/await 語法的操作原理和promise一樣,所以也被稱作Promise 的語法糖,比起Promise的寫法更像同步操作。用async 關鍵字把函式標記為異步函式,在處理promise時加await
請解釋何謂 callback function?
- 定義:當事件發生時,可以呼叫對應的callback function,做進一步的邏輯處理。用途:回調函數常常用於event handling,例如按鈕點擊、表單提交等。
什麼是事件冒泡(event bubbling)?
當子元素上觸發事件時,事件會沿著DOM tree向上層傳遞,同時觸發父元素上的事件處理函式。我們可以在父元素上統一處理事件,而不需要為每個子元素都單獨寫事件處理函式;相反的,事件捕捉是一種"從外到內"的事件傳遞方式
<!-- 知道 debounce 嗎?
debounce 是用於**控制事件觸發頻率**,通過**延遲觸發事件**來**減少處理次數**。
-->
<!--
## 展開運算子
展開運算子spread(...)
### 使用情境
1. **複製現有陣列**:如果你想要創建一個現有陣列的複製,你可以使用展開運算符將現有陣列的元素展開到一個新陣列中。例如,const newArray = [...oldArray] 將創建一個新陣列,它是運用淺拷貝。
2. **合併陣列**:如果你想要通過合併兩個或更多陣列的元素來創建一個新陣列,你可以使用展開運算符將每個陣列的元素展開到一個新陣列中。例如,const newArray = [...array1, ...array2] 將創建一個新陣列,其中包含 array1 的元素,然後是 array2 的元素。
3. **將元素添加到陣列**:如果你想要將一個或多個元素添加到現有陣列並創建一個新陣列,你可以使用展開運算符將現有陣列的元素展開到一個新陣列中,然後添加新元素。例如,const newArray = [...oldArray, newItem1, newItem2]
-->
## JS Methods
### includes & some
- includes()方法用於檢測陣列中是否包含某個值,這個method接受一個值作為參數,並返回一個boolean
- The some() method tests whether at least one element in the array matches the specific condition. It is **different from the includes method as it requires a condition or a testing function**, **but not a value**. It returns true if the condition is met, otherwise false.
some
方法類似於 every,會測試陣列每一個元素是否通過提供的函式,若其中有一個元素測試值為 true,那就會提前結束,並回傳 true
### Immutable methods that don't modify the original array (they return a new array)
> concat()
> slice()
> join()✨
> toString()✨
### Mutable methods (modify the original array):
> pop()
> shift()✨
> unshift()
> splice()
> fill()✨
[source and example answers](https://www.w3resource.com/javascript-exercises/javascript-functions-exercises.php)
---
### find() vs. filter()
- find only returns the first matching item, filter returns the multiple matching items, both immutable.
### findIndex() vs. indexOf()
- findIndex can find the index of an element in the array **based on a condition provided by a callback function**.
- indexOf can find the index of a specified value in the array.
Challenge: Removing Duplicate Items from a Shopping Cart
Scenario: You are working on an e-commerce website, and you need to implement a feature that removes duplicate items from the user's shopping cart. The cart is represented as an array of objects, where each object contains the item's details like name, price, and quantity. Write a function that removes duplicate items from the cart based on their id. If an item with the same id appears more than once, you should combine their quantities and keep only one entry for that item in the cart.
```javascript
const cart = [
{ id: 1, name: 'Product A', price: 10, quantity: 2 },
{ id: 2, name: 'Product B', price: 20, quantity: 1 },
{ id: 1, name: 'Product A', price: 10, quantity: 3 },
{ id: 3, name: 'Product C', price: 5, quantity: 4 },
];
```
<details>
```javascript!
const cart = [
{ id: 1, name: "Product A", price: 10, quantity: 2 },
{ id: 2, name: "Product B", price: 20, quantity: 1 },
{ id: 1, name: "Product A", price: 10, quantity: 3 },
{ id: 3, name: "Product C", price: 5, quantity: 4 },
];
const removeDuplicates = (cart) => {
const uniqueCart = [];
cart.forEach((item) => {
const existingIndex = uniqueCart.findIndex(
(uniqueItem) => uniqueItem.id === item.id
);
if (existingIndex === -1) {
// 如果不存在相同的id(找到的matching index等於-1),則將新項目添加到uniqueCart陣列中
// shallow copy(different memory address)
uniqueCart.push({ ...item });
// deep copy(same memory address)
// uniqueCart.push(item);
// 若跑uniqueCart[1].id = 5;會同時改變原資料
} else {
// 如果已經存在相同的id,則更新已存在項目的數量
uniqueCart[existingIndex].quantity += item.quantity;
}
});
return uniqueCart;
};
const uniqueCartItems = removeDuplicates(cart);
console.log(uniqueCartItems);
```
</details>
### every()
1. 問題一,假設你正在開發一個購物網站,你需要檢查使用者的購物車中的所有商品是否都有庫存。
```javascript
const isEnoughStock = cartItems.every((cartItem) => {
// 找出和購物車商品對應的庫存
const inventoryItem = inventory.find((item) => item.id === cartItem.id);
// 確認有庫存,且庫存數量大於購物車商品數量
return inventoryItem && inventoryItem.quantity >= cartItem.quantity;
});
```
2. 問題二(進階) Write a JavaScript function that checks whether a passed string is a palindrome (回文) or not? A palindrome is word, phrase, or sequence that reads the same backward as forward
```javascript
const checkParlindrome = (string) => {
// split成一個array
const splitString = string.split("");
const reverseSplitString = string.split("").reverse();
// check 兩個array是否相同
const isSame = splitString.every(
(value, index) => value === reverseSplitString[index]
);
// pass in 兩個 arguments 分別是value和index,判斷value是否等於reverseSplitString裡每個index的值
return isSame;
};
console.log(checkParlindrome("madam"));
```
---
### concat()
The contact() method is used when you want to merge two or more arrays.
```javascript
const basicInfo = [{ name: "Product A", price: 10 }];
const userReviews = [{ rating: 4, comment: "Great Product" }];
const relatedProducts = [
{ name: "Product B", price: 15 },
{ name: "Product C", price: 20 },
];
// add userReviews, relatedProducts to the basicInfo object
const mergeData = basicInfo.concat(userReviews, relatedProducts);
console.log(mergeData);
//result
[
{ name: 'Product A', price: 10 },
{ rating: 4, comment: 'Great Product' },
{ name: 'Product B', price: 15 },
{ name: 'Product C', price: 20 }
]
```
---
### pop()
刪除陣列中最後一個元素,同時返回最後一個元素
const arr = ["a", "b", "c", "d"];
const target = arr.pop();
console.log(target);
### unshift()
新增元素到陣列最前面
```javascript
const arr = ["a", "b", "c", "d"];
arr.unshift("a");
console.log(arr);
```
### map()
Given an array of numbers, double each number and return the resulting array 產生新陣列!
Test Input: [1, 2, 3, 4, 5]
Expected Output: [2, 4, 6, 8, 10]
```javascript
const arr = [1, 2, 3, 4, 5];
const result = arr.map((item) => {
return item * 2;
});
console.log(result);
```
### reduce()

Question: Given an array of numbers, calculate the sum of all the numbers.
Test Input: [1, 2, 3, 4, 5]
Expected Output: 15
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((a, b) => a + b);
console.log(sum);
* 實戰例
```javascript!
const sum = cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
```
在這個示例中,我們使用reduce方法遍歷cartItems陣列,並累積所有商品的總價格。回調函式有兩個參數:total代表累積的總和,而item代表陣列中的當前商品。回調函式會將每個商品的price和quantity屬性相乘,然後將結果加到累積的總和中。
傳遞給reduce方法的第二個參數是累加器的初始值,在這個案例中設為0。這確保如果cartItems陣列是空的,計算結果將為0。
執行此代碼後,變數sum將包含購物車中所有商品的總價格。然後,您可以使用此值來顯示或更新使用者介面的其他部分。
* 實戰2 - 合併不同
```jsx
const response = await fetch(
'https://vue3-course-api.hexschool.io/v2/api/newcart1/admin/products/all',
{
headers: { Authorization: token },
method: 'GET'
}
)
const data = await response.json()
const newCartItems = []
for (const key in data.products) {
newCartItems.push(data.products[key])
}
const combinedCartItems = newCartItems.reduce((acc, item) => {
const existingItem = acc.find((i) => i.title === item.title)
if (existingItem) {
existingItem.quantity += item.quantity
} else {
acc.push({ ...item })
}
return acc
}, [])
console.log(combinedCartItems)
```
### reverse()
Write a JavaScript function that reverses a number.
Sample Data and output:
Example x = 32243;
Expected Output: 34223
```javascript
const x = "32243";
const result = x.split("").reverse().join("");
console.log(result);
```
### slice()
Write a JavaScript function that generates all combinations of a string.
Example string : 'dog'
Expected Output : d,do,dog,o,og,g
```javascript
const string = "dog";
// 從(loop首個字母到尾字母)擷取字母,擷取長度從1到字串length
// 也就是在外部loop第一次,從第一字母開始擷取,內部loop,擷取index+1,index+2,直到string length
for (let i = 0; i < string.length; i++) {
for (let j = i + 1; j <= string.length; j++) {
const sliceString = string.slice(i, j);
console.log(sliceString);
}
}
```
### split()
Write a JavaScript function that returns a string that has letters in alphabetical order.
Example string : 'webmaster'
Expected Output : 'abeemrstw'
Assume punctuation and numbers symbols are not included in the passed string.
```javascript
const string = "dcba";
const getResult = (str) => {
const newString = str
.split("")
.sort((a, b) => a.localeCompare(b))
.join("");
return newString;
};
console.log(getResult(string));
```
### charAt()
Write a JavaScript function that accepts a string as a parameter and converts the first letter of each word into upper case.
Example string : 'the quick brown fox'
Expected Output : 'The Quick Brown Fox '
```javascript
const string = "the quick brown fox";
const capitalizeWords = [];
const capitalize = (str) => {
// 根據空格分割文字
const splitString = str.split(" ");
for (let i = 0; i < splitString.length; i++) {
//取得陣列中每個項目,分成首字母和其餘部分處理,先針對他們的首個字母改寫成大寫,然後加上後續的字母(從index1開始直到字尾)
const upperCaseString =
splitString[i].charAt(0).toUpperCase() + splitString[i].slice(1);
capitalizeWords.push(upperCaseString);
}
};
capitalize(string);
const capitalizeString = capitalizeWords.join(" ");
console.log(capitalizeString);
```
- find
```javascript
// 假設這是從後端獲取的庫存資訊
const inventory = [
{ id: 1, name: "iPhone", stock: 5 },
{ id: 2, name: "Laptop", stock: 2 },
{ id: 3, name: "Headphones", stock: 3 },
];
const cartItems = [
{ id: 1, name: "iPhone", quantity: 2 },
{ id: 2, name: "Laptop", quantity: 1 },
{ id: 3, name: "Headphones", quantity: 4 },
];
for (cartItem of cartItems) {
// 使用find 方法取得對應id的商品物件,然後比較兩物件的數量,print出
const matchInventoryItem = inventory.find((item) => item.id === cartItem.id);
if (matchInventoryItem.stock > cartItem.quantity) {
console.log(cartItem.name + " enough stock");
} else {
console.log(cartItem.name + " not enough stock");
}
}
```
### split()
Write a JavaScript function that accepts a string as a parameter and finds the longest word within the string.
Example string : 'Web Development Tutorial'
Expected Output : 'Development'
```javascript
const string = "Web Development Tutorial";
const findLongestWord = () => {
// split
const words = string.split(" ");
// define longest word variable to store value
let longestWord = "";
let longestWordLength = 0;
for (let i = 0; i < words.length; i++) {
const wordLength = words[i].length;
if (wordLength > longestWordLength) {
longestWord = words[i];
longestWordLength = wordLength;
}
}
console.log(longestWord);
};
findLongestWord(string);
```
<!--
### 從過去犯的錯誤中學習
- indexOf 方法是用來在一個普通陣列中找尋指定值的索引,而不是物件陣列。當處理物件陣列時,需要使用 findIndex 方法。
-->
## localStorage使用須知
### 常見應用情境
1. 用戶配置和偏好設置: 例如,用戶的偏好語言、主題選擇、用戶設置等。
2. 快取數據: 將某些靜態或不經常更改的數據保存在本地,以提供更快的加載速度和減少對服務器的請求。
### 總結
localStorage 最適合存儲相對小型、不敏感和不需要高安全性的數據。然而,對於大量數據、敏感數據或需要更高安全性的情況,應該考慮使用其他技術,比如後端數據庫、Cookie、或者其他客戶端存儲解決方案,以確保數據的安全性和完整性。
## 白板題
### 加入購物車
你已經有一個商品列表和一個購物車物件,購物車物件包含用戶添加到購物車中的商品。編寫一個函數,該函數將新的商品添加到購物車中。
```javascript!
const products = [
{ id: 1, name: "iPhone", number: 1, price: 999 },
{ id: 2, name: "iPad", number: 2, price: 699 },
{ id: 3, name: "MacBook", number: 3, price: 1299 },
// ... 更多商品
];
let cart = [];
// 根據商品id加入購物車
const addToCart = (productId) => {
// *** error or exception 處理
// *** bug: 未處理加入undefined productId
// 避免重複加入,用some method而非includes method,因為要使用比較運算子
const isAdded = cart.some((product) => product.id === productId);
const productToAdd = products.find((product) => product.id === productId);
if (productToAdd) {
if (isAdded) {
console.log("不能加入重複商品");
} else {
cart.push(productToAdd);
console.log(productToAdd.name + "已加入購物車");
}
} else {
console.log("找不到該商品。");
}
};
addToCart(1);
addToCart(2);
addToCart(3);
// 刪除商品
const removeFromCart = (id) => {
//使用splice arr.splice(index,removeNum,addItem)
const indexToRemove = cart.findIndex((product) => product.id === id);
if (indexToRemove === -1) {
console.log("product"+id+"is not found")
} else {
cart.splice(indexToRemove, 1);
}
};
removeFromCart(4);
// 加減產品數量
const addQuantity = (id, num) => {
// get cart product with id
const indexToUpdate = cart.findIndex((item) => item.id === id);
cart[indexToUpdate].number += num;
};
const reduceQuantity = (id, num) => {
const indexToUpdate = cart.findIndex((item) => item.id === id);
cart[indexToUpdate].number -= num;
};
addQuantity(2, 10);
reduceQuantity(2, 5);
console.log(cart);
```
### fibonacci找出第幾個數
建立函式 fibonacci 代入參數 position,position 表示的是想要得到 fibonacci sequence 中的第幾個數字的值。
```javascript!
function fibonacci(position) {
if (position <= 0) {
return 0;
} else if (position === 1) {
return 1;
} else {
return fibonacci(position - 1) + fibonacci(position - 2);
}
}
const position = 1;
const result = fibonacci(position);
console.log(`fibonacci第${position}個數字是${result}`);
```
<!-- ### Linked List
資料結構進階題
使用 Linked List 實作 Stack ,實作需包含以下方法。
push() : 添加新元素。 pop():移除元素並返回被移除的元素。 size():返回所有元素數量。 -->
<!--
### 資料整合
將下列輸入資料整合成期望的輸出結果。
輸入資料
```javascript!
const userIds = ['U01', 'U02', 'U03'];
const orderIds = ['T01', 'T02', 'T03', 'T04'];
const userOrders = [
{ userId: 'U01', orderIds: ['T01', 'T02'] },
{ userId: 'U02', orderIds: [] },
{ userId: 'U03', orderIds: ['T03'] },
];
const userData = { 'U01': 'Tom', 'U02': 'Sam', 'U03': 'John' };
const orderData = {
'T01': { name: 'A', price: 499 },
'T02': { name: 'B', price: 599 },
'T03': { name: 'C', price: 699 },
'T04': { name: 'D', price: 799 }
};
```
```javascript!
const result = [
{
user: { id: "U01", name: "Tom" },
orders: [
{ id: "T01", name: "A", price: 499 },
{ id: "T02", name: "B", price: 599 },
],
},
// ... other items
];
```
第一次嘗試(2hr)
```javascript!
const userIds = ["U01", "U02", "U03"];
const userData = { U01: "Tom", U02: "Sam", U03: "John" };
const userOrders = [
{ userId: "U01", orderIds: ["T01", "T02"] },
{ userId: "U02", orderIds: [] },
{ userId: "U03", orderIds: ["T03"] },
];
// 用findIndex,檢查每個orderId有無對應的orderData,若有index,在result裡加入orderData[index]
const orderIds = ["T01", "T02", "T03", "T04"];
const orderData = {
T01: { name: "A", price: 499 },
T02: { name: "B", price: 599 },
T03: { name: "C", price: 699 },
T04: { name: "D", price: 799 },
};
for (let orderKey in orderData) {
const { name, price } = orderData[orderKey];
orderData[orderKey] = { id: orderKey, name, price };
}
// Initialize the result array with empty objects for each user
// const result = [{ user: {}, orders: [] }]; 會造成之後match到的result[index]不存在產生bug
const result = userIds.map(() => {
return { user: {}, orders: [] };
});
// 整理userData
const organizedUserData = userIds.map((id) => {
return {
id: id,
name: userData[id],
};
});
// 結合order,
// const combinedOrders = orderIds.map((orderId) => {
// return {
// id: orderId,
// // take the properties based on key
// ...orderData[orderId],
// };
// });
// console.log(combinedOrders);
const mergeData = () => {
// 把userOrder裡的orderIds取出,透過orderIds取得orderData裡的(多個)物件,將物件依序放進result的orders裡
// 1. match the userOrderId data with the objects in orderData
// 2. put the prev data inside the orders in result
userOrders.forEach((order) => {
const userOrderId = order.orderIds;
const userOrdersData = userOrderId.map((orderId) => orderData[orderId]);
const userIndex = organizedUserData.findIndex(
(user) => user.id === order.userId
);
console.log(userOrdersData);
// Add the matched order data to the corresponding user's orders in result
// result[userIndex].orders = userOrdersData;
// bug, 只會傳入userOrdersData的最後值
// for (let i = 0; i < organizedUserData.length; i++) {
// result[i] = { user: organizedUserData[i], orders: userOrdersData };
// }
// 或者展開陣列裡的每個物件,再push進去
// result[userIndex].orders.push(...userOrdersData);
result[userIndex].orders = userOrdersData;
result[userIndex].user = organizedUserData[userIndex];
});
};
mergeData();
console.log(JSON.stringify(result));
```
```javascript
const userIds = ["U01", "U02", "U03"];
const userData = { U01: "Tom", U02: "Sam", U03: "John" };
const userOrders = [
{ userId: "U01", orderIds: ["T01", "T02"] },
{ userId: "U02", orderIds: [] },
{ userId: "U03", orderIds: ["T03"] },
];
const orderIds = ["T01", "T02", "T03", "T04"];
const orderData = {
T01: { name: "A", price: 499 },
T02: { name: "B", price: 599 },
T03: { name: "C", price: 699 },
T04: { name: "D", price: 799 },
};
// 增加 orderIds to orderData
for (let orderKey in orderData) {
const { name, price } = orderData[orderKey];
orderData[orderKey] = { id: orderKey, name, price };
}
const result = userIds.map(() => {
return { user: {}, orders: [] };
});
// 整理userData
const organizedUserData = userIds.map((id) => {
return {
id: id,
name: userData[id],
};
});
const mergeData = () => {
userOrders.forEach((order) => {
// get order that matches with user order id
const userOrderId = order.orderIds;
const userOrdersData = userOrderId.map((orderId) => orderData[orderId]);
// 訂單資訊要放哪個位置才對應到用戶 (以id做match,userOrders對應organizedUserData哪個index)
const userIndex = organizedUserData.findIndex(
(user) => user.id === order.userId
);
result[userIndex].user = organizedUserData[userIndex];
result[userIndex].orders = userOrdersData;
});
};
mergeData();
console.log(JSON.stringify(result));
```
- ✨重點 spread syntax (...) extracts the properties of the object and creates copies of them,它就像拆盒子一樣把object外殼拆掉,只留裡面的properties
- [如果Object結構複雜可以用這工具幫助理解](https://jsonviewer.stack.hu/)
有沒有優化解法?(把整理user和order的函式結合?)
#### 合併資料
目標是把原始資料中,同樣id的物件裡面的category合併成一起
// 原始資料
```
const data = [
{ id: 1, content: 'hello', category: 'HTML' },
{ id: 1, content: 'hello', category: 'JS' },
{ id: 1, content: 'hello', category: 'CSS' },
{ id: 2, content: 'hi', category: 'CSS' },
]
```
```javascript!
const data = [
{ id: 1, content: "hello", category: "HTML" },
{ id: 1, content: "hello", category: "HTML" },
{ id: 1, content: "hello2", category: "JS" },
{ id: 1, content: "hello3", category: "JS" },
{ id: 1, content: "hello", category: "REACT" },
{ id: 1, content: "hello", category: "CSS" },
{ id: 1, content: "hello", category: "CSS" },
{ id: 1, content: "hello", category: "CSS" },
{ id: 2, content: "hi", category: "CSS" },
];
const removeDuplicates = (data) => {
const uniqueData = [];
data.forEach((item) => {
const existingIndex = uniqueData.findIndex(
(uniqueItem) => uniqueItem.id === item.id
);
if (existingIndex === -1) {
uniqueData.push({ ...item });
} else {
// check if item category(from original data) is already included in uniqueData category, if not, concatenate
if (
!uniqueData[existingIndex].category.split(",").includes(item.category)
) {
uniqueData[existingIndex].category += "," + item.category;
}
if (
!uniqueData[existingIndex].content.split(",").includes(item.content)
) {
uniqueData[existingIndex].content += "," + item.content;
}
}
});
return uniqueData;
};
const uniqueData = removeDuplicates(data);
console.log(uniqueData);
```
* 重點整理
* 給data跑迴圈,判斷裡面是否有和新陣列uniqueData相同的id
* 有則找到該物件(透過index找)
* 判斷是否將加category, content加入其後
-->
### 找出重複的元素
```javascript
const arr = [1, 2, 3, 1];
const duplicate = arr.filter((item, index) => {
return arr.indexOf(item) !== index;
});
console.log(duplicate);
```
### 刪除陣列裡特定項目
```javascript
const newArr = [{ id: 1 }, { id: 3 }];
const oldArr = { id: 3 };
removeItem(oldArr);
function removeItem(oldArr) {
const result = newArr.filter((item) => item.id !== oldArr.id);
console.log(result);
}
```
### 使用javascript繪製星星
```!
for (let i = 1; i <= 5; i++) {
let stars = "";
for (let j = 1; j <= i; j++) {
stars += "★"; // Concatenate stars to the stars variable
}
console.log(stars); // Print stars for each level in the console
}
```
## 同步與非同步的差別
白話的說:
- 同步:按程式寫的順序逐一做事
- 非同步:同一時間可以做多件事
用技術術語說:
- 同步 (sync):程式碼按寫的順序執行,一行接一行,前一行沒完成,下一行不會開始。
- 非同步 (async):程式碼仍然按順序進入事件循環(event loop),但可以先暫停一些操作(例如 setTimeout、Promise),讓 JS 去處理其他同步的程式碼,再回來完成原本的任務。
## 函式宣告式與函式表達式的差別
函式宣告式
```javascript
function add(a,b){
return a+b;
}
```
函式表達式
```javascript
const add = function(a, b) {
return a + b;
};
```
* hoist: 函數宣告式在 JavaScript 中是被 hoisted 的,這意味著它們可以在宣告之前,在整個代碼中使用。另一方面,函數表達式沒有被 hoisted,因此要先宣告之後,才能使用
* 函數表達式對於立即執行函數表達式 (IIFEs) 很有用。

## 閉包Closure
- [補充學習](https://www.explainthis.io/zh-hant/interview-guides/javascript/what-is-closure#%E4%BB%80%E9%BA%BC%E6%98%AF%E9%96%89%E5%8C%85)
- 定義
閉包的概念就是,當一個函數內部定義了另一個函式,即便外部函式執行完畢,內部函式依舊可以訪問外部函式中的變數
```javascript
const outer = () => {
let a = 0;
function inner() {
a += 1;
console.log(a);
}
return inner;
}
const result = outer();
result(); // 1
result(); // 2
result(); // 3
```
- 需注意的點
- memory leak記憶體洩漏,因為閉包會讓內部函式記得外部的變數,如果有太多沒用到的變數,可能會造成變數常駐在記憶體當中,降低運作效能
- 實際用途
- 把以下程式碼改成閉包,避免不小心改到money的值
```javascript
let money = 99;
function add(num) {
money += num;
}
function deduct(num) {
if (num >= 10) {
money -= 10;
}
}
add(1);
deduct(100);
console.log(money);
```
- 解法
```javascript
function createWallet(initMoney) {
var money = initMoney;
return {
add: function(num) {
money += num;
},
deduct: function(num) {
if (num >= 10) {
money -= 10;
} else {
money -= num
}
},
getMoney() {
return money;
}
}
}
var myWallet = createWallet(99);
myWallet.add(1);
myWallet.deduct(100);
console.log(myWallet.getMoney()); // 90
```
## 讀取圖片 轉base64 縮小
```html!
<!DOCTYPE html>
<html>
<head>
<title>Image Resizer</title>
</head>
<body>
<input type="file" onchange="previewFile()" />
<img id="preview" src="" alt="Image preview" />
<script>
function previewFile() {
const file = document.querySelector("input[type=file]").files[0];
const preview = document.querySelector("img#preview");
const reader = new FileReader();
reader.onload = () => {
const img = new Image();
img.src = reader.result;
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// Calculate new dimensions while maintaining the aspect ratio
const [maxWidth, maxHeight] = [1000, 1000];
let { width, height } = img;
if (width > height) {
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
} else {
if (height > maxHeight) {
width *= maxHeight / height;
height = maxHeight;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// Display resized image
preview.src = canvas.toDataURL("image/jpeg");
// Optional: Create a download link for the resized image
<!--
const link = document.createElement("a");
link.href = preview.src;
link.download = "resized_image.jpg";
-->
};
};
if (file) reader.readAsDataURL(file);
}
</script>
</body>
</html>
```
## 時間相關
- 如何把new Date() 轉成 yyyy/mm/dd hh:mm:ss 格式
```js!
const formattedDate = new Date().toLocaleString('zh-TW', {
hour12: false,
})
```
## 輸入驗證
### 基本驗證
- 長度(0~n)
- 型別
- 大小寫
是否允許和其他欄位或資料庫既有值重複
是否排除特殊字符(用正則去除)
限定enum
### 身分證
```js
function verifyId(id) {
id = id.trim();
if (id.length != 10) {
console.log("Fail,長度不正確");
return false
}
let countyCode = id.charCodeAt(0);
if (countyCode < 65 | countyCode > 90) {
console.log("Fail,字首英文代號,縣市不正確");
return false
}
let genderCode = id.charCodeAt(1);
if (genderCode != 49 && genderCode != 50) {
console.log("Fail,性別代碼不正確");
return false
}
let serialCode = id.slice(2)
for (let i in serialCode) {
let c = serialCode.charCodeAt(i);
if (c < 48 | c > 57) {
console.log("Fail,數字區出現非數字字元");
return false
}
}
let conver = "ABCDEFGHJKLMNPQRSTUVXYWZIO"
let weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]
id = String(conver.indexOf(id[0]) + 10) + id.slice(1);
checkSum = 0
for (let i = 0; i < id.length; i++) {
c = parseInt(id[i])
w = weights[i]
checkSum += c * w
}
verification = checkSum % 10 == 0
if (verification) {
console.log("Pass");
} else {
console.log("Fail,檢核碼錯誤");
}
return verification
}
console.log(verifyId("A123456789"));
```
## 效能優化
有哪些能加快網頁讀取速度的方法
1. **將 Javascript 和 CSS 瘦身**:將不必要的程式碼拿掉
2. **外部載入 Javascript 和 CSS**:避免將 Javascript 和 CSS 直接寫在頁面上
3. **使用 CSS Sprites 減少載入圖片載入次數**