# 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 ![](https://i.imgur.com/WK0hHoi.png) ![](https://i.imgur.com/SOOeip8.gif) 在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://s3.us-west-2.amazonaws.com/secure.notion-static.com/409df962-211d-4ea8-a8b8-3bb7962aabd7/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210822%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210822T031244Z&X-Amz-Expires=86400&X-Amz-Signature=298ca19df59e1928cfff92b2ef36e5fc2d1bd07cef6bd8e99122b5d5c5d72896&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22) [https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/) ## 實作 ![Untitled](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e01258a9-1ea8-4b1a-9e22-5976db395f1c/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210822%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210822T031257Z&X-Amz-Expires=86400&X-Amz-Signature=4daef11d729d5e9487cf70a48caea12bb4b9a4fc6d6779066b881c6d784961a0&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22) ```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> ``` ![](https://i.imgur.com/axU6rLY.png) # 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: >