# 8/21讀書會(JS)
###### tags: `面試考古題` `JavaScript`
## Map
定義:key-value配對的物件,和`object`很相似,但兩者的差別
1. Map中的key值是唯一的,如果`set`到重複的key,舊的value就會被蓋掉
2. Map中的key會根據加入資料的時間而有順序性,但`object`沒有順序
3. `object`的key只能是string或symbols,但`Map`的key則可以是任何值,包含物件、函式、原始型別(primitive type)
4. 要取得`Map`的大小可使用`size`屬性,但`object`則不行
5. 當需要經常增加、刪減屬性時,使用`Map`效能會比`object`好
> ES6 中如果希望「陣列(Array)」的元素不會重複,可以使用 Set;如果是希望物件(Object)的鍵不會重複,則可以使用 Map。
使用語法:
```javascript=
// 建立 Map
let myMap = new Map(); // 建立空的 Map
let myMap = new Map([
[1, 'one'],
[2, 'two'],
]); // 建立 Map 時直接代入內容
let keyString = 'a string',
keyObj = {},
keyFunc = function () {};
// 透過 .set(key, value) 來在 Map 中添加屬性
myMap.set(keyString, 'value associated with string');
myMap.set(keyObj, 'value associated with object');
myMap.set(keyFunc, 'value associated with function');
// 方法
myMap.has(keyString); // true,透過 .has 判斷該 Map 中是否有某一屬性
myMap.size; // 3,透過 .size 來取得 Map 內的屬性數目
myMap.get(keyString); // 使用 .get(key) 可取得屬性的內容
myMap.delete(keyString); // 刪除 Map 中的某個屬性,成功刪除回傳 true,否則 false
myMap.clear(); // 清空整個 Map
myMap.keys() // 取得Map所有keys
myMap.values() // 取得Map所有values
myMap.entires() // 取得Map所有內容
// Map合併
const merged = new Map([...first, ...second]) // 如果key相同,後面的key會蓋掉前面的
// 也可以和陣列合併
const merged = new Map([...first, ...second, [1, 'foo']]);
// 用Map做hash table
const arr = ['apple', 'apple', 'banana', 'banana', 'cat', 'dog', 'fat', 'fat', 'fat', 'fat']
const hashTable = new Map()
arr.forEach(item => {
if (hashTable.has(item)) {
hashTable.set(item, hashTable.get(item) + 1)
} else {
hashTable.set(item, 1)
}
})
```
[**Reference**](https://pjchender.dev/javascript/js-map/)
## Event delegation 事件委派
* 受惠於Event Bubbling而能減少監聽器數目的方法
* 在父層統一處理event handler
* Event Delegation的用法很簡單,就是將事件監聽器附加到按鈕的"父級",並在單擊按鈕時捕獲冒泡事件。
```htmlmixed=
<ul id="list">
<li data-num="1"><em>1</em></li>
<li data-num="2" class="hello"><em>2</em></li>
<li data-num="3"><em>3</em></li>
<li data-num="4"><em>4</em></li>
</ul>
```
```javascript=
list.addEventListener('click', event => {
if (event.target.matches('.hello')) {
....
}
})
```
## Event loop


在js中 web api還分為micro task queue 和 macro task queue
* macro task包含:mouse event, keyboard event, network event, HTML parsing
* micro task包含:DOM, Promise
**執行順序**
* js會先執行同步程式碼,當執行完一個macro task後,會檢查micro task中有沒有東西,如果有的話會見縫插針的把所有micro task都執行完,再回去執行macro task
## Ajax (Asynchronous JavaScript and XML)
定義:非單一的技術,是綜合性的瀏覽器端網頁開發技術
目的:處理非同步的情況,發送非同步請求給後端,後端再回傳必要的資料給前端,網頁不用重新整理,就能即時地透過瀏覽器去跟伺服器溝通,撈出資料
常用的三種資料格式
* HTML
* XML
* JSON
# Scoping
## 定義
變數可以被取用(accessibility)或是被看到(visibility)的有效範圍
## 全域作用域
不在函式或區塊內宣告的變數,可以在任何地方取用變數的範圍
- 在最外層直接宣告變數
```jsx
var name = 'tim' // let, const, var都一樣
function call() {
console.log(name)
}
call()
```
- (not recommend)定義變數時沒有使用宣告-使用'use strict'模式,這樣會跳出錯誤
```jsx
function call() {
name = 'tim'
console.log(window.name)
}
call()
```
## 區域作用域
在特定範圍才可以取用的作用域
### 函式作用域: var
被var宣告的變數,能被取用以及看見的區域為**函式**內。
```jsx
function call() {
var name1 = 'tim'
console.log(name1) // tim
}
call()
console.log(name) // is not defined
```
### 區塊作用域: const, let
被const宣告的變數,能被取用以及看見的區域為**區塊**內
```jsx
{
var name3 = 'varTim'
const name4 = 'constTim'
}
console.log(name3) // varTim
console.log(this) // window
console.log(this.name3) // varTim
console.log(name4) // not defined
```
## Scope chain
向外部環境參考(Reference to Outer Environment)尋找需要的變數的行為
```jsx
var name5 = 'timGlobal'
a()
function a() {
var name5 = 'timInFunca' // All
b()
}
function b() {
console.log(name5)
}
// 問a()會產出什麼結果, is not defined, timGlobal, timInFunca
```
這個結果就是scope chain,就是程式碼在搜尋變數時向外找的行為,不過向外找的環境為什麼不是a函式呢?因為scope chain是依據Lexical Environment,而b函式在執行程式時,他在函式內找不到變數,向外之後就直接是全域了。
### 詞彙環境(Lexical Environment)
記錄著程式碼實際上、物理上在哪個位置,和函式們之間的關聯
# Closure 閉包
簡單的說法是函式與函式內的函式形成的環境,函式記得並存取scope的能力(scope chain)。
```jsx
var a = 3
var baz = foo()
baz() // 2
function c() {
console.log(a)
}
function foo() {
var a = 2
function bar() {
console.log(a)
}
c() // 3
return bar
}
```
- JavaScript 引擎的垃圾回收機制會釋放不再使用的記憶體,但閉包為了保留函式記得和存取其語彙範疇的能力,就會予以保留,不做記憶體回收。因此,bar 仍保留指向 foo 的內層範疇的參考,這個參考就是閉包。
## 閉包的使用
- 隱藏**私有變數**讓變數只能被向外開放的介面接觸-按鈕stateA, stateB→state?
```jsx
function GradeCalculater(ch, en, math) {
let chWeighting = 2
let enWeighting = 2
let mathWeighting = 2
function displayGrade() {
const total = ch * chWeighting + en * enWeighting + math * mathWeighting
const totalWeight = mathWeighting + enWeighting + chWeighting
return console.log(total / totalWeight)
}
function changeWeight(ch, en, math) {
chWeighting = ch
enWeighting = en
mathWeighting = math
}
return {
displayGrade, changeWeight
}
}
const timGrade = GradeCalculater(30, 40, 50)
const benGrade = GradeCalculater(100, 40, 50)
timGrade.displayGrade() // 40
benGrade.displayGrade() // 63.333333333333336
benGrade.changeWeight(2, 1, 1)
benGrade.displayGrade() // 72.5
timGrade.displayGrade() // 40
```
- ES6之後,個別載入的檔案視為模組,引入的檔案以及被引入的模組視作在同一個閉包內
```jsx
// 檔案路徑 ./route/index.js
const express = require('express')
const router = express.Router()
const { authenticated, checkNotRole } = require('../middlewares/auth')
const admin = require('./modules/admin')
router.use('/api/admin', authenticated, checkNotRole('user'), admin)
module.exports = router
```
```jsx
// 檔案路徑 ./app.js
// 等於含有routes,而routes裡面又含有router
const express = require('express')
const app = express()
const routes = require('./routes')
app.use(routes) // app.js檔裡有一個route.js檔還有router
module.exports = app
```
- 效能考量:使用prototype代替閉包
用閉包來模擬並且存取私有變數。用prototype來實作通用方法以及存取通用的私有變數。
```jsx
function GradeCalculater(ch, en, math) {
this.chWeighting = 2
this.enWeighting = 2
this.mathWeighting = 2
this.ch = ch
this.en = en
this.math = math
}
// 不能用箭頭韓式
GradeCalculater.prototype.displayGrade = function() {
const {ch, en, math, mathWeighting, enWeighting, chWeighting} = this
const total = ch * chWeighting + en * enWeighting + math * mathWeighting
const totalWeight = mathWeighting + enWeighting + chWeighting
return console.log(total / totalWeight)
}
GradeCalculater.prototype.changeWeight = (ch, en, math) => {
this.chWeighting = ch
this.enWeighting = en
this.mathWeighting = math
}
const timGrade = new GradeCalculater(30, 40, 50)
const benGrade = new GradeCalculater(100, 40, 50)
timGrade.displayGrade() // 40
benGrade.displayGrade() // 63.333333333333336
Object.entries(timGrade)
benGrade.changeWeight(2, 1, 1)
benGrade.displayGrade() // 72.5
```
```jsx
// 會不會出現錯誤呢? NO:
function handle(message) {
let message2 = message
console.log('1', message);
return function (message = message2) {
console.log('2', message)
}
}
function setAlarm(message, timeout) {
setTimeout(handle(message), timeout);
}
setAlarm("Wake UP!", 2000);
```
IIFE 是閉包嗎?
~~為什麼settimeout裡面函式加上()會立即執行:因為拿到的是return後的值~~
為什麼return的函式沒辦法取用父函式的參數:因為需要scope被宣告
# Event bubbling
當事件發生在DOM tree上的其中一個node時候,會觸發parent→parent的parent...一直到root
## DOM事件三階段:
可以使用event.eventPhase取得
1. CAPTURING_PHASE
2. AT_TARGET
3. BUBBLING_PHASE

[https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)
## 實作

```jsx
// list 的捕獲
$list.addEventListener('click', (e) => {
console.log('list capturing', e.eventPhase);
}, true)
// list 的冒泡
$list.addEventListener('click', (e) => {
console.log('list bubbling', e.eventPhase);
}, false)
// list_item 的捕獲
$list_item.addEventListener('click', (e) => {
console.log('list_item capturing', e.eventPhase);
}, true)
// list_item 的冒泡
$list_item.addEventListener('click', (e) => {
console.log('list_item bubbling', e.eventPhase);
}, false)
// list_item_link 的捕獲
$list_item_link.addEventListener('click', (e) => {
console.log('list_item_link capturing', e.eventPhase);
}, true)
// list_item_link 的冒泡
$list_item_link.addEventListener('click', (e) => {
console.log('list_item_link bubbling', e.eventPhase);
}, false)
// 預期結果?
```
```jsx
// list 的冒泡
$list.addEventListener('click', (e) => {
console.log('list bubbling', e.eventPhase);
}, false)
// list 的捕獲
$list.addEventListener('click', (e) => {
console.log('list capturing', e.eventPhase);
}, true)
// list_item 的冒泡
$list_item.addEventListener('click', (e) => {
console.log('list_item bubbling', e.eventPhase);
}, false)
// list_item 的捕獲
$list_item.addEventListener('click', (e) => {
console.log('list_item capturing', e.eventPhase);
}, true)
// list_item_link 的冒泡
$list_item_link.addEventListener('click', (e) => {
console.log('list_item_link bubbling', e.eventPhase);
}, false)
// list_item_link 的捕獲
$list_item_link.addEventListener('click', (e) => {
console.log('list_item_link capturing', e.eventPhase);
}, true)
// 預期結果?
```
# jQuery
是一個簡化瀏覽器API的JS函式庫,把JS包裝成更白話更迅速的寫法 + 豐富套件。
因為原本的瀏覽器API非常簡略,所以在寫複雜的邏輯時就需要「手動做輪子」將複雜的邏輯封裝成簡單的function再呼叫。
舉例來說起初的瀏覽器腳本只有以下API可以存取node
- document.getElementById()
- document.getElementsByTagName()
- document.getElementsByClassName()
如果你想取用的是ul li之類的要怎麼做呢?
統一了過去IE、瀏覽器們不同規範下造成的開發困擾
## 特性
- HTML 元素選取
- HTML 元素操作
- CSS 操作
- HTML 事件函式
- JavaScript 特效和動畫
- HTML DOM 遍歷和修改
- AJAX
- Utilities
## 弱化的原因
W3C制定了統一規範,以及除了IE外的瀏覽器進話,jQuery最大的對手其實是瀏覽器API的更新,比如目前已經有querySelector以及querySelectorAll等API可以更簡潔的存取node。
## 劣勢
也因為他是用過去的瀏覽器API搭配JS,他較慢、較龐大。
- 控管難度高:沒有框架的介入、限制,如何提高維護性全看開發者能力
## 優勢
不適合打造類似Gmail、FB等級複雜又講究效能的高端應用,但要快速開發一般企業內部系統,已經足夠,另外學習成本相較其他主流前端框架更是相當低。
## 推薦使用
快速開發:vue.js MVVM搭配jQuery
# DOM
* 全名:Document Object Model
* 定義:是一個將 HTML 文件以樹狀的結構來表示的模型,而組合起來的樹狀圖,我們稱之為「DOM Tree」,是給 HTML & XML 文件使用的一組 API ,本質是建立網頁與 Script 或程式語言溝通的橋樑。
* DOM Tree 共包含4個類型節點
* 文件節點 `document`: HTML 檔的開端,所有的一切都會從 Document 開始往下進行。
* 標籤元件節點 `Element`:文件內的各個標籤,因此像是`<div>、<p>` 等等各種 HTML Tag 都是被歸類在 Element 裡面。
* 屬性節點 `attribute`:Attribute 就是指各個標籤內的相關屬性
* 文字節點 `text`:被各個標籤包起來的文字,舉例來說在 `<h1>Hello World</h1>` 中, Hello World 被 `<h1>` 這個 Element 包起來,因此 Hello World 就是此 Element 的 Text
* DOM API:JavaScript 可以藉由DOM API去存取並改變HTML架構、樣式和內容的方法。
* 範例說明,以下程式碼轉變成的DOM Tree:
```html
<html>
<head>
<title>example</title>
</head>
<body>
<h1 class="txt">Hello World</h1>
</body>
</html>
```

# Unobtrusive JavaScript 非侵入式JavaScript
* 定義:Unobtrusive Javascript是一種將Javascript從HTML結構抽離的設計概念,避免在HTML標籤中夾雜onchange、onclick Attribute掛載Javascript事件,讓HTML歸HTML、Javascript歸Javascript,功能權責清楚區分,HTML也變得清爽容易閱讀。
```jsx=
<input type="text" name="date" onchange="validateDate()"/>
```
// Unobtrusive JavaScript 會改為這樣
```jsx=
<input type="text" name="date" id="date" />
window.addEventListener("DOMContentLoaded",function(event){
document.getElementById('date').addEventListener("change",validateDate);
});
```
* 其中概念也強調:重要的網站內容應該都要能用純HTML來表示,使用多的JS是為了讓使用者有更好的體驗!
* 可以參考的詳細說明:https://www.w3.org/wiki/The_principles_of_unobtrusive_JavaScript
# prototype / Prototypal inheritance
1. prototype:每一個透過建構式函式產生的物件,都會攜帶一個原型物件。原型物件也有著自己的原型,於是原型物件就這樣鏈結,直到撞見 null 為止:null 在定義裡沒有原型、也是原型鏈(prototype chain)的最後一個鏈結。
2. Prototypal inheritance:**核心目的是「取得另一個物件的屬性與方法」**
JavaScript 的繼承系統會透過原型鍊來繼承原型物件的方法,因此稱為「原型繼承」,「繼承」的物件並不會一併複製功能過來,而是透過原型鍊連接其所繼承的功能。
> 感覺用英文解釋比較順:All JavaScript objects inherit properties and methods from a prototype:
>