項目實戰(zhàn)之本地存儲篇
在前端項目開發(fā)中,前端的本地存儲是必不可少的,今天小編就前端的本地存儲在項目中的使用詳細(xì)的介紹一下。前端本地存儲主要有:
cookie
localStorage
sessionStorage
webSQL/indexDB
接下來就這三種前端常用的存儲方式進(jìn)行介紹。
cookie
cookie就是存儲在客戶端的一小段文本,大小不能超過4kb,在請求接口的時候,cookie會被請求攜帶著,進(jìn)而被服務(wù)器所讀取使用。
打開瀏覽器控制臺,F(xiàn)12>>Application>>Cookies,隨便選擇一個域,我們可以看到里面有很多cookie,如下所示:
主要屬性
Name
cookie的名稱
Value
cookie的值,大小最大4Kb
Domain
cookie存儲的域名
Path
cookie存儲的路徑
Size
cookie的大小
Expires/Max-Age
這兩個屬性涉及到 cookie 的存活時間
Expires 屬性指定一個具體的到期時間,到了這個指定的時間之后,瀏覽器就不再保留這個 cookie ,它的值是 UTC 格式,可以使用 Date.prototype.toUTCString() 格式進(jìn)行轉(zhuǎn)換。
Max-Age 屬性制定了從現(xiàn)在開始 cookie 存在的秒數(shù),比如 60 * 60 (即一小時)。過了這個時間以后,瀏覽器就不再保留這個 Cookie。
Max-Age的優(yōu)先級比Expires高,如果兩者都不設(shè)置,則這個cookie會在瀏覽器關(guān)閉的時候失效。
HttpOnly
如果設(shè)置了該屬性,意思就是這個cookie不能被JavaScript取到,也就防止了cookie被腳本讀取,然后當(dāng)發(fā)起請求的時候,該cookie才會被帶上。
Secure
指定瀏覽器只有在加密協(xié)議 HTTPS 下才能發(fā)送cookie,不需要設(shè)置,當(dāng)協(xié)議是https時,會自動開啟。
使用方式
設(shè)置cookie
/**
* 設(shè)置cookie
* @param {*} key 名稱
* @param {*} val 值
* @param {*} time 失效時間
*/
export const setCookie = (key, val, time) => {
var date = new Date();
var expiresDays = time;
//將時間轉(zhuǎn)換為cookie設(shè)置時間的格式
date.setTime(date.getTime() + expiresDays * 24 * 3600 * 1000);
document.cookie = key + "=" + val + ";expires=" + date.toDateString();
}
獲取cookie
/**
* 獲取cookie
* @param {*} key 名稱
*/
export const getCookie = (key) => {
var getCookie = document.cookie.replace(/[ ]/g, "");
var arrCookie = getCookie.split(";")
var tips;
for (var i = 0; i < arrCookie.length; i++) {
var arr = arrCookie[i].split("=");
if (key == arr[0]) {
tips = arr[1];
break;
}
}
return tips;
}
刪除cookie
刪除cookie的話,可以直接調(diào)用設(shè)置cookie的方法,將失效時間置為-1,如下:
setCookie(key,'',-1)
1
localStorage/sessionStorage
localStorage/sessionStorage是在html5中新加入的技術(shù),兩者除了數(shù)據(jù)的時效性不一樣之外,其他都一樣。大小一般為5MB,存儲的時候,僅僅在客戶端存儲,不會隨著請求的調(diào)用而傳遞到服務(wù)器。
localStorage不手動刪除則永久有效,sessionStorage僅在當(dāng)前會話有效
下面F12>>Application>>Cookies看一個存儲示例:
可以看到,其只有兩個屬性,也就是key(名稱)、value(值)
使用方式
localStorage和sessionStorage都具有相同的操作方法,例如setItem、getItem和removeItem等,為了使用方便,我們對其進(jìn)行二次封裝,示例如下:
存儲Storage
/**
* 存儲Storage
*/
export const setStore = (params = {}) => {
let {
name,//名稱
content,//內(nèi)容
type,//類型
} = params;
let obj = {
dataType: typeof (content),
content: content,
type: type,
datetime: new Date().getTime()
}
if (type) window.sessionStorage.setItem(name, JSON.stringify(obj));
else window.localStorage.setItem(name, JSON.stringify(obj));
}
獲取Storage
/**
* 判斷是否為空
*/
function validatenull (val) {
if (typeof val === 'boolean') {
return false
}
if (typeof val === 'number') {
return false
}
if (val instanceof Array) {
if (val.length == 0) return true
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true
} else {
if (val == 'null' || val == null || val == 'undefined' || val == undefined || val == '') return true
return false
}
return false
}
/**
* 獲取Storage
*/
export const getStore = (params = {}) => {
let {
name,//名稱
debug//是否需要轉(zhuǎn)換類型
} = params;
let obj = {},
content;
obj = window.sessionStorage.getItem(name);
if (validatenull(obj)) obj = window.localStorage.getItem(name);
if (validatenull(obj)) return;
try {
obj = JSON.parse(obj);
} catch{
return obj;
}
if (debug) {
return obj;
}
if (obj.dataType == 'string') {
content = obj.content;
} else if (obj.dataType == 'number') {
content = Number(obj.content);
} else if (obj.dataType == 'boolean') {
content = eval(obj.content);
} else if (obj.dataType == 'object') {
content = obj.content;
}
return content;
}
刪除Storage
/**
* 刪除localStorage
*/
export const removeStore = (params = {}) => {
let {
name,
type
} = params;
if (type) {
window.sessionStorage.removeItem(name);
} else {
window.localStorage.removeItem(name);
}
}
獲取全部Storage
/**
* 獲取全部Storage
*/
export const getAllStore = (params = {}) => {
let list = [];
let {
type
} = params;
if (type) {
for (let i = 0; i <= window.sessionStorage.length; i++) {
list.push({
name: window.sessionStorage.key(i),
content: getStore({
name: window.sessionStorage.key(i),
type: 'session'
})
})
}
} else {
for (let i = 0; i <= window.localStorage.length; i++) {
list.push({
name: window.localStorage.key(i),
content: getStore({
name: window.localStorage.key(i),
})
})
}
}
return list;
}
清空全部Storage
/**
* 清空全部Storage
*/
export const clearStore = (params = {}) => {
let { type } = params;
if (type) {
window.sessionStorage.clear();
} else {
window.localStorage.clear()
}
}
cookie、localStorage、sessionStorage異同
數(shù)據(jù)存儲時間
cookie 可以自己設(shè)置失效時間
localStorage 不主動清除,則永久存儲
sessionStorage 當(dāng)前頁面關(guān)閉時被刪除
大小
cookie 最大4kb
localStorage 最大5MB
sessionStorage 最大5MB
請求是否攜帶
cookie 每次請求會攜帶在請求頭中
localStorage 不攜帶,僅在客戶端存儲
sessionStorage 不攜帶,僅在客戶端存儲
易用性
cookie 原生api使用不友好,需自己二次封裝
localStorage 原生接口可使用,也可以自己二次封裝
sessionStorage 原生接口可使用,也可以自己二次封裝
個人推薦在項目中使用storage存儲,cookie存儲數(shù)據(jù)過多,會造成性能問題。當(dāng)然,大家可以根據(jù)實際情況進(jìn)行選擇,二次封裝方法已雙手奉上。
webSQL/indexDB
對于簡單的數(shù)據(jù)存儲,storage和cookie就已經(jīng)夠用了,但是如果需要存儲比較復(fù)雜的關(guān)系型數(shù)據(jù),再使用storage和cookie,就有點力不從心了。這個時候可以使用webSQL或者indexDB進(jìn)行存儲。
webSQL
Web SQL數(shù)據(jù)庫API是一個獨立的規(guī)范,在瀏覽器層面提供了本地對結(jié)構(gòu)化數(shù)據(jù)的存儲,已經(jīng)被很多現(xiàn)代瀏覽器支持了。
核心api
openDatabase() => 用來打開或創(chuàng)建數(shù)據(jù)庫(沒有時則創(chuàng)建,有則打開)
transaction() => 這個方法可以控制一個或多個事務(wù),以及基于這種情況提交或者回滾
executeSql() => 用于執(zhí)行實際的 SQL 查詢
判斷瀏覽器是否支持該功能
從上面的圖中可以看出,webSQL兼容性并不是太好,因此使用時,我們需要先判讀那瀏覽器是否支持。
if (window.openDatabase) {
// 操作 web SQL
} else {
alert('當(dāng)前瀏覽器不支持 webSQL !!!');
}
webSQL操作類封裝
webSQL操作類封裝代碼量較大,此處就不再展示,需要的小伙伴可以關(guān)注我公眾號回復(fù)【webSQL操作類】獲取。下面給個簡單的例子:
var myDB = {
name: 'formData',
version: 1,
db: null,
};
myDB.db = openDatabase(myDB.name, myDB.version, 'test', 100 * 1024 * 1024);
myDB.db.transaction(function(tx) {
tx.executeSql('', [], function(tx, result) {
if(result.rows.length!=0){
//result.rows.item(i)
}
}, function(tx, error) {
console.log(error);
});
})
常用的SQL語句:
//新建表
'CREATE TABLE IF NOT EXISTS 表名 (列名稱1 PRIMARY KEY,列名稱2 UNIQUE ,列名稱3)'
//刪除表
'DROP TABLE 表名'
//清空表
'DELETE FROM 表名'
//刪除條目
'DELETE FROM 表名 WHERE 列名稱1 = ? and 列名稱2 = ?'
//新增一條
'INSERT INTO 表名 VALUES (?,?,?,?,?,?,?)' //為所有列添加值
'INSERT INTO 表名 (列名稱2,列名稱4,列名稱6) VALUES (?,?,?)' //為指定列添加值
//批量增加
insert into persons
(id_p, lastname , firstName, city )
values
(200,'haha' , 'deng' , 'shenzhen'),
(201,'haha2' , 'deng' , 'GD'),
(202,'haha3' , 'deng' , 'Beijing')
//更新一條
'UPDATE 表名 SET 列名稱1 = ? where 列名稱2 = ? AND 列名稱3 = ?'
'UPDATE 表名 SET 列名稱1 = ?,列名稱2 = ?,列名稱3 = ? where 列名稱2 = ? AND 列名稱3 = ?'
//根據(jù)主鍵存在與否,更新或添加一條數(shù)據(jù)
'replace into 表名 (列名稱1,列名稱2,列名稱3,列名稱4,列名稱5) VALUES (?,?,?,?,?) '
//查找(更多查詢請根據(jù)自己的需要自由組合)
'select * from 表名 where 列名稱1 = ? and 列名稱1 >= ?' //常規(guī)查找
'select * from 表名 where 列名稱1 = ? or 列名稱1 >= ?' //常規(guī)查找
'select * from 表名 ORDER BY ?' //指定排序項
'select * from 表名 ORDER BY ? LIMIT 2;'//只查找符合條件的2條
WHERE 列名稱 IS NOT NULL //非空
WHERE 列名稱 LIKE "111%" //111開頭的
WHERE 列名稱 LIKE "%111" //111結(jié)尾的
WHERE 列名稱 LIKE "%111%" //包含111的
WHERE 列名稱 NOT LIKE "%111%" //不包含111的
'_a_' //三位且中間字母是a的
'_a' //兩位且結(jié)尾字母是a的
'a_' //兩位且開頭字母是a的
WHERE 列名稱 GLOB > 111 //大于111
WHERE 列名稱 GLOB >= 111 //大于等于111
WHERE 列名稱 GLOB != 111 //不等于111
WHERE 列名稱 GLOB '111*' //111開頭的
WHERE 列名稱 IN ( 25, 27 ) //值為25或27的
WHERE 列名稱 NOT IN ( 25, 27 ) //值不為25或27的
WHERE 列名稱 BETWEEN 25 AND 27 //值在25到27之間的
WHERE 列名稱 IN ( '25', '27' ) //注意:拼接sql時不要忘記引號
//索引
'CREATE INDEX IF NOT EXISTS 索引名 on 表名 (列名稱1, 列名稱2) '
'DROP INDEX 索引名'
indexDB
IndexedDB標(biāo)準(zhǔn)是HTML5官方認(rèn)可的本地數(shù)據(jù)庫解決方案。其目的不是取代服務(wù)器端數(shù)據(jù)庫,它在一些特定場景下很有用,比如離線應(yīng)用。IndexedDB是一種輕量級NOSQL數(shù)據(jù)庫,是由瀏覽器自帶。相比Web Sql更加高效,包括索引、事務(wù)處理和查詢功能。
從上圖可以看出indexDB的兼容性還是不錯的。
使用indexDB
創(chuàng)建/打開一個數(shù)據(jù)庫
首先我們需要創(chuàng)建或者打開一個數(shù)據(jù)庫對象,可以使用window.indexedDB.open()方法,示例如下:
var openRequest =window.indexedDB.open(name, version);
var db;
openRequest.onupgradeneeded = function(e) {
console.log("Upgrading...");}
openRequest.onsuccess = function(e) {
console.log("Success!");
db = e.target.result;
}
openRequest.onerror = function(e) {
console.log("Error");
console.dir(e);
}
第一次打開數(shù)據(jù)庫時,會先觸發(fā)upgradeneeded事件,然后觸發(fā)success事件
open方法返回的是一個對象(IDBOpenDBRequest),回調(diào)函數(shù)定義在這個對象上面
回調(diào)函數(shù)接受一個事件對象event作為參數(shù),它的target.result屬性就指向打開的IndexedDB數(shù)據(jù)庫
創(chuàng)建一個存放數(shù)據(jù)的“對象倉庫”
數(shù)據(jù)庫對象有了,我們還需要創(chuàng)建一個存放數(shù)據(jù)的“對象倉庫”,示例如下:
db.createObjectStore("test", { keyPath: "email" });
db.createObjectStore("test2", { autoIncrement: true });
keyPath表示的是存儲數(shù)據(jù)的鍵名,autoIncrement表示是否使用自動遞增的整數(shù)作為鍵名。一般來說,兩個屬性有一個就可以了。
創(chuàng)建一個數(shù)據(jù)庫事務(wù)對象
transaction方法用于創(chuàng)建一個數(shù)據(jù)庫事務(wù)。向數(shù)據(jù)庫添加數(shù)據(jù)之前,必須先創(chuàng)建數(shù)據(jù)庫務(wù)。
transaction方法返回一個事務(wù)對象,該對象的objectStore方法用于獲取指定的對象倉庫。
var transaction = db.transaction(["firstOS"],"readwrite");
var store = transaction.objectStore("firstOS");
transaction方法接受兩個參數(shù):
第一個參數(shù)是一個數(shù)組,里面是所涉及的對象倉庫,通常是只有一個;
第二個參數(shù)是一個表示操作類型的字符串。readonly(只讀)和readwrite(讀寫);
transaction方法有三個事件,可以用來定義回調(diào)函數(shù)。
abort: 事務(wù)中斷; complete: 事務(wù)完成; error: 事務(wù)出錯。
transaction.oncomplete = function(event) {
// some code
};
操作數(shù)據(jù)
transaction對象提供了一些api,供我們操作數(shù)據(jù)。
添加數(shù)據(jù) add()
獲取對象倉庫以后,就可以用add方法往里面添加數(shù)據(jù)了,示例如下:
var transaction = db.transaction(["firstOS"],"readwrite");
var store = transaction.objectStore(“firstOS”);
var data = {name: 'monkey'};
var request = store.add(data,1);
request.onerror = function(e) {
console.log("Error",[e.target.error.name](http://e.target.error.name));
}
request.onsuccess = function(e) {
console.log("數(shù)據(jù)添加成功!");
}
add方法的第一個參數(shù)是所要添加的數(shù)據(jù),第二個參數(shù)是這條數(shù)據(jù)對應(yīng)的鍵名(key),上面代碼將對象o的鍵名設(shè)為1。如果在創(chuàng)建數(shù)據(jù)倉庫時,對鍵名做了設(shè)置,這里也可以不指定鍵名。
更新數(shù)據(jù) put()
var data = { name: 'monkeysoft' };
var request = store.put(data);
讀取數(shù)據(jù) get()
var request = store.get(key);
刪除數(shù)據(jù) delete()
var request = store.delete(key);
清空數(shù)據(jù)庫 clear()
var request = store.clear();
遍歷數(shù)據(jù) openCursor()
var request = store.openCursor();
歡迎關(guān)注微信公眾號:猴哥說前端