Underscore.js - приятное знакомство

Reading time ~19 minutes

Статья посвящена моему знакомству с underscore.js. Я рад, что познакомился с этой библиотекой. Кто плохо владеет english, то здесь находится переведенная на русский язык документация - underscore.js russian;

Что такое underscore.js? Это отличная библиотека с набором javascript-методов для удобной работы с массивами, коллекциями, функциями, объектами, а также просто утилиты.

Чтобы оценить полезность и удобство работы с этой библиотекой, нужно на практике рассмотреть примеры работы методов underscore.js. Поэтому ниже будут представлены именно примеры.

Фиг его знает, но статья получилась больше смахивающей на пересказ официальной документации. Это потому, что я каждый метод (почти каждый) пробовал в действии, чтобы понять его работу.

Введение

Библиотека underscore.js живет по этому адресу - underscore.js. Подключается как любая другая javascript-библиотека или javascript-плагин - через тег script. Есть вариант установки под Nodejs:

npm install underscore

В документе вызывается через символ подчеркивания - _ (отсюда и название библиотеки). Кстати, расширенный вариант underscore - lodash также обыгрывает этот вариант самоназвания ( lodash == low dash ).

underscore.js - работа с коллекциями

Список методов для работы с коллекциями достаточно обширен - Collection Functions. Рассмотрим некоторые из них.

Метод each()

Служит для перебора всех элементов коллекции и применения функции к каждому из элементов. Код ниже пробежится по всем элементам коллекции a1 и выведет каждый из этих элементов в консоль браузера:

var a1 = [ 1, 2, 3 ];
each( a1, function (el) { console.log(el) });

Более сложный пример применения метода each(). Уже здесь начинается демонстрация привлекательности underscore.js.

Код ниже является ни чем иным, как двойным циклом - один цикл вложен в другой цикл. Но с использованием underscore.js код становится буквально изящным - всего одна строка!

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
each( a2, function (el) { each( el, function (element) { console.log(element) } ) });

Вкратце - имеется массив a2, элементы которого - объекты со свойствами. Внешний цикл пробегается по элементам массива.

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

Метод map()

Метод map() получает на вход массив и возвращает новый массив, созданный путем преобразования элементов оригинального массива \ коллекции.

Например, код ниже берет массив a3, вызывает каждый из элементов этого массива, умножает этот элемент на 3 и помещает в новый массив a4, как элемент этого массива:

var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var a4 = map( a3, function (el) { return el\*3 } );

Точно также этот метод работает с коллекцией. Например, увеличим значение только одного определенного ключа коллекции a2. Вернется новый массив a5 с измененным значением ключа sal:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var a5 = map( a2, function (el) { el.sal += 500 } );

Функция для обработки элементов коллекции может быть любой. К примеру, можно создать новый массив, состоящий только из значений ключей spec, name или sal:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var specs = map( a2, function (el) { return el.spec } );
var names = map( a2, function (el) { return el.name } );
var salaries = map( a2, function (el) { return el.sal } );

Метод partition()

Метод partition() создает из одного массива новый массив, который состоит из двух подмассивов.

Первый подмассив будет содержать элементы, которые удовлетворяют условию. Второй подмассив - элементы, которые не удовлетворяют условию.

var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var splitArr = partition( a3, function (el) { if ( el % 2 === 0 ) { return el } } );

Метод partition() также хорошо умеет работать и с коллекциями. К примеру, код ниже отсортирует массив из объектов на основе условия - значения ключа sal:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var splitSalary = partition( a2, function (el) { if ( el.sal < 4000 ) { return el } } );

Метод shuffle()

При каждом запуске изменяет метод shuffle() в случайном порядке индекс элементов существующего массива. В результате получается новый массив на основе существующего массива:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var shuffledArrObj = shuffle(a2);
var shuffledArr = shuffle(a3);

Метод find()

С помощью этого метода можно искать элементы в коллекциях\массивах. Метод возвращает первый элемент, который удовлетворяет условию, заданному в функции:

var findExample = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var findResult = find( findExample, function (el) {
return el % 2 == 0;
});

Метод reduce()

Этот метод выполняет “склеивание” элементов массива \ коллекции - все значения элементов будут объединены в одно значение. Функция-обработчик имеет вид function ( memo, value ), где memo - это начальное значение редукции:

var reduceExample = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var reduceResult = reduce( reduceExample, function (memo, el) {
return memo + el;
});

Метод reduceRight() будет делать тоже самое, что и метод reduce(), но только справа-налево.

