Привет. Мне 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)
})
}
А само приложения вот так:
Пора заняться самым интересным. Начинаем фильтровать. Нам нужна функция, которая на вход берет подстроку(запрос пользователя) и массив всех элементов, а на выходе выдает массив отфильтрованных элементов. Приступим. Самый очевидный вариант такой:
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)
})
А браузер нам показывает вот это:
Упс. Забыли отчищать родительский элемент перед рендером. Добавим это в функцию рендера:
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){
let result;
list.forEach(i=>{
if(i.indexOf(val)!=-1)
result.push(i)
})
return 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))
Такое приложение получилось очень шустрым и нетрудным для понимания. Среднее время для таких данных — 0.3 миллисекунды. А размер после сжатия через babel дает всего 782 байта.
Рабочий пример можете найти по ссылке
Этой статьей я хотел лишь натолкнуть людей не забывать про чистый js. Не так все и плохо там. Пользуйтесь нативными функциями и используйте es6.
Спасибо за внимание.
Автор: Александр