- PVSM.RU - https://www.pvsm.ru -
При программировании на JavaScript часто возникает проблема выбора оптимального представления данных в программе: массивы, хеши, массивы хешей, хеши массивов и т.д. Одни и те же данные могут быть загружены в различные комбинации структур, но трудность выбора обычно заключается в том, как найти компромисс между простотой кода для доступа к этим данным, скоростью работы и количеством требуемой памяти.
В статье рассказано о моей попытке поиска универсального решения.
Пусть нам, например, необходимо отобразить некоторые данные из двух связанных таблиц:
Стандартный подход обычно состоит из следующих шагов:
Недостатки стандартного подхода мне видятся в следующем:
Нестандартный подход — получить таблицы по отдельности и связать их на клиенте. Иногда это можно сделать легко. Например, в приведенной выше структуре можно загрузить таблицу «departments» в хеш, и осуществлять доступ по «id». Но чаще этого сделать нельзя, и приходится пользоваться различными функциями поиска типа Array.find или Array.indexOf.
Работая над своим очередным проектом, и в очередной раз ломая голову над тем, как мне лучше организовать данные, чтобы избежать недостатков обоих подходов, я решил, что настало время разрубить этот узел раз и навсегда.
Подход, при котором сервер выдает нам нормализованные таблицы, а мы потом их связываем в JavaScript-коде, показался мне более привлекательным. Не хватало только инструмента, чтобы их легко связывать. Я отложил все дела и сел его писать.
Требования получились следующие:
Так появились Strelki.js, и пока единственный в нем класс — IndexedArray.
Итак, создадим новый IndexedArray:
var emp = new StrelkiJS.IndexedArray();
Добавим в него данные:
emp.put({
id: "001",
first_name: "John",
last_name: "Smith",
dep_id: "sales",
address_id: "200"
});
emp.put({
id: "002",
first_name: "Ivan",
last_name: "Krasonov",
dep_id: "sales",
address_id: "300"
});
Посмотрим, что внутри:
Под капотом IndexedArray представляет из себя хеш (this.data), куда сохраняются ссылки на объекты. В качестве ключа хеша используется поле «id» сохраняемого элемента, которое должно быть уникально. Так как многие современные серверные фреймворки также используют поле «id» подобным образом, то это ограничение не должно стать проблемой.
Кроме того, в IndexedArray имеется хеш this.indexData. Ключи этого хеша содержат название индексируемого поля, а значения — хеши с ids соответствующих элементов основного хеша. Пока индексов у нас нет, поэтому this.indexData пуст.
Добавим индекс:
emp.createIndex("dep_id");
Посмотрим this.indexData:
this.indexData теперь содержит ключ «dep_id», который содержит данные индекса в виде вложенных хешей.
Поищем что-нибудь по индексу:
> emp.findIdsByIndex("dep_id","sales")
< ["001", "002"]
В отличие от функций типа Array.find, индексный поиск не использует перебор данных, а только хеши, что позволяет добиться высокой скорости. Замеры, правда, я пока не делал, но должно работать быстро.
Добавим еще данных:
emp.put({
id: "003",
first_name: "George",
last_name: "Clooney",
dep_id: "hr",
address_id: "400"
});
emp.put({
id: "004",
first_name: "Dev",
last_name: "Patel",
dep_id: "board",
address_id: "500"
});
Найдем элементы по индексу, и сформируем из них новый IndexedArray:
var sales_emp = emp.where("dep_id","sales");
Создадим и заполним еще один IndexedArray:
var adr = new StrelkiJS.IndexedArray();
adr.put({ id: "200", address: "New Orleans, Bourbon street, 100"});
adr.put({ id: "300", address: "Moscow, Rojdestvensko-Krasnopresnenskaya Naberejnaya"});
adr.put({ id: "500", address: "Bollywood, India"});
Для описания связи данного IndexedArray с любым другим служит объект следующего вида:
{
from_col: "address_id", // поле в данном IndexedArray
to_table: adr, // ссылка на связываемую таблицу
to_col: "id", // "id", или другое индексированное поле в связываемой таблице
type: "outer", // "outer" для LEFT OUTER JOIN, или null для INNER JOIN
join: // null или ссылка на массив точно таких же объектов описания связи, для построения вложенных JOIN-ов
}
Присоединим adr к emp JOIN-ом:
var res = emp.query([
{
from_col: "address_id", // name of the column in "emp" table
to_table: adr, // reference to another table
to_col: "id", // "id", or other indexed field in "adr" table
type: "outer", // "outer" for LEFT OUTER JOIN, or null for INNER JOIN
//join: [ // optional recursive nested joins of the same structure
// {
// from_col: ...,
// to_table: ...,
// to_col: ...,
// ...
// },
// ...
//],
}
])
Аналогичный оператор на SQL выглядел бы так:
SELECT ...
FROM emp
LEFT OUTER JOIN adr ON emp.address_id = adr.id
Результат будет выглядеть так:
[
[
{"id":"001","first_name":"John","last_name":"Smith","dep_id":"sales","address_id":"200"},
{"id":"200","address":"New Orleans, Bourbon street, 100"}
],
[
{"id":"002","first_name":"Ivan","last_name":"Krasonov","dep_id":"sales","address_id":"300"},
{"id":"300","address":"Moscow, Rojdestvensko-Krasnopresnenskaya Naberejnaya"}
],
[
{"id":"003","first_name":"George","last_name":"Clooney","dep_id":"hr","address_id":"400"},
null
],
[
{"id":"004","first_name":"Dev","last_name":"Patel","dep_id":"board","address_id":"500"},
{"id":"500","address":"Bollywood, India"}
]
]
var dep = new StrelkiJS.IndexedArray();
dep.createIndex("address");
dep.put({id:"sales", name: "Sales", address_id: "100"});
dep.put({id:"it", name: "IT", address_id: "100"});
dep.put({id:"hr", name: "Human resource", address_id: "100"});
dep.put({id:"ops", name: "Operations", address_id: "100"});
dep.put({id:"warehouse", name: "Warehouse", address_id: "500"});
var emp = new StrelkiJS.IndexedArray();
emp.createIndex("dep_id");
emp.put({id:"001", first_name: "john", last_name: "smith", dep_id: "sales", address_id: "200"});
emp.put({id:"002", first_name: "Tiger", last_name: "Woods", dep_id: "sales", address_id: "300"});
emp.put({id:"003", first_name: "George", last_name: "Bush", dep_id: "sales", address_id: "400"});
emp.put({id:"004", first_name: "Vlad", last_name: "Putin", dep_id: "ops", address_id: "400"});
emp.put({id:"005", first_name: "Donald", last_name: "Trump", dep_id: "ops", address_id: "600"});
var userRoles = new StrelkiJS.IndexedArray();
userRoles.createIndex("emp_id");
userRoles.put({id:"601", emp_id: "001", role_id: "worker"});
userRoles.put({id:"602", emp_id: "001", role_id: "picker"});
userRoles.put({id:"603", emp_id: "001", role_id: "cashier"});
userRoles.put({id:"604", emp_id: "002", role_id: "cashier"});
var joinInfo = [
{
from_col: "id",
to_table: emp,
to_col: "dep_id",
type: "outer",
join: [{
from_col: "id",
to_table: userRoles,
to_col: "emp_id",
type: "outer",
}],
},
// {
// from_col: "id",
// to_table: assets,
// to_col: "dep_id",
// }
];
//var js1 = IndexedArray.IndexedArray.doLookups(dep.get("sales"),joinInfo);
var js = dep.where(null,null,function(el) { return el.id === "sales"}).query(joinInfo);
// result
[
[
{"id":"sales","name":"Sales","address_id":"100"},
{"id":"001","first_name":"john","last_name":"smith","dep_id":"sales","address_id":"200"},
{"id":"601","emp_id":"001","role_id":"worker"}
],
[
{"id":"sales","name":"Sales","address_id":"100"},
{"id":"001","first_name":"john","last_name":"smith","dep_id":"sales","address_id":"200"},
{"id":"602","emp_id":"001","role_id":"picker"}
],
[
{"id":"sales","name":"Sales","address_id":"100"},
{"id":"001","first_name":"john","last_name":"smith","dep_id":"sales","address_id":"200"},
{"id":"603","emp_id":"001","role_id":"cashier"}
],
[
{"id":"sales","name":"Sales","address_id":"100"},
{"id":"002","first_name":"Tiger","last_name":"Woods","dep_id":"sales","address_id":"300"},
{"id":"604","emp_id":"002","role_id":"cashier"}
],
[
{"id":"sales","name":"Sales","address_id":"100"},
{"id":"003","first_name":"George","last_name":"Bush","dep_id":"sales","address_id":"400"}
,null
]
]
Как видим, связывание массивов превратилось из функций с циклами и переборами в простую декларацию связей.
IndexedArray не хранит копии объектов, а только лишь указатели на них (отсюда и название Strelki). Поэтому, если объект был помещен в IndexedArray методом put(), а затем изменен, информация в индексах может стать некорректной. Чтобы избежать этой ситуации необходимо удалить объект из IndexedArray методом del() перед изменением.
Связывание может осуществляться только по индексированному полю, либо по полю «id».
Некоторые методы объекта IndexedArray (например length()) требуют построения массива ключей «id». При этом массив ключей сохраняется в объекте для возможного повторного использования. При каждом изменении массива (методы put(), del(), и т.п.) массив ключей обнуляется. Поэтому чередование методов, которые создают и затем обнуляют массив ключей, может привести проблемам производительности на больших наборах данных.
StrelkiJS создан для облегчения написания основного проекта KidsTrack, о котором я писал на хабре ранее. Поэтому все решения о новом функционале пока диктуются потребностями родительского проекта. В ближайших планах — сделать более удобный доступ к колонкам в результатах JOIN-а,
Github: github.com/amaksr/Strelki.js [1]
Песочница: www.izhforum.info/strelkijs [2]
Автор: amaksr
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/121139
Ссылки в тексте:
[1] github.com/amaksr/Strelki.js: https://github.com/amaksr/Strelki.js
[2] www.izhforum.info/strelkijs: https://www.izhforum.info/strelkijs/
[3] Источник: https://habrahabr.ru/post/301258/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.