0%

小時候不愛唸書,長大還是要還這些小時候欠的讀書債。因為在學校學了很多年,也放棄了好多年,直到出國才知道英文像把鑰匙,沒有這把鑰匙就無法好好的理解世界,尤其是理解自己喜歡的事物。於是乎出社會後試著想把這些國中遺棄的英文找回來,卻發現不是那麼容易,隨著年齡的增長,記憶與理解力並沒有隨之生長,反而是有下降的趨勢,越學越記越吃力,但真的是如此嗎?腦力是無窮的,應該還是可以靠一些方法逆轉吧?於是把自己當成一個實驗體,就試著不同的方法學習,看是不是可以把一些基礎打好並活化腦細胞。

背單字一直是個學語言的痛點,一個單字記起來了,並不表示就知道怎麼用在話語裡,介於「知道」這個字和「使用」這個字之間,兩者之間有著一段很長的距離。既然無法一下子就會用這個字,那麼至少「記住」它是基礎的,所以試著各種方法背單字。

今天聽到一個名為格林法則的背單自方式,查了一下 wiki 寫著:
「格林法則(Grimm’s law)」又稱「第一次子音推移」,是一種用來描述印歐語語音遞變的定律,由德國語言學家雅各布‧格林(Jakob Grimm)提出。 利用英文的形、聲、義,找出簡單字彙如何演變成艱深單字的方法,推翻以往用字母順序記憶單字的古板方式。

格林法則ㄧ 將母音互換

法則一影片
被單字時,試著將單字中的母音替換成其他母音:a / e / i / o / u / y 互換
例如單字: drink 換成 drank -> drunk… 事實上,許多不規則的動詞變化都是這樣變過來的。

  • sing, sang, sung
  • sink,sank, sunk
  • run, ran, run
  • ring, rang, rung
  • swim, swam, swum
  • begin, began, begun

另一個例子是 hot, a.熱的,但是以格林法則的方式,把中間的母音 o 換成 ea 變成 heat 則成為 n.熱 / vt.加熱。

再舉一個例子 sit, vi.坐,把中間的 i 換成 ea 成為 seat 就成為 n.位子 / vt. 使就坐。

格林法則允許將一個母音互換成兩個字母的母音,因為發音群是相同的。

  • best a.最好的 –> boast vi.自誇(+about/of)
  • fire n.火 –> fury n.怒火 –> furi.ous(adj字尾,表示多) a.盛怒的 –>
  • -> in.furi.ate (in 為 on 持續/.ate n./v. 字尾) vt.使大怒

格林法則二 子音 d / t / s 可互換

法則二影片

待續…

因為做練習接觸稻穀羅馬數字,才知道原來以前的數字那麼複雜、限制那麼多,而現在我們每天在用的阿拉伯數字卻是如此方便!人類的進化總是朝著更方便的方向走,用到後來覺得一切都如此合理與正常,卻不知在遠久的年代裡,也曾有這樣的紀錄數字方式,以及進位的演化。

每學一樣知識總是有驚嘆的部份,很難想像那沒有「零」的年代,且是一種談論禁忌,而在唸出數字時需要花腦筋想一下才能解讀數字,真的很神奇。

這個習題是寫出一個函式,給一個阿拉伯數字,必須回傳羅馬數字,當然也應該要了解一下羅馬數字的規則,感謝網路找到這個解說,算是最清楚了,這篇是觀看這個影片的筆記。知道規則後就開始思考如何轉化成 JS,前人寫的這篇也很值得參考。

認識古羅馬數字的字符

基本字符:
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
重點:七個拉丁字母 / 數字5 很重要 / 是種十進制系統 / 字符的值與位置無關(非位置計數法)

計數規則

- 相同的字符相連,表示的數=字符相加得到的數 → III = 3
- **右加左減** 小的字符在大的字符的右邊,表示的數等於這些字符相加的數 → VIII = 8 / XII = 12
- 只有 I, X, C 可以把小的字符寫在大的字符的左邊,所表示的數等於大數減小數所得到的數  → IV=4 (5減左邊的1)  / IX = 9 (10減左邊的1)。這種情況下,大的字符左邊最多只能有一個小的字符。8 不可以寫成 IIX,而是 VIII
- I 右邊最大字符為 X、 X 右邊最大字符為 C、 C 右邊最大字符為 M。99 = XCIM 而不是 IC
- 相同字符不可重複超過三次 4 = IV  (但仍有古蹟是寫IIII)
- 在一個數的上面畫橫線,表示這個數增值1000倍。 V = 5, V上面有橫線為5000
- 滿足上面規則的情況下,要用最少的字符表示,如100 = C,而不是LL。

計數範例

- I, II, III, IV, V, VI, VII, VIII, IX, X, XI,XII (1-12)
- 91  = XCI   → C100-X10 + I 1
- 2017 = MMXVII → MM 2000 + X 10 + VII 7
- 12345 = XIICCCXLV  → XII 12000 + CCC 300 + XLV 45 (L50 - X 10 + V 5)
- XLIIDC = 42600  → XLII (L50- X10 + II 2) + DC (D500 + C100)
- 加法運算  XIX + XXII = XLI  19+22 = 41
- LXXVII - XLVIII = XXIX  77 - 48 =

關於 0

- 羅馬計數系統中沒有表示0的字符。歐洲教會阻止0的傳播。
- 公元725年,聖彼得主教用字母N(N是 nulla 的簡稱,拉丁文解釋為零)代表零。