Метод filter()

Метод для поиска элементов массива по какому-то условию. Возвращает новый массив:

var filterExample = [ 1,2,3,4,5,6,7,8,9 ];
var filterResult = filter( filterExample, function (el) {
return el % 3 === 0
});

Замечание по поводу метода filter() и его различия с методом map().

“… underscore\lodash - методы map() и filter() - у них есть различие между собой? в оф. документации говорится, что метод map() возвращает массив преобразованных элементов; метод filter() возвращает массив элементов, удовлетворяющих условию. но ведь я могу (?) подставить в оба метода любую (?) фунцию?
и метод filter() будет возвращать массив преобразованных элементов? или я что-то не понимаю? …”

“… Теоретически - да, они идентичны. Но у них разное назначение - если хочется фильтровать, то используй filter, хочешь преобразовывать - map. Могу предположить (я не знаю точно), что из соображений быстродействия filter передает в функцию объект по ссылке, а не его копию, поэтому изменения объекта будут работать, но это значит, что ты отдаешься во власть реализации метода. И никто не гарантирует, что в один день underscore/lodash не начнет передавать копию объекта. В этом случае твой код может поломаться. Поэтому я бы использовал функции по назначению. …”

Метод pluck()

Метод служит для возвращения массива, содержащего значения ключа, указанного в условии.

Синтаксис метода до чрезвычайности прост - указываем имя обрабатываемой коллекции и названия ключа, значение которого хотим получить:

var school = [ { name: 'Mary', age: 12 }, { name: 'John', age: 10 }, { name: 'Peter', age: 13 }, { name: 'David', age: 11 }, { name: 'George', age: 15 } ];
var schoolNames = pluck( school, 'name');

Название метода смешное (имхо) - привет, Кин-дза-дза!. В официальной документации говорится, что это самый часто используемый метод библиотеки underscore.

underscore.js - работа с массивами

Рассмотренные выше несколько методов underscore.js могут одинаково хорошо работать как с массивами, так и с коллекциями.

Однако, помимо этих методов, у underscore.js есть методы исключительно для работы с массивами. По этой ссылке можно ознакомиться со списком таких методов - Underscore.js - Arrays.

Метод first()

Этот метод возвращает первый элемент массива - все просто и понятно:

var a1 = [ 1, 2, 3 ];
var firstEl = first(a1);

Есть метод last(), который аналогичен методу firts(), но возвращает последний элемент коллекции\массива.

Метод flatten()

Этот метод преобразует массив с вложенными подмассивами (многоуровневый массив) в один, одноуровневый массив:

var fltArr = flatten( [ 11, 12, 13, [ 21, 22, 23, [ 31, 32, 33, [ 41, 42, 43, [ 51, 52, 53, [ 61 ] ] ] ] ] ] );

Метод zip()

Этот метод интересен и является примером того, насколько библиотека underscore.js удобная и изящная. Допустим, имеются три массива:

var r1 = [ '1-1', '1-2', '1-3' ];
var r2 = [ '2-1', '2-2', '2-3' ];
var r3 = [ '3-1', '3-2', '3-3' ];

Если передать эти три массива на вход методу zip(), то он преобразует их в один массив, состоящий из трех подмассивов. При этом элементы этих подмассивов будут выглядеть таким образом:

var r4 = zip( r1 , r2 , r3 );
r4 = [ [ '1-1', '2-1', '3-1' ], [ '1-2', '2-2', '3-2' ], [ '1-3', '2-3', '3-3' ] ];

Теперь представьте, что вам пришлось бы затратить достаточно времени для написания кода на нативном JavaScript, чтобы обработать такой пример.

Метод object()

Этот метод преобразует несколько массивов в один объект:

var names = [ 'Peter', 'Mary', 'John' ];
var ages = [ 30, 20, 40 ];
var peoples = object( names, ages );
peoples = {Peter: 30, Mary: 20, John: 40};

underscore.js - работа с функциями

Методы работы с функциями с underscore.js “расположены” здесь - Underscore.js - Functions. Их не так много. Они немного сложнее для понимания. Хотя ничего сложного нет.

Метод bind()

Этот метод “привязывает” исполнение функции только к определенному объекту. Чтобы было понятнее, начнем из далека, из классики.

Создадим три объекта, у каждого из которых будет ключ name:

var someObj1 = {};
var someObj2 = {};
var someObj3 = {};
someObj1.name = 'Peter';
someObj2.name = 'Mary';
someObj3.name = 'John';

Создадим функцию greeting такого вида:

