介紹
LokiJS是一個面向文檔的javascript數據庫,與MongoDB有點相似。
它支持索引,查詢和過濾數據集合。 LokiJS還支持更高級的功能,例如mapReduce,事務,並允許您實現自定義遠程同步以將數據保存到服務器(或移動設備上的本地文件)。
磁盤的持久性已經在諸如nodejs之類的CommonJS環境中實現,
在移動設備上,您只需要請求文件系統並將lokijs的serialize()作為內容傳遞即可。
創建
創建一個 db:
var db = new loki('Example');
除了 Example
這個數據庫的名稱外, 還可以傳遞一個選項參數:
interface LokiConfigOptions {
adapter: LokiPersistenceAdapter | null;
autoload: boolean;
autoloadCallback: (err: any) => void;
autosave: boolean;
autosaveCallback: (err?: any) => void;
autosaveInterval: string | number;
persistenceMethod: "fs" | "localStorage" | "memory" | null;
destructureDelimiter: string;
serializationMethod: "normal" | "pretty" | "destructured" | null;
throttledSaves: boolean;
}
關於參數: persistenceMethod 需要注意的點:
- "fs": 只能在 node 環境中使用(包括 electron)
- "localStorage" : web 中可以使用,並且數據會存在
localStorage
中,進行持久存儲 - "memory": 只是簡單的存在內容中,如果刷新頁面,數據就不存在了
在不同的環境中, 他創建的持久層是不同的, 比如
在瀏覽器中, 是基於 web 數據庫或者 localStorage
(默認) 的
在 node 環境中, 可以基於 fs, 創建文件數據庫
關於他們的變化是需要一個 option.adapter
來進行協助
在 web 中, 如果想要將數據存到 web 數據庫中,
那么就需要 option.adapter
了(當傳遞了 adapter
參數時, 'persistenceMethod' 參數就會被無效)
但是如果使用了 option.adapter
,那么只能使用自動加載數據庫和字段保存數據庫的方案
增加數據
針對 loki 里的 Collection
如果了解數據庫,可以將它當成 表
這種結構
想要添加數據,需要先獲取 collection
對象
可以在添加 collection
的時候獲取:
const users = db.addCollection('users', {indices: ['email']});
可以直接獲取:
const coll = db.getCollection('users')
注意 addCollection
有 2 個參數可以傳遞:
-
第一個參數是 name 是 collection 的名稱
-
第二個是可選項參數,它擁有很多參數:
名稱 | 類型 | 屬性 | 默認值 | 描述 |
---|---|---|---|---|
unique |
數組 | <可選> | [] | 屬性名稱數組,用於定義唯一約束 |
exact |
數組 | <可選> | [] | 屬性名稱數組,用於定義確切的約束 |
indices |
數組 | <可選> | [] | 用於定義二進制索引的數組屬性名稱 |
adaptiveBinaryIndices |
布爾值 | <可選> | true | 收集索引將被重新建立而不是懶加載 |
asyncListeners |
布爾值 | <可選> | false | 偵聽器是否異步調用 |
disableMeta |
布爾值 | <可選> | false | 設置為true以禁用文檔的元屬性 |
disableChangesApi |
布爾值 | <可選> | true | 設置為false以啟用Changes API |
disableDeltaChangesApi |
布爾值 | <可選> | true | 設置為false以啟用Delta更改API(需要更改API,強制克隆) |
autoupdate |
布爾值 | <可選> | false | 使用Object.observe自動更新對象 |
clone |
布爾值 | <可選> | false | 指定是否向用戶插入或從用戶克隆查詢 |
serializableIndices |
布爾值 | <可選> | true[] | 將二進制索引屬性上的日期值轉換為紀元時間 |
cloneMethod |
字符串 | <可選> | 'parse-stringify' | 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign' |
ttl |
int | <可選> | 文件被認為是陳舊/過時之前的文件時間(以毫秒為單位)。 | |
ttlInterval |
int | <可選> | 清除“陳舊”文件的時間間隔;默認情況下未設置。 |
使用 inert 添加數據
const coll = db.getCollection('users')
var odin = users.insert({name: 'odin', email: 'odin.soap@lokijs.org', age: 38});
var thor = users.insert({name: 'thor', email: 'thor.soap@lokijs.org', age: 25});
var stan = users.insert({name: 'stan', email: 'stan.soap@lokijs.org', age: 29});
// 也可以同時插入多個數據
// users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);
這里要注意的是: 分清楚數據的存儲狀態, 當我們不使用自動保存和手動保存的時候 insert, 會將數據插入 collection 中, 但是當我們刷新頁面的時候,數據會重置會原來的數據,
如果我們要將數據全部存下來(即使刷新也會存在的話), 就需要保存:
// var db = new loki('Example'); 這是 db 的由來
db.saveDatabase(error => {
console.log('保存數據')
error && console.log(error)
})
獲取數據:
獲取數據是比較靈活的,我這里說兩種方法:
方法一:
const dv = coll.addDynamicView('test');
const results = dv.data();
console.log(results)
// 這是results打印結果
// 0: {name: "odin", email: "odin.soap@lokijs.org", age: 38, meta: {…}, $loki: 1}
// 1: {name: "thor", email: "thor.soap@lokijs.org", age: 25, meta: {…}, $loki: 2}
// 2: {name: "stan", email: "stan.soap@lokijs.org", age: 29, meta: {…}, $loki: 3}
// 3: {name: "oliver", email: "oliver.soap@lokijs.org", age: 31, meta: {…}, $loki: 4}
// 4: {name: "hector", email: "hector.soap@lokijs.org", age: 15, meta: {…}, $loki: 5}
// 5: {name: "achilles", email: "achilles.soap@lokijs.org", age: 31, meta: {…}, $loki: 6}
方法二:
const resultsLine = coll.chain().data();
console.log(resultsLine)
// 結果與方法一相同
獲取數據時篩選想要的數據:
方法 1 的篩選:
find
// 通過 coll 直接獲取
const results4 = coll.find({'age': {'$aeq': 15}});
console.log('獲取數據 4',results4)
// 可使用不同的指令:
// 指令名 作用
// $eq ===
// $ne !==
// $aeq ==
// $dteq 時間上的相等
// $gt >
// $gte >=
// $lt <
// $lte <=
// $between 介於 2 個數之間
// 如果不希望使用二進制索引,並且希望簡單的javascript比較是可以接受的,我們提供以下操作,由於它們的簡化比較,它們可以提供更好的執行速度。
// $gt -> $jgt
// $gte -> $jgte
// $lt -> $jlt
// $lte -> $jlte
// $between -> $jbetween
// $regex 使用正則
applyFind
const dv = coll.addDynamicView('test');
dv.applyFind({ 'name' : 'odin' });
const results = dv.data();
applyWhere
dv2.applyWhere(function(obj) { return obj.name === 'oliver'; });
// 作用與上述方法相同
applySimpleSort
// 根據年齡進行排序
const dv3 = coll.addDynamicView('test3');
dv3.applySimpleSort("age");
const results3 = dv3.data();
console.log(results3)
findOne
const results5 = coll.findOne({'age': {'$aeq': 31}});
// 獲取到的是對象 而不是一個數組
console.log('獲取數據 5',results5)
findObject
const results6 = coll.findObject({'age': 38});
// 使用的結果和 findOne 類似
console.log('獲取數據 6',results6)
findObjects
const results7 = coll.findObjects({'age': 31});
// 返回的是一個數組
console.log('獲取數據 7',results7)
比較推薦的是使用 addDynamicView
的方式來篩選,而不是通過 collection
直接獲取
需要注意的是 DynamicView
是一個數據格式,他可以 add 可以 get 也可以 remove
方法 2 的篩選:
// 簡單的篩選
const resultsLine2 = coll.chain().find({ 'name' : 'odin' }).data();
// 排序:
const resultsLine3 = coll.chain().simplesort('age').data();
當然 chain 里還有其他操作,如: limit, map, findAnd, eqJoin, count等等,
我是更推薦使用第一種方法,這里的幾種使用方案我就不詳細舉例了
還有不建議使用 chain 的 update,remove 等操作,因為監聽器里面會監聽不到事件,
這個問題不知道是故意這么做 還是 bug
修改數據:
update
與 insert 同理:
// 要修改 就需要先獲取要修改的東西是什么
const item = coll.findOne({'age': {'$aeq': 31}});
item.age = 18
coll.update(item);
console.log(coll.chain().data())
// 打印發現名字為 odin 的年齡已經改成了 18
// 當然想要持久化就得保存數據庫:
db.saveDatabase(error => {
console.log('保存數據')
error && console.log(error)
})
findAndUpdate
coll.findAndUpdate({'age': {'$aeq': 25}}, data => {
// 原名"thor"
data.name = 'grewer'
return data
})
// 獲取並且修改 集中在同一個方法里面
console.log('修改結果 2', coll.chain().data())
updateWhere
coll.updateWhere(data => {
return data.name === 'grewer';
}, data => {
data.age = '999'
return data
})
// 與上面的類似,但是更加自由,而且還可以是用 `{'age': {'$aeq': 15}}` 這種方法來獲取
刪除數據:
remove
刪除數據也是非常簡單的(與更新類似):
const item2 = coll.findOne({'age': {'$aeq': 31}});
coll.remove(item2);
console.log(coll.chain().data())
findAndRemove
coll.findAndRemove({'age': {'$aeq': 15}})
// 同 findAndUpdate, 集中了 find 和 remove
console.log('修改結果 2', coll.chain().data())
removeWhere
// 同 updateWhere
coll.removeWhere((value,index)=>{
return index === 1
})
console.log('刪除結果 3', coll.chain().data())
添加操作的監聽:
Loki 的 DB 支持自定義事件,使用如下:
// 添加自定義 grewer 事件
db.addListener('grewer',(data) => {
console.log('grewer事件', data)
})
// 觸發事件
db.emit('grewer','qwerty')
Loki 支持對 collection
添加操作的監聽, 監聽的事件支持以下事件
close
delete
error
flushbuffer
insert
pre-insert
pre-update
update
warning
使用:
coll.on('update', (event) => {
console.log('coll change 事件', event)
})
// inset, delete 等其他事件同理
在我們使用 update/findAndUpdate/updateWhere
的時候就會自動觸發此回調了
關於 Collection transforms
他的官方介紹是這樣的:
轉換背后的基本思想是允許將結果集“鏈”過程轉換為該過程的對象定義。然后可以選擇命名該數據定義,並將其與集合一起保存在數據庫中。
一個簡單的使用:
var tx = [
{
type: 'find',
value: {
'name': 'oliver'
}
}
];
console.log(coll.chain(tx).data())
// 打印結果:
[{
$loki: 4
age: 18
email: "oliver.soap@lokijs.org"
meta: {...}
name: "oliver"
}]
關於他的使用,感覺像是其他數據庫里面的schema
, 我這里也沒碰到過具體的情況,所以了解不夠深刻
其他數據庫功能:
連表查詢:
public eqJoin(
joinData: Collection<any> | Resultset<any> | any[],
leftJoinProp: string | ((obj: any) => string),
rightJoinProp: string | ((obj: any) => string),
mapFun?: (left: any, right: any) => any,
dataOptions?: Partial<GetDataOptions>
): Resultset<any>;
一個簡單的使用例子:
// 創建另一個 collection(表)
var collection = db.addCollection("test", {
unique: ["name"]
});
collection.insert({owner: 0, name: 'Betsy'});
collection.insert({owner: 1, name: 'Bingo'});
collection.insert({owner: 2, name: 'Fifi'});
collection.insert({owner: 3, name: 'Fuzzy'});
collection.insert({owner: 4, name: 'Gizmo'});
// 這是另一個表
// 進行查詢:
const resultSet = coll.eqJoin(collection.chain(), 'id', 'owner')
// 當 id 和 owner 相等時 數據會被連接
console.log('連表', resultSet.data())
// 打印一下 console
[{
$loki: 1
left: {name: "odin", id: 0, email: "odin.soap@lokijs.org", age: 38, meta: {…}, …}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {owner: 0, name: "Betsy", meta: {…}, $loki: 1}
},
{
$loki: 2
left: {name: "grewer", id: 1, email: "thor.soap@lokijs.org", age: "999", meta: {…}, …}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {owner: 1, name: "Bingo", meta: {…}, $loki: 2}
},
{
$loki: 3
left: {name: "stan", email: "stan.soap@lokijs.org", age: 29, meta: {…}, $loki: 3}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 4
left: {name: "oliver", email: "oliver.soap@lokijs.org", age: 18, meta: {…}, $loki: 4}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 5
left: {name: "hector", email: "hector.soap@lokijs.org", age: 15, meta: {…}, $loki: 5}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 6
left: {name: "achilles", email: "achilles.soap@lokijs.org", age: 31}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
}]
這就是最簡單的連表使用
還有一些沒說到的,但是也就是邊邊角角的東西了,基本就是這些方法的使用
寫在最后
Loki 擁有 adapter 使得他的適用性特別高,但是相對詳細的使用卻比較少,所以我寫了這篇相對詳細一點的文章來記錄此數據庫的相關操作
關於官方文檔地址:
http://techfort.github.io/LokiJS/
還有這篇文章里面的所有例子的地址:
https://github.com/Grewer/JsDemo/blob/master/lokijs/index.html