前端要革命?看我在 JS 里寫 SQL
在日新月異的前端領域中,前端工程師能做的事情越來越多,自從nodejs出現后,前端越來越有革了傳統后端命的趨勢,本文就再補一刀,詳細解讀如何在js代碼中執行標準的SQL語句
為什么要在JS里寫SQL?
隨著業務復雜度的增長,前端頁面可能出現一些數據邏輯復雜的頁面,傳統的js邏輯處理起來比較復雜,我們先看兩個例子:
比如多規格多庫存商品界面,難點在于顏色分類、尺碼、價格、庫存、限購數量以及對應的圖片展示之間有復雜的邏輯關系,用戶進行不同的選擇時,js要經過多次復雜的查詢才能算出結果
比如地區聯動查詢界面,難點在于:
-
如何在本地存儲地區數據,顯然每次拉接口是不現實的,如果存儲在storage里,每次使用時,需要有類似JSON.parse類的字符串轉化為數組或對象的過程,這個操作在數據量大的時候,會造成頁面卡頓,性能極差
-
三級地區聯動查詢復雜,如果要從一個縣級地區查詢到所屬的城市和省份,邏輯會比較復雜
上面兩個例子,如果用傳統js邏輯來寫,大家頭腦中必定已經設計好了算法,免不了用forEach、filter、some、find等各種ES678新方法,筆者開始也是用了各種酷炫的新方法寫出來發現有兩個問題:
-
寫完之后邏輯很復雜,似乎沒有100行代碼實現不了(當然有大神比我活兒好)
-
即使寫了一大堆注釋,同事們看起來還是一頭霧水(因為邏輯確實很復雜。。。)
筆者做過一段時間php開發(還做過PM、UI、QA等)忽然想能不能用SQL的方式實現呢?經過一番研究,筆者寫了這樣一個庫:
Database.js
Database.js基于Web SQL Database,那么Web SQL Database又是啥?
Web SQL Database是WHATWG(Web 超文本應用技術工作組,HTML5草案提出方)在2008 年 1 月提出的***份正式草案,但并未包含在 HTML 5 規范之中,它是一個獨立的規范,它引入了一套使用 SQL 操作客戶端數據庫的 API。由于提出時間較早,盡管 W3C 官方在 2011 年 11 月聲明已經不再維護 Web SQL Database 規范,但這些 API 已經被廣泛的實現在了不同的瀏覽器里,尤其是手機端瀏覽器。
兼容情況
Web SQL Database 和 Indexed Database有啥區別?
Indexed Database 更類似于 NoSQL 的形式來操作數據庫 , 其中最重要的是 Indexed Database 不使用 SQL 作為查詢語言。
筆者為了實現在js里面寫SQL的需求,果斷采用了前者作為底層技術。
Web SQL Database 三個核心方法:
-
openDatabase:這個方法使用現有數據庫或新建數據庫來創建數據庫對象
-
transaction:這個方法允許我們根據情況控制事務提交或回滾
-
executeSql:這個方法用于執行SQL 查詢
代碼示例:
var db = openDatabase('testDB', '1.0', 'Test DB', 2 * 1024 * 1024);
var msg;
db.transaction(function (context) {
context.executeSql('CREATE TABLE IF NOT EXISTS testTable (id unique, name)');
context.executeSql('INSERT INTO testTable (id, name) VALUES (0, "Byron")');
context.executeSql('INSERT INTO testTable (id, name) VALUES (1, "Casper")');
context.executeSql('INSERT INTO testTable (id, name) VALUES (2, "Frank")');
});
`對于沒有SQL經驗的前端同學來講,上面代碼看起來顯然有點陌生,也不太友好,于是Database.js誕生了:
筆者以業務當中的一個需求舉例: 轉轉游戲業務列表頁 篩選菜單是一個三級聯動菜單,每個菜單變動都會影響其他菜單數據,如圖:
原始JSON數據結構
可以看出是3級嵌套結構,筆者處理成了扁平化的數據結構(過程略),并分別存入三個數據庫,分別存儲游戲名稱、游戲平臺、商品類型,如下圖:
舉例游戲名稱數據結構如下圖:
通過chrome控制臺Application面板可以直接看到數據庫,結構、數據清晰可見
核心代碼如下:
/**
* 打開數據庫
* @returns {Promise.<void>}
*/
openDataBase(){
//打開數據庫,沒有則創建
db.openDatabase('GameMenu',1,'zzOpenGameMenu').then(res=>{
//檢測數據庫是否存在
db.isExists('game').then(res=>{
//數據庫已經存在,直接使用,將數據交付給頁面UI組件
this.setSelectData()
}).catch(e=>{
//數據庫不存在,請求接口并處理數據,然后存入數據庫
this.getData()
})
}).catch(e=>{
console.err(e)
})
},
/**
* 獲取分類數據并存儲到數據庫
* @returns {Promise.<void>}
*/
async getData(){
//接口請求數據并處理成三個扁平數組
let data = await this.getMenuData()
for(let i in data){
//創建表并存儲數據
db.create(i,data[i])
}
//將數據交付給頁面UI組件
this.setSelectData()
},
當任意菜單選擇變更時,三列數據將重新查詢,核心代碼如下:
/**
* 重新查詢數據
* @param data 點擊菜單攜帶的數據
* @param index 點擊菜單的序號
* @param all 三個菜單當前選中數據
*/
async onSelect(data,index,all){
let target = [],condition = {}
//業務邏輯:處理查詢條件
if(all['0'] && all['0']['name']!=defaultData[0].default.name)condition['gameName'] =all['0']['name']
if(all['1'] && all['1']['name']!=defaultData[1].default.name)condition['platName'] =all['1']['name']
if(all['2'] && all['2']['name']!=defaultData[2].default.name)condition['typeName'] =all['2']['name']
//創建三個查詢任務
let tasks = ['game','plat','type'].map((v,k)=>{
//使用db.select方法查詢
return db.select(v,this.formatCondition(v,condition),'name,value','rowid desc','name').then((res)=>{
target.push({
options:res.data,
defaultOption:defaultData[k].default,
clickHandle:this.onSelect
})
})
})
//執行查詢
await Promise.all(tasks)
//將數據交付給聯動菜單組件使用
this.selectData = target
}
以上代碼即可完成聯動菜單所需要的數據管理工作,看起來是不是比較清晰?
使用Database.js的優勢
1.將數據結構化存儲于Storage中,避免了以文本形式存入Storage或cookie中再解析的性能消耗流程。
2.將復雜數據清晰的在前端進行管理和使用,代碼邏輯更清晰,數據查詢更簡潔!
Database.js使用文檔
openDatabase
-
功能:打開數據庫,不存在則創建
-
語法:openDatabase(dbName,dbVersion,dbDescription,dbSize,callback)
-
參數:
-
dbName:數據庫名
-
dbVersion:數據庫版本(打開已存在數據庫時,版本號必須一致,否則會報錯)
-
dbDescription:數據庫描述
-
dbSize:數據庫預設大小,默認1M
-
callback:回調函數
-
query
-
功能:執行sql語句,支持多表查詢
-
語法:query(sqlStr,args = [],callback,errorCallback)
-
參數:
-
sqlStr:sql語句
-
args(Array):傳入的數據,替換sql中的?符號
-
callback:成功回調
-
errorCallback:失敗回調
-
-
示例:
//插入數據
db.query('INSERT INTO testTable(id,title) VALUES (?,?)',[1,'這是title'])
//多表查詢
db.query('select game.*,plat.* from game left join plat on game.name = plat.gameName')
isExists
-
功能:檢測表是否存在
-
語法:isExists(tableName)
-
參數:
-
tableName:表名
-
createTable
-
功能:創建一張表
-
語法:createTable(tableName,fields)
-
參數:
-
tableName:表名
-
fields:表結構(需指定字段類型)
-
-
示例:
db.createTable('testTable',{
name:'varchar(200)',
price:'int(100)'
})
insert
-
功能:插入一條或多條數據
-
語法:insert(tableName,data)
-
參數:
-
tableName:表名
-
data(Object or Array):插入的數據,多條數據請傳入數組類型
-
-
示例:
//插入單條
db.insert('testTable',{
name:'商品1',
price:10
})
//插入多條
db.insert('testTable',[
{name:'商品1',price:10},
{name:'商品2',price:20},
{name:'商品3',price:30},
])
將數據存入數據庫的常規流程是先createTable,然后再insert,如果你覺得這樣麻煩,可以試一下create方法:
create
-
功能:直接創建數據庫并存入數據
-
注意:類庫會根據傳入的數據類型自動設置數據庫的字段類型,這樣可以覆蓋大多數需求,但如果你的數據中,同一個字段中有不同的數據類型,有可能不能兼容,建議還是使用常規流程手動設置類型
-
語法:create(tableName,data)
-
參數:
-
tableName:表名
-
data(Object or Array):插入的數據,多條數據請傳入數組類型
-
-
示例:
//直接創建表并存儲
db.create('testTable',[
{name:'商品1',price:10},
{name:'商品2',price:20},
{name:'商品3',price:30},
])
delete
-
功能:刪除數據
-
語法:delete(tableName,condition)
-
參數:
-
tableName:表名
-
condition(String or Obejct):查詢條件
-
-
示例:
//刪除一條數據
db.delete('testTable',{name:'商品1'})
關于condition: 1、傳入array形式時,默認查詢條件連接方式是AND,如果需要用OR等方式,可以在condition中傳入 logic設定,例如{ logic:'OR'} 2、如果查詢條件有AND、OR等多種方式,建議使用string方式傳入
select
-
功能:查詢數據
-
注意:如果需要多表查詢,可參照query方法
-
語法:select(tableName,condition = '',fields = '*',order = '',group = '',limit = '')
-
參數:
-
tableName:表名
-
condition(String or Obejct):查詢條件
-
fields(String or Array):返回字段,默認*,支持distinct
-
order(String or Array):排序規則
-
group(String or Array):分組規則
-
limit(String or Array):分頁規則
-
-
示例:
//查詢name=商品1的數據,并按照price倒序
db.select('testTable',{
name:'商品1'
},'*','price desc')
//查詢價格大于0的商品,并用distinct關鍵字去重
db.select('testTable',{
price:'>0'
},'distinct name,pirce','price desc')
update
-
功能:更新數據
-
語法:update(tableName,data,condition = '')
-
參數:
-
tableName:表名
-
data(String or Obejct):更改數據
-
condition(String or Obejct):查詢條件
-
-
示例:
//將商品1的價格改為99
db.update('testTable',{
price:99
},{
name:'商品1'
})
truncate
-
功能:清空表
-
語法:truncate(tableName)
-
參數:
-
tableName:表名
-
drop
-
功能:刪除表
-
語法:drop(tableName)
-
參數:
-
tableName:表名
-
如何使用Database.js
Github地址:https://github.com/zhangsuoyong/Database.js