0%

前言

上一篇介紹了 scope
這篇就來介紹一個平常都在用卻又不怎麼熟悉的概念 closure

閉包 Closure

Closure 光看單字就有種關了什麼起來的感覺
沒錯,閉包一言以蔽之就是

閉包會記住函式建立時的環境

也可以當作把環境鎖起來,關在閉包當中
如此一來當同時建立很多函式時,便能讓他們互不干涉
我們先從下面的例子來觀察閉包的基本行為

1
2
3
4
5
6
7
const name = 'Tony'

const greeting = () => {
console.log(name)
}

greeting()

我們並沒有將name這個變數傳入greeting中
為什麼還是印的出來呢?
正是因為當執行 greeting 函式時
它會記住建立 greeting 函式時當下的環境(name就在其中)
所以不會找不到
這個例子非常簡單,甚至平常都不會注意到這就是閉包
來看個特殊疫點的例子,巢狀函式

1
2
3
4
5
6
7
8
9
10
11
12
const counter = (num)=>{
const show = ()=>{
console.log(num++)
}
return show
}

const add = counter(1)

add() // 1
add() // 2
add() // 3

首先定義一個 counter 函式,會回傳一個內部函式 show
接著建立一個新函式 add 傳入 1 作為初始值
每次運行 add 時
參數 num 就好像偷偷被存起來一樣
每次都記得,不會被重置
原因就是每當函式被建立時,就會產生一個閉包
之後連續運行三次 add,用的都是相同的閉包
因此變數也得以保留

非同步函式的例子

在下面的例子中
我做了兩個 timer
並連 call 兩次 start 使他們開始運轉
能看到這兩個 timer 各自成一家,自己數自己的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const timer1 = document.querySelector('.timer1')
const timer2 = document.querySelector('.timer2')

// let counter = 0 //如果在這定義 counter,會變成被所有 start 共用的變數

const start = (timer, time) => {
// 每次運行 start,這裡的環境就會被存成一個閉包
let counter = 0
const tick = () => {
counter++
timer.innerText = counter.toString()
}
setInterval(tick, time)
}

const startButton=document.querySelector('.start')
startButton.onclick = ()=>{
start(timer1, 1000)
start(timer2, 500)
}

每當 start 被呼叫時
記憶體中的某處就會空出一個位子給 counter 變數
當我又呼叫一次 start,又會有一個 counter 的位子
這些 counter 互不干涉
這就是閉包的效果

此範例的完整 code 請見 我的 codepen

結論

閉包 Closure:

  1. 是函式與建立函式當下的環境的集合
  2. 會使函式記住宣告它當下的環境
  3. 能讓函式被多次呼叫卻又互不影響

前言

接下來幾篇會著重在一些 javascript 的重要觀念
未來如果忘了哪部分就可以回來看看

作用域 Scope

Scope 其實就是字面上的意思
一個變數或函式所能作用的範圍
在作用域以外的地方,就無法存取該變數
現在就用變數的宣告來解釋 scope 吧

在我開始學程式之前
曾經有個東西風靡一時
那就是var,函數變數
在我寫這篇文章之前,原本以為var是宣告全域變數
結果仔細一查才發現並不是
連現今常用的letconst也不僅僅只是區域變數而已
只要搞清楚它們的作用域範圍就會懂了

var 的作用域

var 用來宣告函數變數 function variable
意即其作用域是在宣告他的函數內
來看個簡單的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const printText = ()=>{
var text1 = 'Earth'
console.log(`text1: ${text1}`)

if (true){
var text2 = 'Moon'
}
console.log(`text2: ${text2}`)
}
printText()

// "text1: Earth"
// "text2: Moon"

text1 能被順利印出很正常
但 text2 也印的出來就沒那麼直觀了
這正是因為用var宣告的變數,在整個宣告它的函數內都有效
就算是在迴圈中宣告也一樣
var變數不會被限制在迴圈的 block 中
但如果在父層函數內的話就行不通囉~ 如下

1
2
3
4
5
6
7
8
9
const printText = ()=>{
const innerPrint = ()=>{
var text = 'Sun'
}
console.log(text)
}
printText()

// Throwing error

text 變數的作用域只在 innerPrint 函式內
超出這個 scope 就取不到了

話說那我們要怎麼宣告全域變數呢
只要在所有函式之外進行 var 宣告就可以了
只是要注意,不要再說「var就是宣告全域變數」囉

let 和 const 的作用域

letconst都是用來宣告「區塊變數 block variable」
他們的作用域相同,都是在區塊內
如迴圈的區塊,或if區塊
再來一個簡單的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const printText = ()=>{
let text1 = 'Earth'
console.log(`text1: ${text1}`)

if (true){
let text2 = 'Moon'
}
console.log(`text2: ${text2}`)
}
printText()

