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範例:

<p class='abc'> test </p>

css:

.abc{ color: #00ff00; }

id範例:

<p id='footer'> test </p>

css:

#footer{ color: #00ff00; }

為元素增加/移除 class

利用 el.classList.add/remove/toggle 來為元素添加/移除/切換 class:

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 屬性:

const block = document.querySelector('.block')
// 監聽所有 block 上有 transition 的 CSS 屬性
block.addEventListener('transitionend', e => {
    console.log(e.propertyName)
})

取得現在的時間

關於更多 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 變數 */
: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)

document.documentElement.style.setProperty(`--${this.name}`, this.value + this.dataset.unit)

取得 data-attribute

我們可以在 html 中利用 data attribute 設值

更多關於 html5 data attribute @ pjchender

<div class="tooltip" data-content="hello"></div>

在 JS 中我們可以使用 el.dataset 來取得 data-attribute 的值

const block = document.querySelector('.block')
console.log(block.dataset.content)

建立正規式

可以利用 new RegExp(regex, flag) 來建立正規式:

  • 第一個參數放的是正規式的內容
  • 第二個參數是 flag
    g 表示 global search,也就是會去找整份文件,而不是找到就停
    i 表示 case insensitive,也就是不去區分大小寫
let regex = new RegExp(wordToMatch, 'gi')

input 事件和 change 事件的差異

這裡我們用 input 事件

  • input event 會在任何元素值改變的時候被出發(例如每打一個字都會觸發一次)
  • change event 則是會在有元素值改變,且該元素脫離 focus 狀態時才觸發
searchInput.addEventListener('input', e => {
    displayMatch(e)
})

Array.prototype.slice 的使用

在原本的內容中,作者使用的是 Array.prototype.splice 這個方法,但 splice 一般來說是在刪除或添加陣列內容時使用,因此個人認為使用單純擷取陣列元素的 Array.prototype.slice 應該更為適合(我想作者有它的考量)。

使用

arr.slice(begin, end)
- 從 begin 開始,不包含 end
- 最後一個元素的 index 是 -1

例子

/**
 * 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 的使用

arr.join(separator)    // separator 預設是 ','

browser視窗

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 的方式或許就不適合。


/**
 * 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

<!--  只有出現 checked attribute 則 checkbox 都會被勾選  -->
<input type="checkbox" checked>
<input type="checkbox" checked="false"> <!-- 一樣會勾選 -->

this 和 e.target在 eventHandler 中的差異

element.addEventListener 中使用 thise.target 的差異:

  • this 總是會拿到被監聽的對象本身,也就是 element.addEventListener(<eventHandler>) 的 element。
  • e.target 則是指事件被觸發時的對象,有可能不是 element 本身。
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

/**
 * 等同於
 * 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 的屬性;而 offsetXoffsetY 是 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)
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>)

const sortedBands = bands.sort((a, b) => a > b ? 1 : -1)

如果我們希望排序時可以過濾掉某些字元,可以使用正規式:

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(...

const timeNodes = [...document.querySelectorAll('[data-time]')]

方法二:使用 Array.from(<arrayLikeObject>)

const timeNodes = Array.from(document.querySelectorAll('[data-time]'))

把 data-set 的資料取出來

使用 Element.dataset.<attribute> 可以把 data-time 的值取出來。

forEach,map,filter,reduce 比較說明

比較