前端不少時候仍是須要保存一些數據的,這裏的保存指的是長久的保存。之前的思想是把數據保存在cookie中,或者將key保存在cookie中,將其餘數據保存在服務器上。javascript
這幾個場景用處不少,也很是的成熟好用。可是我仍是想要一種可以長久的保存在本地的數據,相似數據庫或者相似web sql。前端
新一代瀏覽器基本都支持了本地數據庫,須要用的時候直接使用就行了。實在不行還可使用Storage將就一下。java
假如我須要的是一個數據庫那種功能而且沒有兼容的存儲方式呢?加入我還要加密存儲這些東西在本地呢?加入我要存的東西很是多呢?react
目前我在使用ReactNative的時候確實遇到了這種狀況。我須要將很是多的數據存在本地。有人說了,直接使用sqlite不就行了麼?web
好啊,徹底能夠啊。我這裏僅僅是本着前端的態度去開發而已。假若有的同窗不須要sqlite呢,只須要一個簡單的,可以存儲一些大量數據的方式呢?redis
使用場景可能有不少,實現的底層也能夠隨便替換,甚至實現方式均可以隨便寫寫。這裏我本着前端創造世界的態度來作一個非正式的、前端好使的數據存儲庫。sql
這裏的使用場景是ReactNative,因此我使用的是RN的AsyncStorage。數據庫
將全部要保存的數據轉成對象,並轉化爲字符串。這裏的核心思想就是序列化。將全部數據當成字符串來存。npm
import { AsyncStorage } from 'react-native';
exports.setItem = async (key, value) => {
let item = JSON.stringify({
v: value
});
return await AsyncStorage.setItem(key, item);
}
複製代碼
當讀取的時候也須要作一次轉化,將字符串轉成當初存入的數據。json
exports.getItem = async (key) => {
let item = await AsyncStorage.getItem(key);
if (!item) {
return null;
}
return JSON.parse(item).v || null;
}
複製代碼
須要特殊處理的是列表的獲取。RN有一個根據多個key返回多條數據的api。它返回的是一個數組對象,數組序號0是數據存儲的key值,序號1纔是數據存儲的具體字符串。
exports.getlist = async (keys) => {
let list = await AsyncStorage.multiGet(keys);
list = list.map(item => JSON.parse(item[1]).v || null);
return list;
}
複製代碼
其餘用到的幾個方法也順便拿出來吧。這裏多嵌套一層,跟上面的保持隊形。
exports.removeItem = async (key) => await AsyncStorage.removeItem(key);
exports.removeItems = async (keys) => await AsyncStorage.multiRemove(keys);
exports.allKeys = async () => await AsyncStorage.getAllKeys();
複製代碼
上面只是簡單的實現,若是沒有特殊需求也就差很少了。可是,想要更進一步的能夠考慮開始優化了。
好比,優化json轉化的速度。咱們使用JSON對象的方法去轉化的時候其實還有一個判斷數值類型的過程。若是咱們把數據的類型提早定義好。再轉化的時候就不須要再判斷了。
能夠定義一個model對象,將這個表須要的字段預先定義好。能夠查看一下Sequelize的定義方式。按照關係行數據庫的方式來搞這個事情就很簡單了。
//用戶對象
const users = db.define('t_users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
},
//用戶名
username: {
type: Sequelize.STRING
},
//密碼
pwd: {
type: Sequelize.STRING
},
//狀態
status: {
type: Sequelize.INTEGER
},
//暱稱
nickname: {
type: Sequelize.STRING
},
//token
token: {
type: Sequelize.STRING
},
create_time: {
type: Sequelize.TIME
}
}, {
freezeTableName: true,
timestamps: false,
});
複製代碼
咱們這裏參考一下關係型數據庫的實現。
首先須要分表和分庫。這樣在存入數據的時候能夠更少的關注這方面的信息,將主要精力放在數據操做上。
constructor(tableName = "table", db = "db") {
//檢查庫,表是否存在
//初始化索引表
this.db = db;
this.tableName = tableName;
this.tableKey = db + "_" + tableName;
this.init();
}
複製代碼
將它們分開存儲在當前對象內部,在建立對象的時候就能夠根據不一樣的庫、表建立不一樣的操做方法。這裏使用的是class,每一個表都對應一個單獨的對象。
因爲咱們使用的是RN提供的存儲方法,因此這裏的添加和更新實際上是一個方法。在添加的時候會根據當前時間戳建立一個惟一id,使用這個id做爲key存儲在數據庫中。因此在使用的時候不須要再單獨存入id,不過若是你以爲這個id跟你須要的有差異也能夠本身定義一個id來做爲key值存儲。
//添加和更新
async add(data = {}) {
if (data.constructor !== Object) return;
if (!data._id)data._id = uuid();
await setItem(this.tableKey + "_" + data._id, data);
return data;
}
複製代碼
在獲取的時候單獨提供了一個根據id獲取的方式。這裏考慮的是經過id獲取很是的簡單方便,對於某些數據徹底能夠快速讀取,不必一行一行的去查詢。
/** * 經過id查詢 * @param {*} id */
async getById(id) {
if (!id) return {};
return await getItem(this.tableKey + "_" + id);
}
複製代碼
相對於根據id查詢來講,模糊查詢確實很慢,若是不是真實須要,仍是不要使用這種模糊查詢的好。這裏提供了一個自定義查詢的方法,能夠根據返回的對象判斷是否須要這行數據。同時也能夠添加top參數來限制返回的數量。使用這個參數也能夠在數據不少的時候提升性能。
/** * 經過過濾方法查詢 * @param {*} fn */
async get(fn, top = 0) {
let keys = await allKeys();
if (keys.length == 0) return [];
if (top > 0 && keys.length > top) keys.length = top;
const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0);
if (listkey.length == 0) return [];
let list = await getlist(listkey);
list = list.filter(item => fn(item));
return list;
}
複製代碼
最後把刪除和清空的方法加上,這樣一個簡單的刪除庫就完成了。
/** * 刪除 * @param {*} id */
async delete(id) {
if (!id) return {};
await removeItem(this.tableKey + "_" + id);
}
/** * 清空表 */
async clear() {
let keys = await allKeys();
const listkey = keys.filter(item => item.indexOf(this.tableKey + "_") === 0);
if (listkey.length == 0) return;
removeItems(listkey);
}
複製代碼
使用的時候只須要建立對象,而後在須要的地方調用便可。使用起來簡單又方便,再加上優化以後的狀況甚至能夠當成客戶端的redis來使用。
//初始化數據庫
let db=new JSDB();
//添加一個自定義數據
db.add({name:"test",key:"abc"});
//根據id獲取數據
db.getById("1223467890");
//根據條件查詢數據
db.get(d=>d.name==="test");
//刪除對應的數據
db.delete("1223467890");
//狀況全部數據
db.clear()
複製代碼
首先要優化的就是對象的建立。每一個對象建立其實都是一個很大的消耗,若是能把這個消耗下降豈不是美滋滋!
這裏咱們借鑑數據庫池的概念,實現一個對象池的方法。在對象建立以後並無直接返回,要在通過池的操做。
將對象放入池內,並在頁面銷燬的時候重置爲一個空對象。下次請求建立的時候就沒必要再建立新的了。直接賦值表、庫的名稱就可使用了。內存毫無變化,而且有點想笑。
每次查詢都須要去讀Stroage仍是很麻煩的,尤爲這個操做是異步操做,是須要發消息到native端的。
咱們能夠將上次讀取的數據先存在一個變量中,若是下次還須要使用這行數據,就徹底不須要再去讀取了。這樣就能夠很簡單的提供讀取速度。
這個方式還能夠繼續優化。將變量中保存的數據限制數量,防止數量太多超過了APP的限制。還能夠將這個保存的時限作一個邏輯判斷,常使用的數據放在裏面,不經常使用的就找機會刪除。
使用這種方式也能夠優化變量中數據的有效性,減小變量佔用內存的大小。不過實現的方式儘可能不要使用定時器的形式,能夠考慮使用觸發式的。在條件知足的時候再觸發刪除動做。
上面提到讀取的時候須要放入變量來提升讀取速度。咱們順便想到寫入的速度是否是也能夠提升啊?
咱們將要存的數據放在臨時的變量裏,若是查過咱們設置的時間或者數據長度查過咱們設置的數量就觸發一次保存操做。
這裏要注意,保存數據的變量和存入時候使用的變量要交替使用,防止出現丟數據的狀況。
好比:存的時候使用變量1,在寫到數據庫以前,將要存的對象改爲變量2,而後讀取變量1的數據並存入數據庫中。這就是雙緩存寫入。
固然仍是要判斷一次APP的退出事件的,若是APP退出了,請必定要把變量中的內容保存一次,否則你的心血就全丟了。
好了,一個簡單的數據庫就完成了。想要使用的能夠先在npm上搜索react-native-jsdb
這個庫。我已經將第一部分實現放在了npm上,後續的優化也會滿滿的開源出來的。