// "text1: Earth"
// Throwing error

text2 就印不出來了
因為 text2 只能在 if statement 中生存
這就是超出作用域的例子

var VS let/const

另外補充一個varlet的不同之處是
var會將宣告上提 (hoisting)
只是不會初始化
可看看下列例子

1
2
3
4
5
6
7
8
9
const printText = ()=>{
console.log('inside function before var:', text)
var text = 'Earth'
console.log('inside function after var:', text)
}
printText()

// "inside function before var:", undefined
// "inside function after var:", "Earth"

雖然在宣告 text 前就取值
但只顯示undefined,並不會報錯
因為 text 已經被宣告了,只是沒賦值而已

反之,letconst則會有所謂的暫時執行死區(TDZ)
所以這樣寫會出錯

1
2
3
4
5
6
7
const printText = ()=>{
console.log('inside function before var:', text)
let text = 'Earth'
}
printText()

// Throwing error

有關 hoisting 就留到之後幾篇再說吧

結論

Scope: 作用域,變數或函式的作用範圍,也可以想成生存範圍

var變數的作用域在宣告時的函式內
let變數和const常數的作用域則僅限於宣告時的區塊內

前言

3~6 月國軍 online,一般來說還有周休二日可以碰碰電腦,練練程式
但沒想到居然如願以償去馬祖當兵
身為外島兵,整個下部隊期間只有放一個大約十天的返台假有機會用到電腦
所以我也只能利用平常的空閒時間看看工程師YouTuber的影片
以及看一些書面資料,盡量讓自己不要退步太多
現在終於退伍了
趕緊做點小東西讓自己回歸手感

契機是發現越來越多人會在IG的個人首頁放上連結頁
因為只能放一個連結
如果有很多連結想放的話
可以用像是 linktreeLnk.Bio 這類的服務
快速生成連結網站

用這種簡單的網站作為暖身
應該還行哈哈
同時也能為自己的履歷做準備

設計

首先我先列出一個連結頁要有的內容:

  1. 頭像
  2. 姓名、用戶名
  3. 連結

然後一一思考我想要這些元素怎麼呈現
並把需要的素材找齊
然後就可以來拉畫面了

首先最左邊是 icon、按鈕元件
再來向右邊走分別是第一版的畫面、
第二版的畫面、
為了達到頁面滾動時背景固定而做的元件,
以及作品區塊元件 ( hover 前後的兩個 state )

第一版就是依照大部分聯結頁的排版來做
實際上我也是先做好這個
才覺得想要再做得更特別一些

第二次設計時
最先冒出的想法是想把每個按鈕都變成區塊狀的
並且可以直接看到內容縮圖
有點像作品集的感覺
但後來決定只將最後兩項採用這個做法
原因是前三個連結其實都不太需要畫面呈現
就算硬要擠出一張圖也只是點進去之後的截圖而已
意義不大 又搞得整個畫面太豐富
因此只把真的是作品的做成區塊
Github之類的連結就保持簡潔即可
最終成了第二版

實作的過程

其實沒遇到什麼困難,但有一點很困擾
那就是 CSS 變得好不熟 XD
果然就算之前已經用的蠻順手的了,空窗個四個月還是會忘掉一大堆
真是幸好有做這個小網站
才讓我的記憶慢慢回歸

在這個網站中用到兩個動畫 library
Vanta.js 用來在背景中加入雪花動畫
tilt.js 在頭像加上隨著游標晃動的特效
對一個工程師來說
懂得如何快速運用現成的library
貼過來的程式碼出錯需盡快修正
也是很重要的技能

另外這個網站沒有複雜的 RWD
基本上我只要確保在大小裝置都不要破版就好了
不需要調整到排版
所以我採用 mobile-first 的開發模式
先以小裝置螢幕來設定 CSS
便能避免視窗更動時發生嚴重破版
最後再根據大螢幕做些許調整

Mobile-first 的心得

曾經在 CSS 教學影片中看到兩個觀念
「網頁在還沒加上任何 CSS 前,就是 RWD」
mobile-first 的用意就是先做出CSS規則較少的手機版網頁
再用 media query 為相對較複雜的電腦版多加幾個規則
如果順序調過來的話
等同於先為了做網頁版添加很多規則
再為了手機板用好幾行新規則蓋過去
豈不是多此一舉

所以現在如果是我自己要從頭開發新專案
都會盡量先從手機版開始設計&開發
目的便是減少開發時程與複雜度

成品

完成的網頁在 這裡
雖然說不上多厲害
但用自己做出來的東西
果然還是比較開心