前端要革命?看我在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):插入的数据,多条数据请传入数组类型
- 示例:
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):插入的数据,多条数据请传入数组类型
示例:
//直接创建表并存储 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
如果你有更好的想法,欢迎与我交流,个人微信号:king109400214
更多前端新鲜文章,请关注我司公众号“大转转FE”
如果你喜欢我们的文章,关注我们的公众号和我们互动吧。

前端要革命?看我在js里写SQL的更多相关文章
- asp.net尽量不在js里写<%%>
asp.net尽量不在js里写<%%> eg: <script type="text/javascript"> var rootsid="&quo ...
- 在php里写sql查询需要注意的事情
---恢复内容开始--- 今天往php里写了一条sql查询, $sql = "select * from videos where vuser=".$u: $ret = mysql ...
- 怎么在js里写html
<html> <head> <meta charset="utf-8"/> <title>示例前端模板写在代码里</title ...
- js里写网页结构, 传函数参数
如题 "<td align='center' height='30px' width='80px'><a href='javascript:sort(\"&quo ...
- js里写html代码 啥时候要用“\"转义
当去掉\的时候 字体变黑 需要加\
- web前端开发面试题(Vue.js)
1.active-class是哪个组件的属性?嵌套路由怎么定义? 答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么获取传过来的动态参数? ...
- 【一统江湖的大前端(8)】matter.js 经典物理
目录 [一统江湖的大前端(8)]matter.js 经典物理 一.经典力学回顾 二. 仿真的实现原理 2.1 基本动力学模拟 2.2 碰撞模拟 三. 物理引擎matter.js 3.1 <愤怒的 ...
- 理解 Node.js 里的 process.nextTick()
有很多人对Node.js里process.nextTick()的用法感到不理解,下面我们就来看一下process.nextTick()到底是什么,该如何使用. Node.js是单线程的,除了系统IO之 ...
- 正则表达式,js里的正则应用
我爱撸码,撸码使我感到快乐!大家好,我是Counter.好吧已经到凌晨了,其实还是蛮困的,体力不支了,想了想还是把今天任务结束掉吧,为期5天,又重新把JavaScript以及jQuery给大致过了一遍 ...
随机推荐
- webpack配置这一篇就够
最近看了一篇好文,根据这个文章重新梳理了一遍webpack打包过程,以前的一些问题也都清楚了,在这里分享一下,同时自己也做了一些小的调整 原文链接:http://www.jianshu.com/p/4 ...
- 【★】IT界8大恐怖预言
IT界的8大恐怖预言 本文字数:3276 建议阅读时间:你开心就好 第三次科技革命已经进入白热化阶段---信息技术革命作为其中最主要的一环已经奠定了其基本格局和趋势.OK大势已定,根据目前的形势,小编 ...
- 【转】为什么选择Spring Boot作为微服务的入门级微框架
本文为普元云计算高级工程师许二虎在普元云计算架构设计群的微课堂分享.如需加入普元新一代数字化企业云平台研发设计群参与微课堂.架构设计与讨论直播,请直接回复此公众号:"加群 姓名 公司 职位 ...
- 1001.A+B Format (20)的解题
关于A+B的正确打开方式! 解题思路 gitub 也是研究了很久才学会了本地上传,中间还遇到一些问题,多亏学长的教程跟搜索引擎的帮忙解决啦! 我想还是了解题目的意思是解题的最关键,通过了查词软件跟自身 ...
- 团队作业8——Beta 阶段冲刺1st day
一.今日站立式会议照片 二.每个人的工作 (1) 昨天已完成的工作: 今天是冲刺的第一天,昨天完成的是团队成员任务的分配 (2) 今天计划完成的工作: 界面的完善 (3) 工作中遇到的困难: 对于界面 ...
- 团队作业8——第二次项目冲刺(Beta阶段)5.22
1.当天站立式会议照片 会议内容: ①:检查总结上次任务完成情况 ②:安排本次任务的分工 ③:反思前三次自己的不足 ④:协商解决代码进度.成员投入时间等问题 2.每个人的工作 工作中遇到的困难: 代码 ...
- 201521123014 《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 1.2 可选:使用常规方法总结其他上课内容. GUI与Sw ...
- Android事件机制
一句话描述: 用户和程序之间的互动机制 什么是事件? 用户和程序交互时触发的程序操作. 只要是事件,必须具备三方面: 1 事件的发生者 2 事件接受者 3 事件触发和传递 事件处理的方法 观察者模式: ...
- 201521123016 《Java程序设计》第9周学习总结
1. 本周学习总结 2. 书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避 ...
- python日记_01 python实现6个人围成一圈,扔到第三个人出局,循环扔的问题。
#!/usr/bin/python shoplist=['mango','apple','carrot','banana','oracle','python'] length = len(shopli ...
