# 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)