在日新月异的前端领域中,前端工程师能做的事情越来越多,自从nodejs出现后,前端越来越有革了传统后端命的趋势,本文就再补一刀,详细解读如何在js代码中执行标准的sql语句
为什么要在js里写sql?随着业务复杂度的增长,前端页面可能出现一些数据逻辑复杂的页面,传统的js逻辑处理起来比较复杂,我们先看两个例子:
比如多规格多库存商品界面,难点在于颜色分类、尺码、价格、库存、限购数量以及对应的图片展示之间有复杂的逻辑关系,用户进行不同的选择时,js要经过多次复杂的查询才能算出结果
比如地区联动查询界面,难点在于:
如何在本地存储地区数据,显然每次拉接口是不现实的,如果存储在storage里,每次使用时,需要有类似json.parse类的字符串转化为数组或对象的过程,这个操作在数据量大的时候,会造成页面卡顿,性能极差
三级地区联动查询复杂,如果要从一个县级地区查询到所属的城市和省份,逻辑会比较复杂
上面两个例子,如果用传统js逻辑来写,大家头脑中必定已经设计好了算法,免不了用foreach、filter、some、find等各种es678新方法,笔者开始也是用了各种酷炫的新方法写出来发现有两个问题:
写完之后逻辑很复杂,似乎没有100行代码实现不了(当然有大神比我活儿好)
即使写了一大堆注释,同事们看起来还是一头雾水(因为逻辑确实很复杂。。。)
笔者做过一段时间php开发(还做过pm、ui、qa等)忽然想能不能用sql的方式实现呢?经过一番研究,笔者写了这样一个库:
database.jsdatabase.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:失败回调
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的方法的详细内容。