其他

- 小寫拉丁字母表示數字 i, ii, iii, iv… 西羅馬帝國滅亡之後,開始引入小寫,規則與大寫相同,但使用範圍與用途不同,大寫是在建築、鐘錶、日曆、書籍文件、人名X世、文章編號。
- 小寫用在有序列表(ordered list)的編號上

###
先決定從大到小還是從小到大去對應
遇到5 轉成 V(1位) ,遇到10 轉成 X(2位)
從左到右逐字判斷

useEffect() 本身是個函式,由 React 框架提供。

什麼時候使用 useEffect()

當元件狀態(useState)或變數變動之後,要做什麼事情可透過useEffect來監聽執行。
useEffect() 也是個通用的 Hook,找不到對應的 Hook 使用時也可以使用 useEffect()

例如當我們載某個元件載入之後,希望網頁標題隨之變化顯示:「歡迎來這裡」,那麼改變網頁標題這個動作就需要透過 useEffect 來完成。

1
2
3
4
5
6
7
8
9
10
11
import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const Welcome=(props)=>{
useEffect(() => {
document.title = '歡迎來這裡'
})
return <h1>Hello, {props.name}</h1>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Welcome name="Tracy" />, rootElement);

useEffect() 做什麼事?

以上述的例子來說,useEffect() 的參數是函式,這個函式要完成的就是改變網頁的標題,元件載入後 React 就會去執行這個參數的函式。元件每渲染一次,此函式就自動執行一次,元件在第一次載入頁面時,這個函式也會執行。

useEffect() 的第二個參數

剛剛說過 useEffect()的第一個參數是函式,第二個參數何時用呢?
有時候我們並不希望 useEffect() 每次渲染都執行,這時第二參數就派上用場,使用一個 Array 來指定 useEffect() 的依賴項(資料),只有依賴項發生變化,才會重新渲染。

1
2
3
4
5
6
const Welcome=(props)=>{
useEffect(() => {
document.title = `hello, ${props.name}`
},[props.name])
return <h1>Hello, {props.name}</h1>
}

上述例子中,useEffect()的第二個參數是 Array,指定第一個參數函式的依賴項(props.name)只有此依賴項的變數產生變化時,第一參數的函式才會執行。
如果第二參數是一個空的 Array,就表示第一個參數函式沒有依賴項,也因此第個參數函式只會在元件載入頁面時執行一次,之後元件重新渲染時就不會執行了。

useEffect() 的用途

useEffect() 常見的用途有:

  • 獲取資料/call API (data fetching)
  • 事件的監聽或訂閱(setting up a subscription)
  • 改變 DOM 元素(changing the DOM)
  • 輸出日誌(logging)

使用 useEffect call API

終於寫到重點了,useEffect()最常用來獲取遠端伺服器上的資料(call API),以下是範例:
此範例使用 axios 套件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, {useState, useEffect} from 'react'
import axios from 'axios'

const urlAPI = 'https://hn.algolia.com/api/v1/search'

const GetHitList = () => {
const [data, setData] = useState({hits:[]}) // hit: 轟動一時的人的列表

useEffect(()=>{
const fetchData = async () => {
const result = await axios(`${urlAPI}?query=redux`)

setDate(result.data)
}

fetchData()
},[])
return (
<ul>
{data.hits.map((item) => {
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
})}
</ul>
)
}

export default GetHitList
  • useState()來產生一個狀態變數data來保存取的得的資料;
  • useEffect()的第一參數函式使用 async 用來取得從遠端伺服器上非同步取得的資料;
  • 取得資料後再用setData()來觸發元件的重新渲染;
  • 因為取得資料只需要一次,所以useState()的第二參數依賴項為空的 Array;

其他 Call API 的寫法

以下範例是以useEffect()呼叫 API ,並把回傳的圖片 url 顯示到畫面上的三種寫法,都是使用fetch()

  • fetch()是XMLHttpRequest 的升級版,用於在 JavaScript 裡發出 HTTP 請求時使用。
  • fetch()使用Promise,不使用 callback 函式,因此大大簡化了寫法,寫起來更簡潔。

第一種寫法 fetch, async / await

1
2
3
4
5
6
7
8
9
10
11
12
13
function CallApi() {
const [data, setData] = React.useState({})
const urlAPI = 'https://dog.ceo/api/breeds/image/random'
// 第一種寫法 async / await 1.
React.useEffect(()=>{
// 先把 call API 的邏輯放到一個單獨的函式中,再呼叫它
const fetchAPI = async () => {
const res = await fetch(urlAPI)
const resJson = await res.json()
setData(resJson)
}
fetchAPI()
}, [])

第二種寫法 fetch, async / await + try / catch

1
2
3
4
5
6
7
8
9
10
11
12
React.useEffect(()=>{
const getData = async () => {
try {
const res = await fetch(urlAPI)
const resJson = await res.json()
return setData(resJson)
} catch (err) {
console.log(err)
}
}
getData()
}, [])

第二種寫法 fetch, then / catch

1
2
3
4
5
6
7
8
9
10
11
12
React.useEffect(()=>{
fetch(urlAPI)
.then(res => res.json())
.then(resJson => setData(resJson))
.catch(err => console.log(err))
}, [])

return <div>
{
data.message && <img src={data.message} />
}
</div>

useEffect() 何時需要回傳值?

useEffect()是隨著元件載入的時候執行的,那麼在元件卸載時,有時也需要清理這些useEffect()useEffect()可以回傳一個函式,在元件卸載時執行這個函式就可以清除。
如果不需要清除useEffect()第一參數函式所做的事,就可以不用回傳任何值。
例如,在元件載入時,我們使用useEffect()在加載時訂閱了一個事件,並希望在元件卸載時取消訂閱,這時就可以用useEffect()回傳的函式去取消。

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscribe()
}
},[props,source])

