javascript

Глупый JS. Делаем фильтры «по красоте»

  • четверг, 23 ноября 2017 г. в 03:14:19
https://habrahabr.ru/post/342922/
  • Визуализация данных
  • JavaScript


Привет. Мне 17 лет и я JS-разработчик. Возможно это приговор, а может быть это классическое приветствие в «Клубе анонимных JS-никовпрограммистов» — мне этого не узнать. Сейчас во многом моя работа заключается в работе с данными, их обработкой, фильтрами, сортировкой и так далее. Естественно, что я использую не нативный JS в проектах. Сегодня будем делать фильтры на чистом js-е. Увидим насколько это круто и быстро. Узнаем возможности es6 и сделаем рефакторинг кода. Заинтересованных прошу под кат.

Разве чистый JS не крут?


Сейчас, да и в принципе давно уже, мало кто пишет на чистом js. Все хотят писать на современных фреймворках. Я не являюсь исключением и на работе в основном пишу на VueJS(2.0+) и стареньком AngularJS. И этот выбор полностью оправдан. Не буду углубляться в подробности, но в большинстве случаев при грамотном построении приложения на фреймворке вы выиграете в скорости, поддерживаемости и читаемости.

Так к чему же эта статья?


Я хочу повествовать о том, что не стоит забывать о чистом js-е. Ведь все же мы начинали с простого:

console.log('Hello world');

Вы же не будете писать на фреймворке такое? Хотя, возможно, я очень плохо с вами знаком. В этой статье я хочу написать фильтры на чистом js-е. Задача, в принципе, тривиальная и не требует каких-либо задатков гения. Я постараюсь написать как можно более сжатый код. Не гарантирую, что читателю, который не знаком с особенностями js-а будет понятно. Однако я постараюсь описать весь процесс как можно подробнее.

Начнем


Для начала примерно набросаем, что нам надо и как это будет работать.
Нам нужны данные. Пусть будет статический массив. Поехали.

let list = ['JavaScript','Kotlin','Rust','PHP','Ruby','Java','MarkDown','Python','C++','Fortran','Assembler']

(Заранее прошу прощения, если не указал ваш любимый язык)

Отлично, у нас есть данные. На самом деле эти данные должны получаться через запросы к серверу, но у нас, как и у «ООО» — упрощенная модель.

Для вывода я сделал инпут и список. Выглядит это вот так:

<input type="text" id="search">
<ul id="results">
</ul>

В input пользователь вводит запрос, а список будет заполняться в зависимости от запроса. И еще определим, что, если input пустой, то мы будем выводить весь список.

У нас есть данные и пользовательский интерфейс(можете смеяться). Теперь нам нужна функция, которая отрендерит нам список.

function renderList(_list=[],el=document.body){
  _list.forEach(i=>{
    let new_el = document.createElement('li')
    new_el.innerHTML=i
    el.appendChild(new_el)
  })
}

Получилось вот так. Функция принимает два аргумента — массив с элементами и элемент DOM-дерева, к который мы хотим нарисовать список. Если первый аргумент не задан, то мы говорим функции, что это пустой массив. Аналогично с элементом дерева.

Далее мы перебираем все элементы массива в цикле и для каждого элемента создаем узел DOM-дерева, добавляем внутрь этого узла сам элемент и добавляем его в конец нашего родительского элемента. В принципе ничего сложного.

На данном этапе код выглядит примерно так:

let list = ['JavaScript','Kotlin','Rust','PHP','Ruby','Java','MarkDown','Python','C++','Fortran','Assembler']
const result = document.getElementById('results')
renderList(list,result)
function renderList(_list=[],el=document.body){
  _list.forEach(i=>{
    let new_el = document.createElement('li')
    new_el.innerHTML=i
    el.appendChild(new_el)
  })
}

А само приложения вот так:

image

Пора заняться самым интересным. Начинаем фильтровать. Нам нужна функция, которая на вход берет подстроку(запрос пользователя) и массив всех элементов, а на выходе выдает массив отфильтрованных элементов. Приступим. Самый очевидный вариант такой:

function filter(val,list){
let result=[];
  list.forEach(i=>{
    if(i.indexOf(val)!=-1)
      result.push(i)
  })
return result;
}

У нас есть черновой вариант. Теперь соединим все вместе, добавим обработчик и поехали. Код теперь такой:

let list = ['JavaScript','Kotlin','Rust','PHP','Ruby','Java','MarkDown','Python','C++','Fortran','Assembler']
const result = document.getElementById('results')
renderList(list,result)
function filter(val,list){
let result=[];
  list.forEach(i=>{
    if(i.indexOf(val)!=-1)
      result.push(i)
  })
return result;
}
function renderList(_list=[],el=document.body){
  _list.forEach(i=>{
    let new_el = document.createElement('li')
    new_el.innerHTML=i
    el.appendChild(new_el)
  })
}
document.getElementById('search').addEventListener('input',e=>{
let new_arr = filter(e.target.value,list)
renderList(new_arr,result)
})

А браузер нам показывает вот это:

image

Упс. Забыли отчищать родительский элемент перед рендером. Добавим это в функцию рендера:

el.innerHTML='';

Теперь у нас все работает. Примерно также работают фильтры на вашем любимом фреймворке. Но мы же хорошие программисты и мы хотим сделать все красИвее, а может красивЕе. К счастью, я не филолог. Вернемся к нашей функции фильтра и попробуем ее переписать.

Для начала заменим if на тернарный оператор:

(i.indexOf(val)!=-1)?result.push(i):'';

Покопавшись в документации, я нашел, что можно юзать побитовое отрицание. Работает оно по формуле -(N+1). То есть при нашем значении -1 в indexOf мы получаем по формуле -(-1+1)=0. То есть это уже готовый false для нашего условия. Перепишем:

(~i.indexOf(val))?result.push(i):'';

А теперь десерт. forEach уже не модно. Есть же ES6. Там есть метод для перебора массива — filter. Он возвращает значение, если оно соответствует условию. Применим его:

function filter(val,list){
console.time('test')
  return list.filter(i=>(~i.indexOf(val)))
};

Красиво, не так ли?

Теперь переделаем обработчик. Запишем все в одну стрелочную функцию. У меня получилось вот так:

document.getElementById('search').addEventListener('input',e=>renderList(filter(e.target.value,list),result))

Я надеюсь, что все понятно. Берем отфильтрованный массив и кидаем его как аргумент для рендера.

Захотел замерить время обработки. Использовал модные функции.

На этом я остановился. Полный и рабочий код получился такой:

let list = ['JavaScript','Kotlin','Rust','PHP','Ruby','Java','MarkDown','Python','C++','Fortran','Assembler']
const result = document.getElementById('results')
renderList(list,result)
function filter(val,list){
console.time('test')
  return list.filter(i=>(~i.indexOf(val)))
};
function renderList(_list=[],el=document.body){
	el.innerHTML='';
  _list.forEach(i=>{
    let new_el = document.createElement('li')
    new_el.innerHTML=i
    el.appendChild(new_el)
  })
  console.timeEnd('test')
}
document.getElementById('search').addEventListener('input',e=>renderList(filter(e.target.value,list),result))

image

Такое приложение получилось очень шустрым и нетрудным для понимания. Среднее время для таких данных — 0.3 миллисекунды. А размер после сжатия через babel дает всего 782 байта.

image

Рабочий пример можете найти по ссылке
Этой статьей я хотел лишь натолкнуть людей не забывать про чистый js. Не так все и плохо там. Пользуйтесь нативными функциями и используйте es6.

Спасибо Houston Теперь пример выглядит вот так и не зависит от регистра.
Спасибо за внимание.