關於封裝 localStorage 的小技巧

關於封裝 localStorage 的小技巧

前言

在開發網頁時,常常會需要使用到 window.localStorage 來儲存資料,但是 window.localStorage 本身的 API 並不是很好用,所以我們可以透過封裝的方式來讓它更好用。

window.localStorage

window.localStorage 是瀏覽器所提供的一個 Web API,只要你是網頁開發者你一定會使用過它,它可以讓我們在瀏覽器中儲存資料,並且不會因為關閉瀏覽器而消失,另一個則是 window.sessionStorage,它也是瀏覽器所提供的 Web API,但是它的特性是當瀏覽器關閉時就會消失,因此我們可以透過這兩個 Web API 來儲存資料。

但這一篇我將會著重於介紹 window.localStorage,因為它的特性是不會因為關閉瀏覽器而消失,因此我們可以透過它來儲存一些重要的資料,例如:使用者的登入資訊、使用者的偏好設定等等。

Note
window.sessionStorage 的運作以及使用方式與 window.localStorage 一模一樣,只是它的特性是當瀏覽器關閉時就會消失,因此我們可以透過它來儲存一些不重要的資料,例如:使用者的暫存資料等等,但有趣的是 window.localStorage 是可以在不同網頁上共用資料,但 window.sessionStorage 則不行,畢竟它的特性就是當瀏覽器關閉時就會消失,因此 window.sessionStorage 只能在同一個網頁上共用資料。

那麼 window.localStorage 有以下幾種方法可以使用

  • window.localStorage.setItem(key, value):新增一筆資料
  • window.localStorage.getItem(key):取得一筆資料
  • window.localStorage.removeItem(key):刪除一筆資料
  • window.localStorage.clear():刪除所有資料

當你使用 window.localStorage.setItem('myName', 'Ray') 之後,你可以在瀏覽器的開發者工具中的 Application 頁籤中的 Local Storage 中看到你新增的資料,如下圖

Firefox

Note
各家瀏覽器呈現都差不多,由於我是使用 Firefox,因此可能會與你的瀏覽器呈現不同,但是大同小異,你可以自行嘗試。

window.localStorage 的缺點

那麼其實 window.localStorage 本身的 API 就是這麼簡單,但是它有幾個缺點,就是它的 API 其實有一些問題,什麼意思呢?

其實 window.localStorage 是可以儲存物件的,例如…

1
window.localStorage.setItem('myObj', { name: 'Ray', age: 18 })

但是如果你使用 window.localStorage.getItem('myObj') 來取得資料時,你會發現它會回傳 [object Object]

難道這是 Bug 嗎?還是要用什麼特別的方法才能解析呢?其實原因是因為 window.localStorage 只能儲存字串,因此當你儲存物件時,你必須先將物件轉成字串,例如:

1
window.localStorage.setItem('myObj', JSON.stringify({ name: 'Ray', age: 18 }))

這樣子你才可以正確的儲存物件。

當然不只有這個問題,剛剛有提到,因為 window.localStorage 只能儲存字串,因此當你取出資料時,你必須先將字串轉成物件,例如:

1
const myObj = JSON.parse(window.localStorage.getItem('myObj'))

那麼這就會延伸另一個問題,也就是如果 window.localStorage.getItem('myObj') 如果取出來是 null 的話,雖然 JSON.parse(null) 也會回傳 null,然後不會出現錯誤,但如果今天你的資料是 undefined 的話,那麼 JSON.parse(undefined) 就會出現錯誤

1
JSON.parse(undefined) // Uncaught SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

又或者你在儲存資料時,忘記轉成字串,例如:

1
2
3
window.localStorage.setItem('myObj', { name: 'Ray', age: 18 })

JSON.parse(window.localStorage.getItem('myObj')) // Uncaught SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data

雖然最簡單方式就是使用 try...catch 或者當出現 null 的時候就改回傳空物件,但是這樣子其實會讓程式碼變得很醜,因此我們可以透過封裝的方式來解決這個問題。

封裝 window.localStorage

首先封裝的方式有很多種,有些人會使用 Class 來封裝,有些人會使用 Function 來封裝,那麼這邊兩種方式我都會介紹,但是我個人比較喜歡使用 Function 來封裝,因此我會先介紹 Function 的方式,後面再介紹 Class 的方式。

那麼通常我們封裝後會放在 utils 資料夾中,因此我們可以在 utils 資料夾中新增一個 localStorage.js,接著我們就可以開始封裝了。

第一個要封裝的功能就是取得資料,因此我們可以新增一個 get 的方法,並且將 window.localStorage.getItem(key) 的功能封裝起來,例如:

1
2
3
export const getStorageItem = (key) => {
return window.localStorage.getItem(key)
}

很簡單吧?但其實這樣是不夠的,如果開發者忘記傳入 key 的話,那麼就會出現 undefined,因此我們可以判斷一下 key 是否存在,如果不存在的話就回傳 console.error 警告給開發者,例如:

1
2
3
4
5
6
7
8
export const getStorageItem = (key) => {
if (!key) {
console.error('缺少取得 localStorage 的 key');
return
}

return window.localStorage.getItem(key)
}

這樣子使用者只要忘記傳入 key 的話,就會出現警告,這樣子就可以避免開發者忘記傳入 key

缺少 key

