下一代 Web 查詢語言,使 javascript 支持 LINQjavascript
Github: github.com/dntzhang/qo…前端
最近恰好修改了騰訊文檔 Excel 表格公式的一些 bug,主要是修改公式的 parser 。好比下面的腳本怎麼轉成 javascript 運行?java
= IF(SUM(J6:J7) + SUM(J6:J7) > 10, "A2 是 foo", "A2 不是 foo")
複製代碼
公式或一些腳本語言的實現包含幾個主要步驟:git
scanner > lexer > parser > ast > code string
複製代碼
獲得 code string 以後能夠動態運行,好比 js 裏使用 eval ,eval 能保留上下文信息,缺點是執行代碼包含編譯器代碼,eval 的安全性等。 獲得 code string 以後也可直接使用生成的 code string 運行,缺點是依賴構建工具或者編輯器插件去動態替換源代碼。github
好比 wind 同時支持 JIT 和 AOT, qone 的思路和上面相似,但不徹底相同, qone 的以下:數據庫
scanner > lexer > parser > ast > method(ast)
複製代碼
這個後面寫原理時候再細說。npm
總的來講,由於騰訊文檔公式相關工做、早年的 kmdjs 開發 (uglify2) 和 .NET 開發,因此有了 qone 。安全
LINQ (語言集成查詢) 是 .NET Framework 3.5 版中引入的一項創新功能。在 Visual Studio 中,能夠用 Visual Basic 或 C# 爲如下數據源編寫 LINQ 查詢:SQL Server 數據庫、XML 文檔、ADO.NET 數據集,以及可枚舉的 Objects(即 LINQ to Objects)。bash
qone 是一款讓 Web 前端工程師在 javascript 使用 .NET 平臺下相似 LINQ 語法的前端庫。qone 讓 Web 前端工程師經過字符串的形式實現了 LINQ to Objects 的調用(下面統一叫作 qone to objects),Objects即 JSON 組成的 Array。舉個簡單的例子(qone 遠比下面的例子強大):前端工程師
var list = [
{ name: 'qone', age: 1 },
{ name: 'linq', age: 18 },
{ name: 'dntzhang', age: 28 }
]
var result = qone({ list }).query(` from n in list where n.age > 18 select n `)
assert.deepEqual(result, [{ "name": "dntzhang", "age": 28 }])
複製代碼
與 LINQ 同樣,和 SQL 不一樣,qone 的 from 也在前面,爲後面語句可以有智能提示,qone 是基於 string 的實時編譯,不是 javasript 的原生語法,因此雖然 from 寫在前面但不支持智能提示,但能夠專門爲 qone 寫個編輯器插件去實現智能提示,因此 qone 語法設計上依然把 from 放在前面。
從根本上說,qone to objects 表示一種新的處理集合的方法。 採用舊方法,您必須編寫指定如何從集合檢索數據的複雜的 foreach 循環。 而採用 qone 方法,您只需編寫描述要檢索的內容的聲明性代碼。 另外,與傳統的 foreach 循環相比,qone 查詢具備三大優點:
一般,您要對數據執行的操做越複雜,就越能體會到 qone 相較於傳統迭代技術的優點。
npm install qone
複製代碼
其中 from 和 in 一塊兒使用,orderby 和 desc 或者 asc 一塊兒使用。
from 也能夠把子屬性做爲 dataSource:
from n in data.list
複製代碼
qone 支持下面三類運算符:
條件判斷語句也支持 null, undefined, true, false。
經過上面各類組合,你能夠寫出很複雜的查詢條件。好比:
qone({ list }).query(` from n in list where !(n.age > 17 || n.age < 2) && n.name != 'dntzhang' select n `)
複製代碼
也支持 bool 類型的查詢:
var list = [
{ name: 'qone', age: 1, isBaby: true },
{ name: 'linq', age: 18 },
{ name: 'dntzhang', age: 28 }]
var result = qone({ list }).query(` from a in list where a.isBaby && n.name = 'qone' select a `)
assert.deepEqual(result, [{ name: 'qone', age: 1, isBaby: true }])
複製代碼
其中 isBaby 是 bool 類型,一樣的寫法: a.isBaby = true (等同於: a.isBaby) a.isBaby = false (等同於: !a.isBaby)
經過上面介紹發現 qone 不支持加減乘除位求模運算?怎麼才能圖靈完備?方法注入搞定一切!以下所示:
QUnit.test("Method test 8", function (assert) {
var arr = [1, 2, 3, 4, 5]
qone('square', function (num) {
return num * num
})
qone('sqrt', function (num) {
return Math.sqrt(num)
})
var result = qone({ arr }).query(` from n in arr where sqrt(n) >= 2 select { squareValue : square(n) } `)
assert.deepEqual(result, [{ "squareValue": 16 }, { "squareValue": 25 }])
})
複製代碼
方法也是支持多參數傳入,因此能夠寫出任意的查詢條件。其中select, where, orderby, groupby 語句都支持方法注入。
經過 select 能夠輸出各類格式和字段的數據:
QUnit.test("Select JSON test", function (assert) {
var list = [
{ name: 'qone', age: 1 },
{ name: 'linq', age: 18 },
{ name: 'dntzhang', age: 28 }
]
var result = qone({ list }).query(` from n in list where n.age < 20 select {n.age, n.name} `)
assert.deepEqual(result, [
{ "age": 1 , "name": "qone" },
{ "age": 18, "name": "linq" }
])
})
複製代碼
把全部場景列舉一下:
select n
輸出源 itemselect n.name
輸出一維表select n.name, n.age
輸出二維表select { n.age, n.name }
缺省方式輸出 JSON Array(key自動使用 age 和 name)select { a: n.age, b: n.name }
指定 key 輸出 JSON Arrayselect { a: methodName(n.age), b: n.name }
注入方法select methodName(n.age), n.name
注入方法select methodName(n.age, n.name, 1, true, 'abc')
注入方法並傳遞參數var result = qone({ list }).query(` from n in list where n.age > 0 orderby n.age asc, n.name desc select n `)
複製代碼
若是沒有標記 asc 或者 desc,使用默認排序 asc。
QUnit.test("Simple groupby test 1", function (assert) {
var list = [
{ name: 'qone', age: 1 },
{ name: 'linq', age: 18 },
{ name: 'dntzhang1', age: 28 },
{ name: 'dntzhang2', age: 28 },
{ name: 'dntzhang3', age: 29 }
]
var result = qone({ list }).query(` from n in list where n.age > 18 groupby n.age `)
assert.deepEqual(result, [
[{ "name": "dntzhang1", "age": 28 }, { "name": "dntzhang2", "age": 28 }],
[{ "name": "dntzhang3", "age": 29 }]])
})
複製代碼
groupby 能夠做爲結束語句,不用跟着也不能跟着 select 語句,groupby 也能夠支持方法注入。
QUnit.test("Multi datasource with props condition", function (assert) {
var listA = [
{ name: 'qone', age: 1 },
{ name: 'linq', age: 18 },
{ name: 'dntzhang', age: 28 }]
var listB = [
{ name: 'x', age: 11 },
{ name: 'xx', age: 18 },
{ name: 'xxx', age: 13 }
]
var result = qone({ listA, listB }).query(` from a in listA from b in listB where a.age = b.age select a, b `)
assert.deepEqual(result, [[{ "name": "linq", "age": 18 }, { "name": "xx", "age": 18 }]])
})
複製代碼
多數據源會產生笛卡兒積。
QUnit.test("Multi deep from test ", function (assert) {
var list = [
{ name: 'qone', age: 1, isBaby: true, colors: [{ xx: [1, 2, 3] }, { xx: [11, 2, 3] }] },
{ name: 'linq', age: 18, colors: [{ xx: [100, 2, 3] }, { xx: [11, 2, 3] }] },
{ name: 'dntzhang', age: 28, colors: [{ xx: [1, 2, 3] }, { xx: [11, 2, 3] }] }]
var result = qone({ list }).query(` from a in list from c in a.colors from d in c.xx where d === 100 select a.name, c,d `)
assert.deepEqual(result, [["linq", { "xx": [100, 2, 3] }, 100]])
})
複製代碼
也能夠和自身的數據源會產生笛卡兒積。
經過 limit 能夠應付最多見的兩種查詢場景 - top N 和 分頁。
查詢top3:
QUnit.test("Limit top 3", function (assert) {
var list = [
{ name: 'dntzhang1', age: 1 },
{ name: 'dntzhang2', age: 2 },
{ name: 'dntzhang3', age: 3 },
{ name: 'dntzhang4', age: 4 },
{ name: 'dntzhang5', age: 5 },
{ name: 'dntzhang6', age: 6 },
{ name: 'dntzhang7', age: 7 },
{ name: 'dntzhang8', age: 8 },
{ name: 'dntzhang9', age: 9 },
{ name: 'dntzhang10', age: 10 }
]
var pageIndex = 1,
pageSize = 4
var result = qone({ list }).query(` from n in list select n limit 0, 3 `)
assert.deepEqual(result, [
{ name: 'dntzhang1', age: 1 },
{ name: 'dntzhang2', age: 2 },
{ name: 'dntzhang3', age: 3 }
])
})
複製代碼
分頁查詢:
QUnit.test("Limit one page test", function (assert) {
var list = [
{ name: 'dntzhang1', age: 1 },
{ name: 'dntzhang2', age: 2 },
{ name: 'dntzhang3', age: 3 },
{ name: 'dntzhang4', age: 4 },
{ name: 'dntzhang5', age: 5 },
{ name: 'dntzhang6', age: 6 },
{ name: 'dntzhang7', age: 7 },
{ name: 'dntzhang8', age: 8 },
{ name: 'dntzhang9', age: 9 },
{ name: 'dntzhang10', age: 10 }
]
var pageIndex = 1,
pageSize = 4
var result = qone({ list }).query(` from n in list where n.age > 0 select n limit ${pageIndex * pageSize}, ${pageSize} `)
assert.deepEqual(result, [
{ name: 'dntzhang5', age: 5 },
{ name: 'dntzhang6', age: 6 },
{ name: 'dntzhang7', age: 7 },
{ name: 'dntzhang8', age: 8 }])
})
複製代碼