在日新月異的前端領域中,前端工程師能做的事情越來越多,自從nodejs出現後,前端越來越有革了傳統後端命的趨勢,本文就再補一刀,詳細解讀如何在js程式碼中執行標準的SQL語句
為什麼要在js裡寫SQL?
隨著業務複雜度的成長,前端頁面可能會出現一些資料邏輯複雜的頁面,傳統的js邏輯處理起來比較複雜,我們先看兩個例子:
例如多規格多庫存商品介面,難點在於顏色分類、尺寸、價格、庫存、限購數量以及對應的圖片展示之間有複雜的邏輯關係,用戶進行不同的選擇時,js要經過多次複雜的查詢才能算出結果
#例如地區聯動查詢介面,難度在於:
如何在本地儲存地區數據,顯然每次拉接口是不現實的,如果存儲在storage裡,每次使用時,需要有類似JSON.parse類的字符串轉化為數組或對象的過程,這個操作在資料量大的時候,會造成頁面卡頓,效能極差
#三級地區連動查詢複雜,如果要從一個縣級地區查詢到所屬的城市與省份,邏輯會比較複雜
上面兩個例子,如果用傳統js邏輯來寫,大家腦中必已經設計好了演算法,免不了用forEach、filter、some、find等各種ES678新方法,筆者開始也是用了各種酷炫的新方法寫出來發現有兩個問題:
寫完之後邏輯很複雜,似乎沒有100行程式碼實現不了(當然有大神比我活兒好)
即使寫了一大堆註釋,同事們看起來還是一頭霧水(因為邏輯確實很複雜。。。)
筆者做過一段時間php開發(還做過PM、UI、QA等)忽然想能不能用SQL的方式實作呢?經過一番研究,筆者寫了這樣一個函式庫:
#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.将复杂数据清晰的在前端进行管理和使用,代码逻辑更清晰,数据查询更简洁!
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:失败回调
1 //插入数据 2 db.query('INSERT INTO testTable(id,title) VALUES (?,?)',[1,'这是title']) 3 4 //多表查询 5 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:表结构(需指定字段类型)
示例
1 db.createTable('testTable',{ 2 name:'varchar(200)', 3 price:'int(100)' 4 })
insert
功能:插入一条或多条数据
语法:insert(tableName,data)
参数:
tableName:表名
data(Object or Array):插入的数据,多条数据请传入数组类型
示例: javascript //插入单条 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):插入的数据,多条数据请传入数组类型
示例:
1 //插入数据 2 db.query('INSERT INTO testTable(id,title) VALUES (?,?)',[1,'这是title']) 3 4 //多表查询 5 db.query('select game.*,plat.* from game left join plat on game.name = plat.gameName')
delete
功能:删除数据
语法:delete(tableName,condition)
参数:
tableName:表名
condition(String or Obejct):查询条件
示例:
1 //删除一条数据 2 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):分页规则
示例:
1 //查询name=商品1的数据,并按照price倒序
**update** - 功能:更新数据 - 语法:update(tableName,data,condition = '') - 参数: - tableName:表名 - data(String or Obejct):更改数据 - condition(String or Obejct):查询条件 - 示例:
1 //将商品1的价格改为99 2 db.update('testTable',{ 3 price:99 4 },{ 5 name:'商品1' 6 })
truncate
功能:清空表
语法:truncate(tableName)
参数:
tableName:表名
drop
功能:删除表
语法:drop(tableName)
参数:
tableName:表名
以上是在js裡寫SQL的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!