使用 useEffect() 的注意事項

  • 一次只做一件事,不要在useEffect()裡一次做多件事,例如 TodoList 有讀取、寫入、刪除,這三件事需要寫成三個useEffect()

最近發現以 Vue Vite 的方式來設置環境超級快,build 也快,開啟也快,但是因為有些舊專案仍然需要用的 Vue-CLI 所以就退回來以 Vue-CLI 設置專案,順便也想把 TailwindCss 設置好,才發現有許多眉眉角角的事情要注意。

通常我們會先去看官方文件怎麼寫,但是解決方案真的只有官方文件寫的那樣嗎?還有,網路上的資源那麼多,但是真的對你而言都適用嗎?在出現錯誤的過程中,我們匍匐前進,但是如果不去推敲,很難找出為什麼跑不起來的原因。例如,只是想要簡單的使用 TailwindCss,卻不想使用 CDN 的方式引進來,於是去 TailwindCss 的官網看,發現還有 Tailwind CLI、Using PostCSS、Framework Guides 這幾種。先從 Tailwind CLI 的方式來,卻怎麼樣也裝不起來,或許是配置的設定不熟,於是退而求其次的試著 Using PostCSS 的方式安裝,跑步起來,加上自己要的只是簡單的介面,不置於客製化或加上自己的樣式,所以繼續看下去…。這時候以 Vue-CLI 安裝的配置被自己弄得好複雜,依照網路上找到的設置也跑步起來,打掉重練,最後找到這個 vue-cli-plugin-tailwind,只要再安裝完 Vue-CLI,加上這麼一個神奇的指令vue add tailwind就,好,了。
當然,安裝套件也是有依賴的風險,但是這算是目前用最順的方式,所以在此記錄一下。

環境設置需求:

  • MacOS
  • Vue3
  • Vue-CLI
  • TailwindCss3

方法

  • 以 Vue-CLI 安裝好 Vue3 之後,再補上vue add tailwind指令來安裝 vue-cli-plugin-tailwind 這個套件。
  • 套件會自動生成 tailwind.config.js 設置檔案。
  • 在根目錄底下的 main.js 也會自動引入 import './assets/tailwind.css'
  • 這時 src 底下的 assets 會自動生成 tailwind.css 裡面會引入 tailwind 主要的三支 Css : base, components 和 utilities。
  • 同時也會自動生成一個 postcss.config.js,主要是可以改變 build 出來後的 CSS 檔案。

接下來就可以寫入 TailwindCss 的語法在頁面上試試了。

後續在網路上查找發先已經有前輩介紹的更詳細,如果想瞭解更細部的設定可參考:Vue CLI 安裝 Tailwind CSS,雖是2020年的文章,但大致上安裝方式沒什麼變喔~

MDN的 Document Object Model (DOM) 介紹了許多 DOM 的相關資訊,有機會用到的話,再來仔細研究一下介紹之外的其他方法,這裡只會介紹最常用的部份。
之前曾寫過一篇DOM - 操控網頁元素的必學技巧這篇就算是再一次複習吧!

DOM 到底是什麼?

在學習如何操控 DOM 之前,先來好好的理解什麼是 DOM :

DOM 是文件物件模型(Document, Object, Model)與就是用來呈現與操控 HTML 的程式介面,或 XML 檔案的互動 API。也就是把 HTML 標記(Document)轉換成物件(Object)。
這個文件物件模型提供了一個文件(樹狀)的結構表示法,並且定義讓程式可以存取或修改文件的架構、風格與內容的方法。

DOM 提供了如樹狀結構的節點,這些樹狀結構的節點都可以有自己的屬性,在節點上也可以綁定事件處理的程序,一旦這些節點因使用者行為被觸發,就會執行綁定的程序。
簡單的說 DOM 就是 JS 跟 HTML 溝通的橋樑(牽紅線)。DOM 允許我們透過 JS 來操作 HTML(Document) 這個物件,但不能直接操控頁面上的標籤,DOM 所做的事就是瀏覽器幫 Object 轉換成 HTML 對應的標記,進而讓 JS 可以改變頁面上的元素。

什麼是 Window Object

在 JS 當中的 window Object,是指我們目前程式正在運行的電腦螢幕視窗(瀏覽器視窗)。這個 window 物件裡有許多方法,例如window.alert()或是window.addEventListener()透過這些 Window 物件裡的方法,來操控物件裡的元素。以下列舉幾個常用的方法,因 JS 本身會是寫在 Window Object 這個物件裡,所以寫 JS 時 window 可省略:

使用 JavaScript 來操控網頁元素(DOM)有三種方法:

  • 介面:如何改變介面
  • 事件:如何監聽事件並做出反應
  • 資料:如何與伺服器交換資料

選取 DOM 元素

  • Tag
  • Class Name
  • ID (getElementById) Element 為單數,因為 ID 只有一個。
  • CSS 選擇器
  • element.closest() 最靠近父層的元素