var greeting = function (el) {
console.log( el + ', ' + this.name );
};

А теперь вызовем функцию greeting на каждом из двух первых объектов:

greeting.apply( someObj1, ['Hello'] );
greeting.apply( someObj2, ['Holla'] );

Результатом будет такой текст:

Hello, Peter
Holla, Mary

… потому что this.name каждый раз будет ссылаться на разный объект - в зависимости от того, на котором из объектов было вызвано исполнение функции.

А теперь воспользуемся методом bind() библиотеки underscore.js таким образом:

var greetingBind = bind( greeting, someObj3 );

Что мы сделали в этом коде? Мы создали новый экземпляр greetingBind функции greeting и привязали исполнение этого нового экземпляра на объект someObj3.

И только на него. Теперь нам не надо даже указывать, что функцию нужно исполнять на объекте someObj3 - мы просто вызываем функцию greetingBind на исполнение и передаем ей аргумент:

greetingBind('Welcome');
greetingBind('Chao');

Вывод кода будет таким:

Welcome, John
Chao, John

Такой способ привязки функции к объекту носит название карринг и мы только что познакомились с ним на практике.

Есть еще методы debounce(), once(), after() - с ними еще не познакомился. Но они весьма любопытны и полезны.

underscore.js - работа с объектами

Метод keys()

Преобразует ключи объекта в массив:

var fruits = { apple: 10, melon: 20, potato: 30, tomato: 50 };
var fruitSorts = keys( fruits );

Метод values()

Преобразует значения ключей объекта в массив:

var fruitsvalues = values( fruits );

Метод pairs()

Преобразует пары ключ-значение объекта в массив, состоящий из подмассивов:

var fruitsPairs = pairs( fruits );

Метод invert()

Инвертирует пару ключ:значение в объекте:

var fruitsInvert = invert( fruits );

Метод pick()

Вернет новый объект, в котором будут только указанные ключи:

var fruitsPicked = pick( fruits, [ 'melon', 'tomato' ] );

Метод clone()

Возвращает полную копию оригинального объекта:

var man = { weight: 80, age: 30, height: 180, gender: 'male' };
var manDouble = clone( man );
man.age = 32;
manDouble.gender = 'female';
console.log( man );
console.log( manDouble );

Метод extend()

Копируем свойства одного объекта в другой:

var brick = { weight: 200, width: 250, height: 150, thickness: 100 };
var color = { color: 'red' };
var brickFull = extend( brick, color );

underscore.js - утилиты

Метод random()

Возвращает случайное число из диапазона min - max ( включительно нижнюю и верхнюю границы )

var rnd = random( 0, 255 );

Метод now()

Возвращает текущее время:

var currTime = now();
console.log( currTime );

Метод times()

Запускаем функцию на исполнение три раза:

times(3, function () {
console.log( 'Holla!' );
});

underscore.js - template

Интересная возможность создания шаблонов. Кто знаком с HTML-шаблонизаторами (такими, как Pug (бывший Jade)), станет все сразу понятно с первого взгляда.

Кому не станет ясно с первого взгляда, есть хорошая статья у Ильи Катора - Шаблонизатор LoDash.

Но по коду, как мне кажется, станет ясно без объяснений:

<button id="add" type="button">add underscore template</button>

<div class="canvas"></div>
var users = [ 'Peter', 'Mary', 'John', 'Josef' ];
var messages = [ 'Hello', 'Holla', 'Welcome', 'Greeting' ];
var block;
var compiled = template( "<dl class='info'><dt><%= name %></dt><dd><%= message %></dd></dl>" );

function insertBlock () {
block = compiled({ name: users[ random( 0, users.length-1 ) ], message: messages[ random( 0, messages.length-1 ) ] });
document.querySelector('.canvas').innerHTML += block;
}

document.querySelector('#add').addEventListener('click', insertBlock, false);

Заключение

Личное впечатление от underscore.js - я очень доволен, что познакомился с этой библиотекой! Она крайне полезная, удобная и приятная в работе. Теперь буду стараться использовать ее там, где это понадобится.

Еще стоит заметить один немаловажный факт - некоторые методы underscore\lodash планируются\внесены в стандарт ES6 (например, методы map() и filter()).


VSC - explorer.compactFolders

В Visual Studio Code по умолчанию стоит настройка, которая отображает на владке Explorer вложенные папки таким образом:![VSC - Default Vi...… Continue reading

Flattering operators

Published on July 12, 2024

Оператор withLatestFrom

Published on July 03, 2024