# Javascript note ## class vs id class相當於筆記軟體裡的`標籤`,可以有很多個,識別代號為`.` id就是`ID`,只能有一個,識別代號為`#` 比較: 這兩者最大的不同,是在於 ID 選擇器在一個 HTML 文件中只能被使用一次,而 Class 選擇器在一個 HTML 文件中可以被使用多次。第二個不同的地方,是 ID 選擇器可以被 Javascript 中的 GetElementByID 函數所運用,而 Class 選擇器無法被 Javascript 運用到。 並沒有什麼固定的規則,來決定什麼時候要用 ID 及什麼時候要用 Class。我們的建議是盡量用 Class,因為這樣子最靈活 (同一個 HTML 文件可以利用這類的選擇器多次)。唯一的例外,是當你要用 Javascript 的 GetElementByID 函數時。在這個情況下,你就應該要用 ID。 Class 名稱及 ID 名稱都是對大小寫敏感的。舉例來說,.classone 及 .ClassOne 是代表兩個不同的 Class 選擇器。 class範例: ```htmlmixed= <p class='abc'> test </p> ``` css: ```css= .abc{ color: #00ff00; } ``` id範例: ```htmlmixed= <p id='footer'> test </p> ``` css: ```css= #footer{ color: #00ff00; } ``` ## 為元素增加/移除 class 利用 `el.classList.add/remove/toggle` 來為元素添加/移除/切換 class: ```javascript const block = document.querySelector('.block') // 為 block 這個元素添加/移除/切換 active 這個 class block.classList.add('active') block.classList.remove('active') block.classList.toggle('active') ``` ## 監聽 transitionend 利用`transitionend`事件,監聽所有 block 上有 transition 的 CSS 屬性,並且透過 `propertyName` 來判斷是哪個 css 屬性: ```javascript const block = document.querySelector('.block') // 監聽所有 block 上有 transition 的 CSS 屬性 block.addEventListener('transitionend', e => { console.log(e.propertyName) }) ``` ## 取得現在的時間 >關於更多 [JavaScript 時間函式](https://www.evernote.com/l/AcVsMUPlMCxLoL5ovXUzbpfymBO2f7O2aBY) ```javascript let now = new Date() let hour = now.getHours() let minute = now.getMinutes() let second = now.getSeconds() ``` ## css 變數 利用 `:root{--<varName>: <varValue>}` 來建立 CSS 的變數,並且在 CSS 屬性值中使用 `var(--<varName>)` 來代入變數的值: ```css /* 建立 css 變數 */ :root { --base: #ffc600; --spacing: 10px; } /* 使用 css 變數 */ img { display:block; box-sizing: border-box; margin: 0 auto; padding: var(--spacing); background: var(--base); } ``` ### 利用 JS 動態改變 CSS 變數的值 我們可以利用 `document.documentElement.style.setProperty('--<varName>', '<varValue>')` 來動態修改 CSS 變數的值,但要記得要**加上單位(例如,px)**: ```javascript document.documentElement.style.setProperty(`--${this.name}`, this.value + this.dataset.unit) ``` ### 取得 data-attribute 我們可以在 html 中利用 data attribute 設值 > 更多關於 [html5 data attribute](https://pjchender.blogspot.tw/2017/01/html-5-data-attribute.html) @ pjchender ```htmlmixed <div class="tooltip" data-content="hello"></div> ``` 在 JS 中我們可以使用 `el.dataset` 來取得 data-attribute 的值 ```javascript const block = document.querySelector('.block') console.log(block.dataset.content) ``` ### 建立正規式 可以利用 `new RegExp(regex, flag)` 來建立正規式: - 第一個參數放的是正規式的內容 - 第二個參數是 `flag` -- `g` 表示 global search,也就是會去找整份文件,而不是找到就停 -- `i` 表示 case insensitive,也就是不去區分大小寫 ```jsx let regex = new RegExp(wordToMatch, 'gi') ``` ### input 事件和 change 事件的差異 這裡我們用 `input` 事件 - `input event` 會在任何元素值改變的時候被出發(例如每打一個字都會觸發一次) - `change event` 則是會在有元素值改變,且該元素脫離 focus 狀態時才觸發 ```jsx searchInput.addEventListener('input', e => { displayMatch(e) }) ``` ### Array.prototype.slice 的使用 在原本的內容中,作者使用的是 `Array.prototype.splice` 這個方法,但 splice 一般來說是在刪除或添加陣列內容時使用,因此個人認為使用單純擷取陣列元素的 `Array.prototype.slice` 應該更為適合(我想作者有它的考量)。 #### 使用 ```jsx arr.slice(begin, end) - 從 begin 開始,不包含 end - 最後一個元素的 index 是 -1 ``` #### 例子 ```jsx /** * arr.slice(begin, end),從 begin 開始,不包含 end **/ let arr = [0,1,2,3,4,5,6,7,8,9,0] arr.slice(0,3) // [0,1,2] arr.slice(3,7) // [3,4,5,6] // 最後一個是 -1 arr.slice(-3, -1) // [8,9] arr.slice(-5,-3) // [6,7] arr.slice(-3,-5) // [] // 如果要擷取到最後一個 arr.slice(-5, arr.length) // [6,7,8,9,0] ``` ### Array.prototype.join 的使用 ```jsx arr.join(separator) // separator 預設是 ',' ``` ### browser視窗 ```jsx window.scrollY // 取的瀏覽器視窗捲軸 Y 的高度(捲軸在最上方時是 0) window.innerHeight // 瀏覽器內視窗的高度 element.offsetTop // 元素距離外層容器上方的距離 element.height // 元素的高度 ``` 這裡有一個要留意的地方是, `offset` 指的就是相對於 `offsetParent` 的距離。一般來說`offsetParent`都會是 `body`,但是如果這個元素的外層有另外設定 `position` 的話,那麼 offsetParent 就可能改變。 **因此,再使用 `offset` 屬性前,盡量先用 `offsetParent` 來查看該元素的父層對應到的是哪個元素。** ### 使用 <form> HTMLFormElement 因為 JS 再操作 DOM 的時候很方便,因此有些時候沒有真的要把表單送出(submit)的話,就都沒有把 `<input>` 外面在包一層 `<form>`。但是使用 `<form>` 包住有幾個好處,一個是我們在監聽的時候可以監聽 `submit` 事件。更重要的是 HTMLFormElement 提供了我們 `reset()` 這個方法,讓我們可以一次把表單的內容全部清空。 但是使用 `<form>` 的話要記得把 button 的 type 設成 submit (`<button type="submit">` );另外,要記得在按鈕點擊時要 `preventDefault()` ,否則會真的把表單送出(`<form>` 沒有設定`location` 的話變成重新整理)。 另外,通常一個 form 裡面只會有一個 submit button ,如果你有多個 button 要做不同功能的話,form + submit 的方式或許就不適合。 ```jsx /** * STEP1: 添加 item 項目 * 這裡監聽的是 submit 事件,好處是當使用者按 enter 也會觸發 **/ addItems.addEventListener('submit', addItem) function addItem (e) { e.preventDefault() // 使用 preventDefault 避免真的 submit let text = document.querySelector('[name="item"]').value let item = { text, done: false } plateItems.push(item) /** * HTMLFormElement 可以使用 reset() 方法來清空表單 **/ e.target.reset() } ``` ### HTML的checkbox ```htmlmixed <!-- 只有出現 checked attribute 則 checkbox 都會被勾選 --> <input type="checkbox" checked> <input type="checkbox" checked="false"> <!-- 一樣會勾選 --> ``` ## this 和 e.target在 eventHandler 中的差異 在 `element.addEventListener` 中使用 `this` 和 `e.target` 的差異: - `this` 總是會拿到被監聽的對象本身,也就是 element.addEventListener(`<eventHandler>`) 的 element。 - `e.target` 則是指事件被觸發時的對象,有可能不是 element 本身。 ```jsx const hero = document.querySelector('.hero') hero.addEventListener('mousemove', moveShadow) function moveShadow (e) { console.log('this', this) // 回傳的一定是 ".hero" console.log('e', e.target) // 回傳的可能是 ".hero" 也可能是 "h1" } ``` ## 陣列的解構賦值(Object Destructing) ![img](https://4.bp.blogspot.com/-KN5lYhUEW_Q/WVR2qyEE6nI/AAAAAAAA0JI/K32KxhvNatIJiTHQitu-q2qCpR0gbouWgCLcBGAs/s1600/object.jpg) ```jsx /** * 等同於 * const heroWidth = hero.offsetWidth * const heroHeight = hero.offsetHeight * let offsetXAfterAdjust = e.offsetX * let offsetYAfterAdjust = e.offsetY **/ const {offsetWidth: heroWidth, offsetHeight: heroHeight} = hero; let {offsetX: offsetXAfterAdjust, offsetY: offsetYAfterAdjust} = e; ``` ## offset 要特別留意的是 `offsetLeft`, `offsetTop`, `offsetWidth`, `offsetHeight` 是 Element 的屬性;而 `offsetX` 和 `offsetY` 是 Event 的屬性。 ### Element 屬性 - `offsetLeft`, `offsetTop` 指該 element 到 offsetParent 的距離。 - `offsetWidth`, `offsetHeight` 指該 element 的寬高。 ### Event 屬性 - `offsetX`, `offsetY`,指滑鼠到外層容器的距離。 ### 座標轉換 - 因為 `<h1>` 的外層沒有設定 position,所以它的 offsetParent 一樣會是 `<body>`,因此 `e.target.offsetLeft` 指的就是 `<h1>` 左側到 `<body>` 左側的距離(h1 <-> body)。 - 因為 `offsetX`, `offsetY` 是 event 的屬性,所以當它進入到 `<h1>` 時,offsetX 指的是該滑鼠到容器(h1)外層的距離(滑鼠 <-> h1)。 - 如果要取得++滑鼠相對於 body++ 的座標(滑鼠 <-> body),我們就用 offestX(滑鼠 <-> h1) + e.target.offsetLeft(h1 <-> body) ```jsx if (this !== e.target) { // 當 this !== e.target ,意思就是指 e.target === <h1> 的時候 offsetXAfterAdjust = offsetXAfterAdjust + e.target.offsetLeft; offsetYAfterAdjust = offsetYAfterAdjust + e.target.offsetTop; } ``` ## 陣列排序 SORT 一般情況下陣列的排序可以使用 `Array.prototype.sort(callback<a, b>)`: ```jsx const sortedBands = bands.sort((a, b) => a > b ? 1 : -1) ``` 如果我們希望排序時可以過濾掉某些字元,可以使用正規式: ```jsx function strip (word) { let regex = new RegExp('^(a |the |an )', 'i') return word.replace(regex, '').trim() } // 讓排序的依據是被 strip 過的字元 const sortedBands = bands.sort((a, b) => (strip(a) > strip(b)) ? 1 : -1) ``` ### 把 Array-like 的 Node 元素轉成 Array(Array-like to Array) 我們取得的 Node Element 它是 `Array-like` 的物件,而不是真的 Array,因此他沒有 `Array.prototype.map` 這個 function 可以使用,因此我們需要先把它轉成 Array: #### 方法一:使用 spread operator(`...`) ```jsx const timeNodes = [...document.querySelectorAll('[data-time]')] ``` #### 方法二:使用 Array.from(<arrayLikeObject>) ```jsx const timeNodes = Array.from(document.querySelectorAll('[data-time]')) ``` ### 把 data-set 的資料取出來 使用 `Element.dataset.<attribute>` 可以把 `data-time` 的值取出來。 ## forEach,map,filter,reduce 比較說明 [比較](http://www.tangshuang.net/2875.html)