1
2
3
4
5
const header = document.getElementsByTagName('header'); // tag 指 HTML 標籤
const box = document.getElementsByClassName('box'); // class name
const icebox = document.getElementById('ice-box'); // ID
const icebox_p = document.querySelector('#ice-box p'); // 匹配到的第一個 #ice-box p
const icebox_p_all = document.querySelectorAll('#ice-box p'); // // 匹配到的 所有 #ice-box p ,抓取成為 NodeList Array

CSS 選擇器

  • CSS 選擇器最常使用querySelectorquerySelectorAll
  • querySelector只會抓取並回傳第一個找到的元素,
  • querySelectorAll 會選取匹配條件的元素,並且把收集到的元素存成陣列(NodeList)回傳回來。

改變 CSS

我們可以直接改變 DOM 的樣式,但是累贅,需要一個一個寫入 style 的屬性,最常處理的方式是在元素上加上 class 類別,需要時例如事件觸發時,再加上狀態或屬性。

1
2
const linkA = document.querySelector('.box a');
linkA.classList.add('active');

增加或刪減 className

  • 增加:.classList.add('active');
  • 移除:.classList.remove('active');
  • 切換:.classList.toggle('active'); 如果沒這個 class 就新增,有就移除
  • 包含:是否包含此 class.classList.contains('active'); 回傳布林值

取得、改變文字或標籤內容

會替代掉原本的元素內文字或內容

  • [DOM].innerText =
    只抓取元素的內容,不包含子元素,只能寫入存文字
  • element.innerHTML =
    會抓取元素的內容,包含子元素,且寫入標籤元素
    ex.element.innerHTML = '<div>Hello<div>'
  • element.outerHTML=
    不只改變元件內容,也可替換掉元件本身的標籤
    ex.element.outerHTML = '<div>Hello<div>'

加入與移除元素

  • Element.append()
    是在元素的最後一個子節點之後插入一組 Node 物件或是 DOMString 物件,被插入的 DOMString 物件等價為 Text 節點。

  • Element.appendChild(aChild)
    將一個節點加到指定的父節點,子節點的列表的末端。如果某個結點已經擁有父節點,使用此方法後,此節點會先被移除,在被插入到新的位置。如果要保留原本的節點,可先使用Node.cloneNode()來複製一份,再將這個複製的節點附加到目標父節點下。而使用Node.cloneNode()複製的節點不會自動保持同步。

  • Node.removeChild()
    從 DOM 移除一個子節點,並回傳移除的節點。

1
2
3
// 創建一個新的段落元素 <p>,然後增加到 <body> 的最尾端
const p = document.createElement('p');
document.body.appendChild(p);

複製元素

  • Node.cloneNode(Deep)
    參數如果是 tree,表示為深度 clone, 節點的所有的後代節點都會被複製,如果是 false 則只會複製節點本身。
    如是 深度 clone,需注意後代節點中是否有唯一的 ID
1
2
const p = document.getElementById('para1');
const p_prime = p.cloneNode(true);

學習程式就是如此,常常沒用的東西就忘了,要用的時會再來找其實也可以,但是如果可以使用的更熟練,應該可以省下一些時間找資料吧。

展開與其餘運算子是 ES6 的特性之一,這兩個運算子的寫法都是三個點 … ,這樣的寫法使得在做一些 Array 或 JSON 的處理時,寫法簡潔很多。
透過這篇筆記的整理,希望能更熟悉的應用在自己的實作之中。

  • 展開與其餘運算子的符號是一模一樣的(…)三個點,但情況與意義不同。
  • 中文(…)通常代表其餘更多或忽略,英文 Ellipsis 代表省略,也會用”three dots”與”dot-dot-dot”這三個點
  • 應用在 Array 上,是 ES6 中的其中兩種新特性
  • Spread 展開 : 展開陣列中的值
  • Rest 其餘 : 集合其餘的值成為陣列

展開運算子(spread operator)

摘要

  • 展開運算子可輕易合併多個陣列
  • 可拿來淺拷貝,必免更動到原資料
  • 可以 slice() 來指定取出的陣列元素
  • 也可使用在物件裡並加入資料
  • 可將類陣列轉成一般陣列
  • 可把某個陣列展開,然後傳入函式作為傳入參數值

應用:兩組陣列合併在一起

以往大都使用 concat() 來合併兩個陣列,使用 (…) 可更靈活。

1
2
3
4
5
6
7
let europe = ['Paris', 'Milan', 'Berlin'];
let asia = ['Taipei', 'Tokyo', 'Hongkong'];

const allCity = [...europe, ...asia];
console.log(allCity); // ["paris", "Milan", "Berlin", "Taipei", "Tokyo", "Hongkong"]
const allCityEurope = ['London', ...europe, 'Lyon'];
console.log(allCityEurope); // ['London', 'Paris', 'Milan', 'Berlin', 'Lyon']

陣列展開語法與原本處理的方法比較,使用展開的寫法簡潔許多。

1
2
3
4
5
6
7
8
const values = [1, 2, 3];
const copiedValues = [...values];
// 等同於:
const values = [1, 2, 3];
const copiedValues = [];
for (let i = i; i < values.length; i++) {
copiedValues.push(values[i]);
}

在陣列中以展開加入資料或是,以 slice() 指定要取出的部份

1
2
3
4
5
6
7
8
9
// 前後加資料
const arr = ['a', 'b', 'c'];
const arr2 = ['z', ...arr, 'x'];

console.log(arr); // ['a', 'b', 'c']
console.log(arr2); // ["z", "a", "b", "c", "x"]

