在日新月异的前端领域中,前端工程师能做的事情越来越多,自从nodejs出现后,前端越来越有革了传统后端命的趋势,本文就再补一刀,详细解读如何在js代码中执行标准的SQL语句

为什么要在js里写SQL?

随着业务复杂度的增长,前端页面可能出现一些数据逻辑复杂的页面,传统的js逻辑处理起来比较复杂,我们先看两个例子:

比如多规格多库存商品界面,难点在于颜色分类、尺码、价格、库存、限购数量以及对应的图片展示之间有复杂的逻辑关系,用户进行不同的选择时,js要经过多次复杂的查询才能算出结果

比如地区联动查询界面,难点在于:

  1. 如何在本地存储地区数据,显然每次拉接口是不现实的,如果存储在storage里,每次使用时,需要有类似JSON.parse类的字符串转化为数组或对象的过程,这个操作在数据量大的时候,会造成页面卡顿,性能极差
  2. 三级地区联动查询复杂,如果要从一个县级地区查询到所属的城市和省份,逻辑会比较复杂

上面两个例子,如果用传统js逻辑来写,大家头脑中必定已经设计好了算法,免不了用forEach、filter、some、find等各种ES678新方法,笔者开始也是用了各种酷炫的新方法写出来发现有两个问题:

  1. 写完之后逻辑很复杂,似乎没有100行代码实现不了(当然有大神比我活儿好)
  2. 即使写了一大堆注释,同事们看起来还是一头雾水(因为逻辑确实很复杂。。。)

笔者做过一段时间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 查询

代码示例:

  1. var db = openDatabase('testDB', '1.0', 'Test DB', 2 * 1024 * 1024);
  2. var msg;
  3. db.transaction(function (context) {
  4. context.executeSql('CREATE TABLE IF NOT EXISTS testTable (id unique, name)');
  5. context.executeSql('INSERT INTO testTable (id, name) VALUES (0, "Byron")');
  6. context.executeSql('INSERT INTO testTable (id, name) VALUES (1, "Casper")');
  7. context.executeSql('INSERT INTO testTable (id, name) VALUES (2, "Frank")');
  8. });

对于没有SQL经验的前端同学来讲,上面代码看起来显然有点陌生,也不太友好,于是Database.js诞生了:

笔者以业务当中的一个需求举例: 转转游戏业务列表页筛选菜单是一个三级联动菜单,每个菜单变动都会影响其他菜单数据,如图:

原始JSON数据结构

可以看出是3级嵌套结构,笔者处理成了扁平化的数据结构(过程略),并分别存入三个数据库,分别存储游戏名称、游戏平台、商品类型,如下图:

举例游戏名称数据结构如下图:

通过chrome控制台Application面板可以直接看到数据库,结构、数据清晰可见

核心代码如下:

  1. /**
  2. * 打开数据库
  3. * @returns {Promise.<void>}
  4. */
  5. openDataBase(){
  6. //打开数据库,没有则创建
  7. db.openDatabase('GameMenu',1,'zzOpenGameMenu').then(res=>{
  8. //检测数据库是否存在
  9. db.isExists('game').then(res=>{
  10. //数据库已经存在,直接使用,将数据交付给页面UI组件
  11. this.setSelectData()
  12. }).catch(e=>{
  13. //数据库不存在,请求接口并处理数据,然后存入数据库
  14. this.getData()
  15. })
  16. }).catch(e=>{
  17. console.err(e)
  18. })
  19. },
  20. /**
  21. * 获取分类数据并存储到数据库
  22. * @returns {Promise.<void>}
  23. */
  24. async getData(){
  25. //接口请求数据并处理成三个扁平数组
  26. let data = await this.getMenuData()
  27. for(let i in data){
  28. //创建表并存储数据
  29. db.create(i,data[i])
  30. }
  31. //将数据交付给页面UI组件
  32. this.setSelectData()
  33. },

当任意菜单选择变更时,三列数据将重新查询,核心代码如下:

  1. /**
  2. * 重新查询数据
  3. * @param data 点击菜单携带的数据
  4. * @param index 点击菜单的序号
  5. * @param all 三个菜单当前选中数据
  6. */
  7. async onSelect(data,index,all){
  8. let target = [],condition = {}
  9. //业务逻辑:处理查询条件
  10. if(all['0'] && all['0']['name']!=defaultData[0].default.name)condition['gameName'] = all['0']['name']
  11. if(all['1'] && all['1']['name']!=defaultData[1].default.name)condition['platName'] = all['1']['name']
  12. if(all['2'] && all['2']['name']!=defaultData[2].default.name)condition['typeName'] = all['2']['name']
  13.  
  14. //创建三个查询任务
  15. let tasks = ['game','plat','type'].map((v,k)=>{
  16. //使用db.select方法查询
  17. return db.select(v,this.formatCondition(v,condition),'name,value','rowid desc','name').then((res)=>{
  18. target.push({
  19. options:res.data,
  20. defaultOption:defaultData[k].default,
  21. clickHandle:this.onSelect
  22. })
  23. })
  24. })
  25. //执行查询
  26. await Promise.all(tasks)
  27. //将数据交付给联动菜单组件使用
  28. this.selectData = target
  29. }

以上代码即可完成联动菜单所需要的数据管理工作,看起来是不是比较清晰?