接著最後,我們取出來時,也要判斷一下,如果是 null 或者是 undefined 的話,就回傳空物件,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const getStorageItem = (key) => {
if (!key) {
console.error('缺少取得 localStorage 的 key');
return
}

const data = window.localStorage.getItem(key)

if (!data) {
return {}
}

return JSON.parse(data)
}

Note
要回傳空物件或者是空陣列,其實都可以,但是我個人比較喜歡回傳空物件,因為這樣子就可以直接使用 Object.keys 來判斷物件是否為空物件,例如:Object.keys({}).length === 0,但是如果是空陣列的話,就必須要使用 Array.isArray 來判斷,例如:Array.isArray([]) && [].length === 0

接著使用時,就只需要引入 getStorageItem 這個 Function,並且傳入 key 就可以了,例如:

1
2
3
import { getStorageItem } from './utils/localStorage'

const myObj = getStorageItem('myObj')

發現了嗎?透過封裝之後,我們就不需要再使用 JSON.parse 來解析資料了,而且我們還做了一些判斷,讓開發者不會忘記傳入 key,這樣子就可以避免一些低級錯誤。

接著當然就是最常使用的 set 方法,也就是新增資料,封裝方式也是一樣概念,所以我就直接貼上來,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
export const setStorageItem = (key, value) => {
if (!key) {
console.error('缺少設定 localStorage 的 key');
return
}

if (!value) {
console.error('缺少設定 localStorage 的 value');
return
}

window.localStorage.setItem(key, JSON.stringify(value))
}

很簡單吧?其實封裝並不困難,但是透過這些封裝可以大幅度減少一些低級錯誤,例如:忘記傳入 key 或者是 value,甚至是忘記轉成字串,這些都可以透過封裝來避免,透過封裝也可以讓我們的程式碼更加的乾淨。

當然這邊其實還有一件事情是封裝的優勢,也就是當我們封裝引入後,我們可以判斷 window.localStorage 是否存在,如果不存在的話,我們就要提示開發者,當前的開發環境不支援 window.localStorage,例如:

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
32
33
34
35
36
37
38
export const getStorageItem = (key) => {
if (!window.localStorage) {
console.error('當前的開發環境不支援 localStorage');
return
}

if (!key) {
console.error('缺少取得 localStorage 的 key');
return
}

const data = window.localStorage.getItem(key)

if (!data) {
return {}
}

return JSON.parse(data)
}

export const setStorageItem = (key, value) => {
if (!window.localStorage) {
console.error('當前的開發環境不支援 localStorage');
return
}

if (!key) {
console.error('缺少設定 localStorage 的 key');
return
}

if (!value) {
console.error('缺少設定 localStorage 的 value');
return
}

window.localStorage.setItem(key, JSON.stringify(value))
}

那麼問題來了,感覺我們封裝的功能越來越多了,那麼我們要怎麼管理呢?其實這時候就可以使用 Class 來封裝,例如:

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
32
33
34
35
36
37
38
39
class LocalStorage {
constructor() {
if (!window.localStorage) {
console.error('當前的開發環境不支援 localStorage');
return
}
}

get(key) {
if (!key) {
console.error('缺少取得 localStorage 的 key');
return
}

const data = window.localStorage.getItem(key)

if (!data) {
return {}
}

return JSON.parse(data)
}

set(key, value) {
if (!key) {
console.error('缺少設定 localStorage 的 key');
return
}

if (!value) {
console.error('缺少設定 localStorage 的 value');
return
}

window.localStorage.setItem(key, JSON.stringify(value))
}
}

export default new LocalStorage()

接下來使用時,就可以直接引入 LocalStorage 這個 Class,並且使用 LocalStorage.get('myObj') 來取得資料,或者是使用 LocalStorage.set('myObj', { name: 'Ray', age: 18 }) 來新增資料,例如…

1
2
3
4
5
import LocalStorage from './utils/localStorage'

LocalStorage.set('myObj', { name: 'Ray', age: 18 });

const myObj = LocalStorage.get('myObj')

當我們使用 import LocalStorage from './utils/localStorage' 引入時,其實就已經執行了 new LocalStorage(),因此我們初始化時,就同時判斷了 window.localStorage 是否存在,如果不存在的話,就會出現警告,這樣子就可以避免開發者在使用時,忘記判斷 window.localStorage 是否存在的問題。

那麼我相信你對於 window.localStorage 的封裝應該有一些概念哩~

底下我也提供一個完整的範例,你可以參考看看~

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class LocalStorage {
constructor() {
if(typeof window === 'undefined') {
console.error('當前的開發環境不支援 window');
return
}
if (!window.localStorage) {
console.error('當前的開發環境不支援 localStorage');
return
}
}

get(key) {
if (!key) {
console.error('缺少取得 localStorage 的 key');
return
}

const data = window.localStorage.getItem(key)

if (!data) {
return {}
}

return JSON.parse(data)
}

set(key, value) {
if (!key) {
console.error('缺少設定 localStorage 的 key');
return
}

if (!value) {
console.error('缺少設定 localStorage 的 value');
return
}

window.localStorage.setItem(key, JSON.stringify(value))

const data = this.get(key)
if(!data) {
console.error('設定 localStorage 失敗');
return false
} else {
return true
}
}

remove(key) {
if (!key) {
console.error('缺少刪除 localStorage 的 key');
return false
}

window.localStorage.removeItem(key)

const data = this.get(key)

if(data) {
console.error('刪除 localStorage 失敗');
return false
} else {
return true
}
}

clear() {
window.localStorage.clear()
}
}

export default new LocalStorage()

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