# Javascipt Note ###### tags: `Javascript` ## 變數、常數、資料 ```javascript= //單行註解 /* 多行註解 * 多行註解*/ //Variant, Constant var camelCase//命名慣例 //undefined vs not define var hi console.log(hi)//undefined console.log(hello)//not defined //JS Vaiable Hoisting變數提昇 (var & let都有,var範圍大一些) var v1 let v2 //推薦 //const為常數,一開始就必須指定value const age = 13 const array = [1,2,3] array[0] = 5 //這是合法的,矩陣起始指標沒有re-assign // Data Type //原始型別(Primitive Type) & 物件型別(Object Type) //原始型別:Number, String, Boolean, Null, Undefined, Symbol //物件型別:Array, Function, Object console.log( typeof "123" ) let a = 1 a++ ++a let name1 = "name" let name2 = `123` name1 = String(123) //轉換成String a = Number("123") //轉換成Number a = Number("word") //NaN 類型也是number NaN === NaN //結果是false // 強制轉型Type Coercion 123+"hello!" //String 123hello console.log([]+[]) console.log([]+{}) console.log({}+[]) console.log({}+{}) ``` **型別判斷** ```javascript typeof true; // 'boolean' typeof 'Kuro'; // 'string' typeof 123; // 'number' typeof NaN; // 'number' typeof { }; // 'object' typeof [ ]; // 'object' typeof undefined; // 'undefined' typeof window.alert; // 'function' typeof null; // 'object' Number.isNaN(variable)//判斷是不是NaN true or false Array.isArray()//判斷是不是array true or false ``` **資料傳遞方式Pass by sharing** - 基本型別類似pass by value - 物件型別類似pass by reference - 例外狀況:當 function 的參數,如 function changeValue(obj){ ... } 中的 obj 被重新賦值的時候,外部變數的內容是不會被影響的。 ## Scope chain - var: funciton scope - let / const: block scope { } ```javascript const a = 1 function f1(){ var a = 100 // shasdowing console.log(a) //100 } function f2(){ console.log(a) //not defined var a = 100 } function f2(){ console.log(a) //can't access! let a = 100 } ``` ```javascript if (true) { var a = 100; //block 關不住var let b = 100; const c = 100; } console.log(a); //100 console.log(b); //can't access! console.log(c); //can't access! ``` **Lexical scope** ```javascript let a = 1; function hey() { let a = 2; hello(); } function hello() { console.log(a); //找尋定義上的a } hey(); // 1 ``` ## 流程控制 ```javascript= //if else var age =20 if(age >= 18){ console.log("adult") } else if(age >= 12 && age < 18){ console.log("teenager") } else{ console.log("child") } //switch let gender = "male" switch(gender){ case "male": console.log("male") break case "female": console.log("female") break default: break } //判定成false的狀況:0, undefined, null, 空字串, NaN(不包含[] ) // and: && or: || not: ! // let yourInput = prompt("Please enter a number") //Loop for(var i = 0; i < 5; i++){} let n = 1 while(n<10){n++} for(let i =5; i > 0; i-=2){ console.log(`Number${i}`) } for(let i =1; i<=5; i++ ){ console.log(" ".repeat(5-i) + "*".repeat(i*2-1)) } ``` ## Function ```javascript= //Anonymous Function const myFunciton = function(){ console.log("myFunciton") } //Function這種寫法放哪都可以 function myFunciton2(){ console.log("myFunciton2") } //Arrow Function const myFunciton3 = ()=>{ console.log("myFunction3") } //Parameter vs Argument //Default Argument function sayHello(u1 = "hi", u2, u3){ console.log(u1, u2, u3) } sayHello(1,2,3) //1 2 3 sayHello(1,2) //1 2 undefined sayHello(1,2,3,4)//1 2 3 //arguments object //function有arguments物件,但arrow function沒有 //Return Value //所有函數都有回傳值,預設是undefined function bmiCalculator(height, weight){ let bmi = weight / (height**2*0.0001) // return Math.round(bmi * 100) / 100 return bmi.toFixed(2) } //function內可以再定義function //裡面的function是不可以被外面取用 //好處:當一個function很龐大,可以使用內部的funciton做簡化與整理,但是那些功能外面的物件又用不到,或者不希望外面的物件去取用 funciton myFunction(){ funciton innerFunction(){ } ... } ``` ## 陣列、物件 ```javascript= //Array裡面什麼都可以放 let array = [1 ,"2" ,undefined, function(){}] array.length console.log( array[3] ) array[array.length-1]//拿取最後一個元素 array.push("E")//最後面放入 const last = array.pop()//最後面抽出 array.unshift("newFirst")//最前面放入 const first = array.shift()//最前面抽出 //修改元素 array[0] = 0 array.splice(0,2)//第0個元素開始,刪掉2個元素 array.splice(1,2,"x")//刪掉後放入x array.splice(1,0,"X")//插入X //組合Array let H1 = ["A", "B", "C"] let H2 = ["D", "E", "F"]//[ 'A', 'B', 'C', 'D', 'E', 'F' ] let H1H2 = H1.concat(H2) //陣列有沒有某個元素 let array3 = [1, 2, 3] array3.indexOf(2)//1 array3.includes(3)//true //forEach遍歷每個元素(使用callback function) array3.forEach(function(i){ console.log(i) }) myArray.forEach((element, index, array)=>{}) //find尋找符合條件的第一個元素 const foundNumber = array3.find((i)=>{ return i==3 }) //map對每個元素做某件事情,回傳新的陣列 const newArray = array3.map((i)=>{ return i*100 }) const myArray = myArray.map((element, index, array) => {}) //filter找到某個符合條件的元素 let result = array3.filter((i)=>{ return i>=3 }) //reduce妹回合傳進「累加值」和「目前這個元素」 //回傳值變成下一回的「累加值」 let scores = [87,65,92,75,98,100] const totalScore = scores.reduce((acc,current)=>{ return acc + current }, 0)//0是給予的初始值 reduce((accumulator, currentValue, currentIndex, array) => { /* … */ }, initialValue) //Reference參考 a = [1,2,3] b = a //Object物件 = 屬性+行為 ({key: value}) let blackCat = { name: "biubiu", age: 3, attack: function(){ console.log("Mio~~~") } } console.log(blackCat.name) console.log(blackCat["name"]) blackCat.attack() blackCat.color = "Black"//增加屬性 delete blackCat.color//移除屬性 ``` ## DOM ```javascript= //抓取HTML元素 const d1 = document.getElementById("doll-1") const d2 = document.querySelector("#doll-1") const d3 = document.getElementsByClassName("doll")//d3是集合,HTML Collection const d4 = document.querySelectorAll(".doll")//NodeList //抓取後進行操作 d1.textContent = "修改其中的文字內容" d2.innerHTML = "<h1>新增一個標籤</h1>" d3.style.color = "red" ``` ## 事件 Event ```javascript= addEventListener(type, listener) removeEventListener(type, listener) // 當整個HTML頁面載入完成後 // 寫法1:在html中defer <script src="app.js" defer></script> // 寫法2:在css中的一開始等待 document.addEventListener("DOMContentLoaded",()=>{ // 監聽click事件,當發生的時候就... btn.addEventListener("click",funciton(){...}) }) //callback function也可以寫在外面,但是放在這裡時不能加() btn.addEventListener("click",clickHandler) //比較不推薦的寫法 //1. 寫在HTML中 <btn onclick="...">...</btn> //2. onclick寫法,此寫法無法疊加事件 btn.onclick = function(){...} // 有些HTML物件的“預設行為”可以被擋下來 link.addEventListener("click",(e)=>{ e.preventDefault() }) //e.target vs e.currentTarget e.target //觸發事件的物件 e.currentTarget //監聽事件註冊的物件 ``` **Event flow** - 事件冒泡 (Event Bubbling) - 事件捕獲 (Event Capturing) 兩種機制皆會執行 綁定事件在哪個階段上:`addEventListener(type, listener, useCapture)`, `useCapture`預設為false(冒泡) -![](https://i.imgur.com/ELPopDe.png) **事件指派 Event Delegation** 事件指派是利用「事件流程」以及「單一事件監聽器」來處理多個事件目標。 一般要使用迴圈等的方法針對新生成的多個事件指派監聽事件,不但麻煩,還有可能會發生重複監聽 or 忘記移除監聽事件等的狀況,此時使用event delegation就能簡單的只在一個物件上指派監聽事件,僅要針對event.taget做進一步的判斷即可。 ```javascript // 取得容器 var myList = document.getElementById('myList'); // 改讓外層 myList 來監聽 click 事件 myList.addEventListener('click', function(e){ // 判斷目標元素若是 li 則執行 console.log if( e.target.tagName.toLowerCase() === 'li' ){ console.log(e.target.textContent); } }, false); ``` ## ES6 ```javascript= console.log(`hello! my name is ${name}`) // 箭頭函式終極簡化版 const addNumber = (a,b) => a + b // 物件極簡寫,當key跟變數名稱相同時 let name = "hsuan" let age = 25 let cat = { name, age, } // 物件解構 const hero ={ name: "Ironman", age: 40, location: "Earth", } const { name, age } = hero // name可以換個名字username const { age, name: username } = hero; // 函式內的物件解構 function printUser({name, age}){} // 點點點 ... // 1. 展開 spread syntax array1 = ["a", "b", "c"] array2 = ["A", "B", "C"] const newArray = [...array1, ...array2] // 2. 剩下的我全收了 rest parameter const [a, ...bc] = newArray function sayHello(user, ...others){} sayHello("Hsuan", "Betty", "Leo")//Betty, Leo收集成一個array了 // Optional chaining operator const adventurer = { name: 'Alice', cat: {name: 'Dinah'} }; const dogName = adventurer.dog?.name; console.log(dogName);// Expected output: undefined ``` ## 更多DOM操作 ```javascript= // 新增DOM元素 const h = document.createElement("h1") h.textContent = "I am h1" d.appendChild(h) //移除DOM元素 const child = document.querySelector("ul:last-child") if(child){ ul.removeChild(child) //or child.remove() } //取得上層Element console.log(d.parentElement)//Element console.log(d.parentNode)//Node //取得子層Element console.log(d.children)//Element console.log(d.childNodes)//Node //取得兄弟層Element //Element console.log(li.previousElementSibling) console.log(li.nextElementSibling) //Node console.log(li.previousSibling) console.log(li.nextSibling) //在指定位置安插DOM const ul = document.querySelector("ul") const li = document.createElement("li") li.textContent = "x" ul.insertAdjacentElement("beforebegin",li) ul.insertAdjacentElement("afterbegin",li) ul.insertAdjacentElement("beforeEnd",li) ul.insertAdjacentElement("afterEnd",li) //or const li = "<li>Z</li>" ul.insertAdjacentHTML("...",li) ``` ![](https://i.imgur.com/vivh0mU.png) ### Node 和 Element的差別 - Node: 泛指所有DOM裡面的所有Object - Element:特殊類型的Node,有較為常見的HTML標籤 ## 抓取網路資料介紹 ### REST = Representational state transfer - GET - POST - PUT - PATCH - DELETE HTML表單僅支援GET, POST ### 資料傳輸格式 - XML - CSV - JSON = JavaScript Object Notation __AJAX__ = __Asynchronous Javascript and XML__(非同步的Javascipt and XML技術) AJAX為幾種技術的綜合體 同步 vs 非同步 JavaScript是單執行緒的程式語言(single-threaded)的程式語言 ![](https://i.imgur.com/FdT4mke.png) ```javascript= //使用XMLHttpRequest方式 const api = "https://jsonplaceholder.typicode.com/posts"; const req = new XMLHttpRequest(); req.addEventListener("load", () => { const posts = JSON.parse(req.responseText); posts.forEach((i) => { console.log(i); }); }); req.open("GET", api); req.send(); ``` ```javascript= //使用fetch函數 const api = "https://jsonplaceholder.typicode.com/posts"; //fetch 會回傳promise物件 fetch(api) .then((resp) => { return resp.json(); }) .then((data) => { data.forEach((i) => { console.log(i); }); }); ``` ```javascript= // 使用async & await的方法 const api = "https://jsonplaceholder.typicode.com/posts"; async function getPost() { const resp = await fetch(api); const req = await resp.json(); req.forEach((i) => { console.log(i); }); } getPost(); console.log("Hello!"); ``` __CORS__ = __Cross-Origin Resource Sharing__ 一種安全機制,會阻擋瀏覽器的JS抓取該網站的資料 ## Event loop ..........依附瀏覽器存在的事件監聽器 分為幾個區域: - Call stack:後進先出(LIFO=Last In, First Out)的執行堆疊 - Web API:瀏覽器提供許多API實現非同步執行,如AJAX, Timeout - Callback Queue:先進先出(FIFO=First In, First Out)的工作佇列,當堆疊沒有執行項目時,將工作內容送至stack中執行 ![](https://i.imgur.com/iz1dzmh.png) ## JQuery 這邊僅列舉簡單的JQuery使用方法 ```javascript= $().ready(() => { const div = $("#JS101"); div.html("hello!!!!!!"); }); //ajax const url = "https://jsonplaceholder.typicode.com/posts"; $.ajax({ url }).done((response) => { console.log(response); }); ``` ```javascript= // Ubike api示範碼 $().ready(() => { const api = "https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json" const keyword = $("#searchKeyword") const form = $("#searchForm") const siteList = $(".siteList") form.submit((e) => { e.preventDefault() siteList.html("") const query = keyword.val() if (!query) return $.ajax({ url: api }).done((sites) => { sites = sites.filter((site) => { return site.ar.includes(query) }) sites.forEach((element) => { const li = `<li class="list-group-item fs-5"> <i class="fas fa-bicycle"></i> ${element.sna.replace("YouBike2.0_", "")} (${element.sbi})<br> <small class="text-muted">${element.ar}</small> </li>` siteList.append(li) }) }) }) }) ``` ## 物件導向程式設計(Object-oriented programming, OOP) JS本身沒有class的設計,而是使用Prototype chain 原型鏈來實現物件導向的功能,雖然有提供`class`,但也只是語法糖衣,背後的運作機制仍是Prototype chain。 **`proto` & `prototype`:** 1. 所有物件都有`__proto__`屬性,裡面放一堆有多個共用function 2. 所有的function都有`prototype`屬性 2-1. 新寫出的function,預設的`prototype`是`{}` 3. ```javascript function makeHero(name, power) { // 1. this -> { } // 2. { }.__proto__ -> 生它的 function 的 prototype = { } this.name = name; this.power = power; // 3. return { } } makeHero.prototype.action = function () { console.log("go in function"); }; const h1 = new makeHero("kk", 200); console.log(h1); ``` ```javascript class car { #engine = "v8"; //private私有變數 constructor(name, color) { this.name = name; this.color = color; } speedUp(second) { console.log(`speedup for ${second}`); } getEngineType() { console.log(this.#engine); } } let myCar = new car("toyota", "blue"); console.log(`my car name: ${myCar.name}, color: ${myCar.color}`); myCar.speedUp(100); ``` ## This 1. 誰呼叫,誰就是this 2. 沒人呼叫,this -> 全域物件(window/global) 3. 有沒有使用`new` 4. 有沒有使用`()=>{}` 它沒有自己的this 5. 有沒有 `call, apply, bind`綁架this? 6. 是否使用嚴格模式(strict mode) `"use strict"`,將影響第二條的狀況,寫在文件最上方或是寫在部分function內 this應用場景example: ```javascript const hero = { hp: 100, mp: 30, attack: function () { console.log("ATTACK!!!!"); }, }; const mage = { hp: 50, mp: 80, attack: function () { console.log("attack~~~~"); }, heal: function () { this.hp = this.hp + 30; }, }; mage.heal();//幫自己補血 mage.heal.apply(hero);//幫英雄補血 ``` ## 模組化 - 瀏覽器中運行模組化,要在html的`script`標籤中加上`type="module"`,也就是 `<script src="app.js" type="module"></script>`,這樣除了有支援import/export 的效果之外,同時它也會有 defer 效果。 - export方式分為兩種: 1. named export具名匯出:可匯獨立的物件、變數、函式等等,匯出前必須給予特定名稱,而匯入時也必須使用相同的名稱。另外,一個檔案中可以有多個 named export。 2. default export預設匯出:一個檔案僅能有唯一的 default export,而此類型不需要給予名稱。 - import後,嚴格模式預設是打開的 Named export: ```javascript 方法1: const b = 2; const obj1 = { name: 'obj1' }; export { b, obj1}; 方法2: export function myFun(){...} export let a =3 ``` Default export: ```javascript 方法1: export default function myFun(){...} 方法2: function myFun(){...} export default myFun; ``` Import: ```javascript //Named import {obj1, b} from "./lib/xx.js" import {obj1 as myObj}from "./lib/xx.js" //換名字 console.log(myObj) import * as my_module "./lib/xx.js" //一次性匯入,並且給定總名稱 my_module.obj1; my_module.b; //Default import hello from "./lib/xx.js" //import default名字可以直接換 ``` ## 前後端溝通方法 開發網站時,後端語言(ex:Ruby)是不能直接與前端的JS溝通,因為彼此是兩種不同的程式語言,但若要傳遞訊息,便要透過HTML做為資訊傳遞的中介。 常用方法: 自定義屬性,並且該屬性有data做開頭,再使用JS dataset抓取data開頭的屬性。 ```htmlembedded <div id="total-price" data-a="123" data-b="456" data-price="1000"> $NT 1,000 </div> ``` ```javascript const price = document.querySelector("#total-price"); console.log(price.dataset); console.log(price.dataset.price) ``` ## closure 閉包 當funciton中取用的變數在未來會被released時,javascript程式會自動地將該變數保留,以此達成取用其定義範圍外的變數的效果。 ```javascript function a() { var i = 1; return function () { // Closure 閉包 console.log(i); }; } const outer_function = a(); let i = 2; outer_function(); // 1 ``` ```javascript function createCounter() { let count = 0; return function() { count++; console.log(count); }; } const counter = createCounter(); counter(); // 输出 1 counter(); // 输出 2 counter(); // 输出 3 ``` ## IIFE IIFE = Immediately Invoked Funtion Express 宣告的函式會直接使用,外部的程式無法使用該函式,也不會被其內部的變數所影響,因此使用IIFE時可以在裡面放心大膽地宣告變數,而不需擔心污染外部範圍,此技術大型專案常用到。 ```javascript for (var i = 0; i < 3; i++) { (function (n) { setTimeout(() => { console.log(n); }, 0); })(i); } ``` Higher-Order Function, HOF 1. 可以把別的funciton當參數/引數來傳 2. 會回傳一個function ## 安裝第三方套件 third-party `yarn init`or`npm init`產生儲存套件版本資訊的package.json。 ```bash $ npm init (-y) $ npm install XXXX $ yarn init (-y) $ yarn add XXXX ``` **使用套件時要注意License:** license: "BSD/MIT/ISC" GPL我公開,你也要公開 MIT/BSD隨便用 **載入node_module:** node_module存放下載的套件,由於下載套件的版本資訊會儲存在package.json,所以就算把node_module刪掉也沒關係,可以使用`npm install`or`yarn add`把node_module下載回來。 下載時使用`--save`or`--save-dev`參數可以將將套件下載至devDependencies裡面,這樣套件便不會上線。ex: tailWind、parcel。 ```bash $ npm install axios $ npm install sweetalert $ npm install $ npm --save $ npm install XXXX --save-dev #上線用不到,只有打包會用到 ``` **打包工具parcel** ``` #package.json中 #自定義指令 ""scripts":{ "xxxxx": "npx parcel index.html" } # HTML type = "module" # 執行 $ npm run xxxx localhost:1234 ``` ## 自動化測試 TDD = Test-Driven Development 測試驅動開發 有經驗的工程師寫出測試用程式碼,作為規格書進行開發。 使用如jest的套件做測試。 Framework框架 = 他會執行你寫的程式 Library 函式庫 = 你會執行他寫的程式 ```javascript function adder(n, m) { return n + m; } // 測試,規格 // 寫說明書 test("測試 hello world", () => { expect(adder(2, 3)).toBe(5); }); ``` **3A 原則:Arrange, Act, Assert** ```javascript describe("存錢功能", () => { test("可以存錢", () => { // 3A 原則 // Arrange 安排,無 -> 有 const account = new BankAccount(10); // Act 動作,實際操作 account.deposit(5); // Assert 評估結果 expect(account.balance()).toBe(15); }); }); ``` **重構 Refactor(ing):** 在不影響輸出、測試結果的狀況下,對內部結構進行修改與重組,以求更好的維護性或保有未來的開發彈性。 重構的前提是開發團隊要有寫測試。