使用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.create('testTable',[
    3. {name:'商品1',price:10},
    4. {name:'商品2',price:20},
    5. {name:'商品3',price:30},
    6. ])

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倒序
    2. db.select('testTable',{
    3. name:'商品1'
    4. },'*','price desc')
    5.  
    6. //查询价格大于0的商品,并用distinct关键字去重
    7. db.select('testTable',{
    8. price:'>0'
    9. },'distinct name,pirce','price desc')
  1. **update**
  2. - 功能:更新数据
  3. - 语法:update(tableName,data,condition = '')
  4. - 参数:
  5. - tableName:表名
  6. - dataString or Obejct):更改数据
  7. - conditionString or Obejct):查询条件
  8. - 示例:
  1. //将商品1的价格改为99
  2. db.update('testTable',{
  3. price:99
  4. },{
  5. name:'商品1'
  6. })

truncate

  • 功能:清空表
  • 语法:truncate(tableName)
  • 参数:
    • tableName:表名

drop

  • 功能:删除表
  • 语法:drop(tableName)
  • 参数:
    • tableName:表名

如何使用Database.js

Github地址:https://github.com/zhangsuoyong/Database.js

如果你有更好的想法,欢迎与我交流,个人微信号:king109400214

更多前端新鲜文章,请关注我司公众号“大转转FE”

如果你喜欢我们的文章,关注我们的公众号和我们互动吧。

前端要革命?看我在js里写SQL的更多相关文章

  1. asp.net尽量不在js里写<%%>

    asp.net尽量不在js里写<%%> eg: <script type="text/javascript"> var rootsid="&quo ...

  2. 在php里写sql查询需要注意的事情

    ---恢复内容开始--- 今天往php里写了一条sql查询, $sql = "select * from videos where vuser=".$u: $ret = mysql ...

  3. 怎么在js里写html

    <html> <head> <meta charset="utf-8"/> <title>示例前端模板写在代码里</title ...

  4. js里写网页结构, 传函数参数

    如题 "<td align='center' height='30px' width='80px'><a href='javascript:sort(\"&quo ...

  5. js里写html代码 啥时候要用“\"转义

    当去掉\的时候 字体变黑 需要加\

  6. web前端开发面试题(Vue.js)

    1.active-class是哪个组件的属性?嵌套路由怎么定义? 答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么获取传过来的动态参数?  ...

  7. 【一统江湖的大前端(8)】matter.js 经典物理

    目录 [一统江湖的大前端(8)]matter.js 经典物理 一.经典力学回顾 二. 仿真的实现原理 2.1 基本动力学模拟 2.2 碰撞模拟 三. 物理引擎matter.js 3.1 <愤怒的 ...

  8. 理解 Node.js 里的 process.nextTick()

    有很多人对Node.js里process.nextTick()的用法感到不理解,下面我们就来看一下process.nextTick()到底是什么,该如何使用. Node.js是单线程的,除了系统IO之 ...

  9. 正则表达式,js里的正则应用

    我爱撸码,撸码使我感到快乐!大家好,我是Counter.好吧已经到凌晨了,其实还是蛮困的,体力不支了,想了想还是把今天任务结束掉吧,为期5天,又重新把JavaScript以及jQuery给大致过了一遍 ...

随机推荐

  1. [转载]C header files matching your running 

    原文地址:C header files matching your running kernel were not found.作者:[Opser]小默 c header files matching ...

  2. 安装Window下Jenkins

    之前没接触过持续集成工具,之前只是了解了下自动化部署,最近一直在看自动化集成这块,发现要学的东西好多好多,可能在小公司用的不多,但如果在大公司,如果每个项目都要手动build.deploy的话那也太耗 ...

  3. 转:【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469    在Java中,可以通过配合调用Object对象的wait()方法和no ...

  4. 【Alpha阶段】第二次scrum meeting

    每日任务: ·1.本次会议为第二次Meeting会议: ·2.本次会议于今日上午08:30第五社区五号楼下召开,会议时长15min. 一.今日站立式会议照片: 二.每个人的工作: 三.工作中遇到的困难 ...

  5. 结对作业-基于GUI的四则运算

    一.需求分析 1.题目要求: 我们在个人作业1中,用各种语言实现了一个命令行的四则运算小程序.进一步,本次要求把这个程序做成GUI(可以是Windows PC 上的,也可以是Mac.Linux,web ...

  6. 201521123061 《Java程序设计》第十一周学习总结

    201521123061 <Java程序设计>第十一周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 本周学习的是如何解决多线程访问中的互斥 ...

  7. 使用Eclipse Egit与码云管理你的代码

    总体流程: 建立远程仓库 建立本地仓库并与远程仓库关联 将Eclipse中的项目提交到本地仓库并进而push到远程仓库 一. 配置Eclipse EGit 图解Eclipse中安装及配置EGit插件中 ...

  8. 解决"应用程序无法启动,因为应用程序的并行配置不正确"问题

    想必不少人都会遇到题目中的问题.我在一次和舍友一起重装系统的时候变遇到了上述的问题, 经过仔细分析发现电脑会出现上述问题所必要的条件 系统中没有存在合理的运行库文件 所运行的软件是之前重装系统之间留下 ...

  9. 201521123119 《Java程序设计》第13周学习总结

    1. 本周学习总结 Q以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 Q1. 网络基础 Q1.1 比较ping www.baidu.com与ping cec. ...

  10. tsst

    import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Sc ...