// 修改要展開的資料,用slice切片的方式
const arr3 = [...arr.slice(0, 2), 'hi', ...arr.slice(3)]; // ['a', 'b', 'hi']

應用:在物件,以展開加入資料

在物件裡也可以使用展開運算子來加入資料,需注意,如有相同的 key 則會有被覆蓋的風險,並取最後一個。
以下面的範例來說,bar 物件本身就有一個 key a, 在以 Spread operator 的方式將 bar, bar1 加入物件時,bar1, bar2 物件也有兩 key 為 a ,所以重複的會被覆蓋,而以最後一個為主。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const bar = {
a: 1,
b: 2,
c: 3,
};

const bar1 = {
a: 18,
d: 99,
};

const bar2 = {
a: 2, // 會被展開覆蓋掉
...bar,
...bar1,
a: 42, //
};

console.log(bar); // {a: 1, b: 2, c: 3}
console.log(bar2); // {a: 42, b: 2, c: 3, d: 99}

應用:淺層複製(shallow copy)

在操作陣列時,因為陣列為物件型別,以參考傳值的特性,所以複製的陣列如有修改,會改到原本的陣列。
為避免動到原始資料,可利用展開運算子來淺層拷貝。

1
2
3
4
5
6
7
8
9
10
11
12
13
const europe = ['paris', 'Milan', 'Berlin'];
const europe2 = europe;
europe2.push('Lyon');
console.log(europe); // ["paris", "Milan", "Berlin", "Lyon"]
console.log(europe2); // ["paris", "Milan", "Berlin", "Lyon"]

// 淺層拷貝作法
const europe = ['Paris', 'Milan', 'Berlin'];
const europe2 = [...europe];
europe2.push('Lyon');

console.log(europe); // ["Paris", "Milan", "Berlin"]
console.log(europe2); // ['Paris', 'Milan', 'Berlin', 'Lyon']

應用:把類陣列(Array-like)轉成純陣列

類陣列無法繼承陣列的所有方法,類陣列的產生有:從 DOM 抓取而成的陣列、arguments 物件,如果要對類陣列使用部份 Array 方法,需要先轉換成一般陣列。
可利用展開運算子來轉換。

1
2
3
4
const lists = document.querySelectorAll('li');
// 得到的 NodeList 是類陣列,無法使用部分陣列方法
listLiArr = [...lists];
// 轉換成一般陣列

應用:用來把某個陣列展開,然後傳入函式作為傳入參數值,例如加總函式的範例:

1
2
3
4
5
function sum(a, b, c) {
return a + b + c
}
const args = [1, 2, 3]
sum(…args) // 6

其餘運算子 (rest operator)

摘要

  • 其餘運算子 (rest operator)也可稱為其餘參數,也就是傳入的參數。
  • 函式可以使用arguments來取得參數,但是arguments為類陣列,但使用其餘運算子所取得的其餘參數則是陣列。
  • 其餘運算子會收集其餘的(剩餘的)值,將其轉成一個陣列。
  • 會用在函式定義時的傳入參數識別名定義(其餘參數, Rest parameters),以及解構賦值時。
  • 使用情境 1. : 在函式定義中的傳入參數定義中,稱之為其餘參數(Rest parameters)。
  • 使用情境 2. : 用在解構賦值時。

** 把其餘的都放在一起 **

應用:希望取前兩個各自給變數名稱,剩餘的放在另一個變數裡

1
2
3
4
5
const city = ['Taipei', 'Paris', 'Tokyo', 'Lyon', 'Tainan'];
const [capTaiwan, capFrance, ...autre] = city;

console.log(capTaiwan, capFrance); // "Taipei", "Paris"
console.log(autre); // ["Tokyo", "Lyon", "Tainan"]

其餘參數

1
2
3
4
5
6
function sum(a, b, c) {
return a + b + c;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2)); // NaN
console.log(sum(1, 2, 3, 4)); // 6

隨時都有可以加總更多或少於參數數量的數字,遇此狀況可利用其餘參數(…valus)來改寫此函數

1
2
3
4
5
6
7
8
9
function sum(...values) {
let total = 0;
for (let i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5));

其餘參數也可以和一般的函數參數使用,要注意一個函式只能有一個其餘參數,且只能寫在參數的最後一個位置

1
2
3
function (x,y, ...values) {
// do something
}

函式在接收參數時

情境:函式不確定會接收幾個參數,可使用其餘運算子的方式,讓參數無論是幾個,都會儲存成一個陣列

1
2
3
4
5
6
7
8
9
10
11
// 加總函式
function addTotal(...nums) {
// 參數成為陣列,也要以陣列的方式處理
let total = 0;
nums.forEach((element) => {
total += element;
});
return total;
}

addTotal(1, 2, 3, 4, 5, 6, 7); // 28

小練習

  1. 將 Paris 放到 europe 裡,其餘放在 asiaCity 裡
1
2
3
4
const city = ['Paris', 'Taipei', 'Tokyo', 'Tainan'];
const [europe, ...asiaCity] = city;
console.log(europe); // 'Paris'
console.log(asiaCity); // ['Taipei', 'Tokyo', 'Tainan']
  1. 把 food 和 drink 合併到 cafeShop 裡,並在中間加一項 beer
1
2
3
4
5
const food = ['cookie', 'cake', 'pizza'];
const drink = ['cafe', 'soda', 'the'];

const cafeShop = [...food, 'beer', ...drink];
console.log(cafeShop); // ['cookie', 'cake', 'pizza', 'beer', 'cafe', 'soda', 'the']

補充:

起手式的寫法

Vu2

1
2
3
4
5
6
7
8
9
10
11
12
13
let app = new Vue({
el: "#app",
data:{
todos: [
'todo1', 'todo2', 'todo3'
],
},
methods: {
deleteTodo: (index)=> {
app.todos.splice(index, 1)
},
},
});

Vue3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const app = Vue.createApp({
data(){
return {
todos: [
'todo1',
'todo2',
'todo3'
],
};
},
methods: {
deleteItem: function(index)=> {
this.app.todos.splice(index, 1)
},
},
})

const vm = app.mount('#app'); // 最後在綁定

TELEPORT

component 寫法不同

Vue2

HTML 裡的 <model-button> 為自定義標籤,HTML 內容則寫在model-buttontemplate裡。
這個 Modal 彈出視窗通常會放在 <body>附近,如果寫在 HTML 太裡面會不好調整, 如果使用 Teleport 則可以使用<teleport to="body">語法,將這個 Modal 放到 </body> 前面。

1
2
3
4
5
6
7
8
9
<body>
<div style="position: relative;">
<h3>cTooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
<!-- 使用 Teleport 會將 template 放到這裡來。 -->
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .Vue.extend({
app.component('modal-button',{
template: `
<button @click="modalOpen" = true>
Open full screen modal!
</button>

<div v-if="modalOpen" class="modal">
<div>
I'm a modal
<button @click="modalOpen" = false>
Close
</button>
</div>
</div>
`,
data(){
return {
modalOpen: false,
}
}
})

Teleport 寫法與用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Vue3寫法
template: `
<button @click="modalOpen" = true>
Open full screen modal! (With teleport!)
</button>

<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleport modal (my parent is "body")
<button @click="modalOpen" = false>
Close
</button>
</div>
</div>
</teleport>
`,
data(){
return {
modalOpen: false,
}
}

Composition API

Option API 的寫法叫零散,而 Composition API 寫法適合較大的專案,把同樣性質的碼放在同一區塊裡,讓專案更好管理。

KEY

Vue2

在 Vue2 時頗常使用 KEY 的寫法,來區分哪個 input

1
2
3
4
5
6
7
8
9
// Vue2 寫法
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>

Vue3

不需寫 key 讓程式碼更精簡,Vue3 會自動判斷。

1
2
3
4
5
6
7
8
9
// Vue3 寫法
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>

在 Template 寫法

Vue2

需要寫在每個標籤上

1
2
3
4
5
// Vue2
<template v-if="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>

Vue3

1
2
3
4
5
// Vue3
<template v-if="item in list" :key="item.id">
<div >...</div>
<span >...</span>
</te

ARRAY CHANGE DETECTION

通常 Data 裡面的資料只要一被更動,就會馬上畫面上更動呈現,但在 Array 的部份,並沒這麼聰明,當我們更改資料裡的陣列時, Vue2 不會立即反應至畫面,解決方法是在以 set 的方式來解決。

Vue2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var vm = Vue({
data: {
items: ['a', 'b', 'c'],
}
})
vm.item[1] = "x"; // is NOT reactive
vm.item.length = 2; // is NOT reactive
// --------------
// 以 Vue.set來解決
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
Vue.items.splice(indexOfItem, 1, newValue)

Vue.$set(vm.items, indexOfItem, 1, newValue)
1
2


Vue3

會直接修改畫面,而不需使用 set 範例

V-FOR With V-IF

在Vue 裡 V-FORV-IF 混在一起寫的狀況是非常不推薦的。
在 Vue2 如把兩者寫在一起 <v-for> 會優先,再檢查<v-if> 有沒有成立,而 Vue3 則相反,會先檢查 <v-if> 條件有沒成立,再去跑<v-for> 迴圈。

1
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>

如果不想記執行順序,最好還是多寫一層會比較清楚。

1
2
3
<template v-for="todo in todos">
<li v-if="!todo.isComplete"> {{ todo }} </li>
</template>

KEY CODE

KEY CODE 是鍵盤的對應代碼,例如 13 = enter,在 Vue3 裡已經棄用此方法,而直接以 Key modifiers 方式寫鍵盤名稱。

1
2
3
4
5
6
7
<!-- vue2 寫法 -->
<input v-on:keyup.13="submit">

<!-- only call `vm.submit()` when the `key` is `Enter` -->
<input @keyup.enter="submit" />
<!-- converting key names to kebad-case -->
<input @keyup.page-down="onPageDown" />

Key Aliases

.enter, .tab, .delete(captures both "Delete" and "Backspace" keys), .esc, .space, .up, .down, .left, .right

CONPONENT

Vue2

component 在 Vue2 的寫法是用 vue 的物件去宣告,切需要將內容包在一個 div(html root) 裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Define a new component called button-counter
Vue.component('button-counter', {
data: function(){
return {
title: "title",
content: "<p>content...</p>"
}
},
template:`
<div class="blog-post">
<h3>{{title}}</h3>
<div v-html="content"></div>
</div>
`
})
new Vue({ el: '#components-demo'})

Vue3

component 在 Vue3 的寫法會先createApp({})再去宣告一個 component。且內容物不需再以 div 包起來。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// create a Vue application
const app = Vue.createApp({})

// Define a new global component called counter
app.component('button-counter', {
data(){
return: {
title: "title",
content: "<p>content...</p>"
}
},
template:`
<h3>{{title}}</h3>
<div v-html="content"></div>
`
})

app.mount('#components-demo')

V-MODEL ON COMPONENT

1
2
3
4
<input v-model="searchText" />

<!-- 綁定資料 / 監聽 -->
<input :value="searchText" @input="searchText = 4event.target.value" />

在 component 裡綁定的方法也不太相同

Vue2

Vue2 的 v-model 是綁定 value,綁定的事件是 input

1
2
3
4
5
6
7
8
9
10
11
12
<custom-input v-bind:value="searchText" v-on:input="searchText = $event"></custom-input>

// component
Vue.component('custom-input', {
props: ['value'],
templete:
`
<input
v-bind:value="value",
v-on:input="$emit('input', $event.target.value)"
`
})

Vue3

Vue3 的 v-model 綁定的是 model-value 與 update 的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
<custom-input v-bind:model-value="searchText" @update:model-value="searchText = $event"></custom-input>

// component
app.component('custom-input', {
props: ['modelValue'],
emits:['update:modelValue'],
templete:
`
<input
:value="modelValue",
@input="$emit('update:modelValue', $event.target.value)"
`
})
1
<custom-input v-model="searchText"></custom-input>

DOM TEMPLATE PARSING CAVEATS (DOM模板解析的注意事項)

有時想要在一個

塞入一個元件,但因為瀏覽器解析沒有表格標籤trtd就會報錯,所以 vue.js 在 table 同樣置入一個<tr>的標籤,並在標籤上塞入元件名稱,來看看 vue2, 3的差異。

1
2
3
4
<!-- 這樣寫會出錯 -->
<table>
<blog-post-row></blog-post-row>
</table>

Vue2

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

Vue3

1
2
3
4
<table>
<!-- 不可直接寫元件名,需用單引號包起來 -->
<tr iv-s="'blog-post-row'"></tr>
</table>

REMOVE $on / $off / $once (棄用)

原本是以 $on / $off / $once 來湊出新的架構為: EVENBUS 來當作各元件之間的橋樑
現在作法會先開一個空的 Vue 並說明客製化一個名為 custom-event ,當event發生時會做一些事情。

Vue2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// eventHub.JS
const eventHub = new Vue()
export default eventHub

// ChildComponent.vue
import eventHub from './eventHub'

export default {
mounted() {
// adding eventHub listener
eventHub.$on('custom-event', ()=>{
console.log('Custom event triggered');
})
},
beforeDestroy() {
// removing eventHub listener
eventHub.$off('custom-event')
},
}

// ParentComponent.vue
import eventHub from './eventHub'

export default {
methods:{
callGlobalCustomEvent(){
eventHub.$emit('custom-event')
// if ChildComponent is mounted, we will...
}
}
}

eventBus 被棄用後,替代方案為 Vuex

Vue Cli / Vite

Vue Cli 以下指令的方式創建專案,使用webpack
Vite 以下指令的方式創建專案,但渲染頁面速度更快,因為使用瀏覽器內建系統。

Vue2

Vue3

Vue2

Vue3

Vue2

Vue3

有很多時候,我們只會拿這放大鏡看,而忘了抬起頭來,退後幾步讓視野看得更多更廣。有時候 coding 就是這麼一回事。

如何開始一個小專案

想要做一個小小志工排班管理系統,卻不知如何開始,花了許多時間在看線上教學,但自己實作時卻遇上許多問題,如何開始是一個大問題,接著網頁要放哪裡?要不要有登入機制?資料要放哪裡?開放哪些權限?這些只是大方向,接著就會陷入糾結,例如用哪個框架?(其實比較會的也只有Vue),以 CDN 或 CLI 實作?要用自己的資料庫還是線上的(比較熟mySql,但是PHP有點忘了?),就這樣反覆折騰之後,想說那就用 VueJs 和 Firebase 搭配吧?接這就花些時間熟悉 Firebase 所提供的服務,且看來在自己還不會後端自己開 API 之前,Firebase 似乎是比較適合。

Firebase

看來 firebase 的功能不少,如果熟悉應該會對開發小專案有用,十幾年前開始使用 MySql 也真的習慣「關連式」的資料結構,firebase 使用的是 collection 的資料結構,我把它想成想是一個物件型態,這樣或許比較好理解。

collection 的資料結構有個好處就是想加資料就加資料,彈性很大。先把需要的資料大概的列出來,在 firebase 設定好,再去設定連線資料,回到開發環境,就可以連上去了。firebase 也提供 CLI 工具,讓我們在本地開發更方便,且可以直接將專案推上 Github 真的蠻不錯的。

這裡蒐集平常看到的小題目,無論做對或做錯,都記錄下來。

No.1

What will this code log to the consol?

1
2
3
const foo = [1, 2, 3];
const [n] = foo;
console.log(n); // 1

使用解構賦值 (Destructuring assignment) ,可以把陣列或物件中的資料解開擷取成為獨立變數。
解構賦值使用類似語法;不過在指定敘述式的左側,要宣告從來源變數接收解開值之變數。

1
2
3
4
const x = [1, 2, 3, 4, 5];
const [y, z] = x;
console.log(y); // 1
console.log(z); // 2

No.2

What does this code print to the consol?

1
2
3
4
const x =[1, 2];
const y =[5, 7];
const z =[x,y];
console.log(z); // [1,2,5,7]

使用 ES6 的解構賦值 (Destructuring assignment),可以把陣列或物件中的資料解開擷取。

No.3

What value is assigned to total after this code executes?

1
2
3
4
5
6
7
const sum = function(num1, num2 =2, num3 = 3){
return num1 + num2 + num3
}

let values = [1, 5];
let total = sum(4, ...values);
console.log(total); // 10

一樣使用 ES6 的解構賦值 (Destructuring assignment)

No.4

What line of code causes this code segment to throw an error?

1
2
3
4
5
6
7
const lion = 1;
let tiger = 2;
var bear;

++lion;
bear += lion + tiger;
tiger++;

++lion;,因為是以 const 賦值,如果值為原始型別,就無法再度賦值。如果是物件型別則可修改物件裡的內容。
所以答案是:++lion;,because lion cannot be reassigned a value.

No.5

What is the output that is printed when the div containing the text “click Here” is clicked?

1
2
3
4
5
<div id="A">
<div id="B">
<div id="C">Click Here</div>
</div>
</div>
1
2
3
document.querySelectorAll("div").forEach(e => {
e.onclick = e => console.log(e.currentTarget.id);
});

答案是 “C” “B” “A”,冒泡(bubbling)
可參考這篇:冒泡和捕获

No.6

把缺的 code 補上

1
2
3
4
// Missing Line
for(var i = 0; i < vowels.length; i++){
console.log(vowels[i]); // a e i o u
}

別想太多,就是 vowels 變數沒定義與賦值:let vowels = "aeiou"

No.7

Which snippet could you add to this code to print “YOU GOT THIS” to the console?

1
2
3
4
5
6
let cipherText = [..."YZOGUT QGMORTZ MTRHTILS"];
let plainText = "";

/* Missing Snippet */

console.log(plainText); // YOU GOT THIS

答案:

1
2
3
4
5
for(let [index, value] of cipherText.entries()){
plainText += index % 2 === 0 ? value : ""
// 如果 index 除二餘數是零,就取 value 不然就空字串
console.log(index,value);
}

原來可以使用 for..of 這樣取到 index 和 value !

No.8

Which code would you yse to access the Irish flag?

1
2
3
4
5
6
7
var flagsJSON = '{"countries" : [' +
'{"country":"Ireland" , "flag":"irish"}, ' +
'{"country":"Serbia" , "flag":"serbia"}, ' +
'{"country":"Peru" , "flag":"peru"} ]}';

var flagDatabase = JSON.parse(flagsJSON);
console.log(flagDatabase);

答案:flagDatabase.countries[0].flag

No.9

What statement can be used to select the element from the DOM containing the “The LinkedIn Learning library has great Javascript courses” from this markup”?

1
2
3
4
<h1 class="content">LinkedIn Learning</h1>
<div class="content">
<span class="content">The LinkedIn Learning library has great Javascript courses</span>
</div>

答案:document.querySelector("span.content");

No.10

What is wrong with this code?

1
2
3
4
5
6
7
const foo = {
bar(){
console.log("Hello World!");
},
name: "Tracy",
age: 42,
}

一切正常,不會出錯。

No.11

Which line could you add to this code to print “jaguar” to the console?

1
2
3
4
let animals = ["jaguar", "eagle"];
// Missing Line
let
console.log(animals.pop()); // prints jaguar

答案:animals.reverse()
Array.prototype.pop() 會刪除陣列的最後一個元素,並回傳刪除元素,先使用 reverse() 將陣列反轉,在取出最後一個元素。

No.12

What is the output of the code?

1
2
3
let rainForests = ["Amazon", "Borneo", "Cerrado", "Congo"];
rainForests.splice(0,2)
console.log(rainForests);

答案: ['Cerrado', 'Congo']
splice(0,2):從 index 0 切兩個掉。

前陣子在進行 Vue.js 專案時,因為要使用 Tailwind Css 的關係所以把 Node 的版本升到 v14.17.6 的版本,但最近在更新 hexo 文章時卻無法部署,查了一下錯誤訊息hexo The "mode" argument must be integer. Received an instance of Object的來源才知道是因為 Node 的版本對 hexo 來說太高,所以無法生成與部署,記得 nvm 是在做 Node 版本控管,查了一下只要再把較低版本安裝回去,然後切換到原本的版本就好了。

已經忘記當初裝 hexo 的 Node 版本是多少,於是從 v11.13.0 開始安裝,裝了之後 hexo 出現一片白,看來是太低,憑印象好像是 v12,查了一下穩定版本是 v12.22.8 於是使用 nvm 指令安裝:
nvm install v12.22.8
安裝完畢 node 會直接使用剛安裝的版本,這時再生成和部署一次 Hexo 就沒問題了。

NVM 是什麼?

NVM 是 node 版本管理工具
以下記錄一下 nvm 的指令:

  • 查看版本:nvm vnvm version
  • 查看所有可安装 Node 版本: nvm list available
  • node 安装最新版本(最新不一定最穩) : nvm install node
  • node 安装指定版本(推薦) : nvm install [指定版本]
  • 查看已安装 node 版本: nvm lsvm list
  • 使用指定版本: nvm use [指定版本]
  • 卸載已安装的 node 版本: nvm uninstall [版本]

所以記得下次要在 Hexo 發文部署前,先使用 nvm use v12.22.8 切回 hexo 相容的 node 版本,發完文章再切回 Tailwind Css 需要的,也就是當前常用的 v14.17.6 版本就可以了。