Библиотека LoDash - это развитие и продолжение библиотеки Underscore.js. Если выбирать, какой библиотекой пользоваться, то выбор будет однозначно за LoDash. Достаточно сравнить колличество методов Underscore.js vs LoDash - LoDash Documentation.

Еще один большой плюс LoDash - это ее модульность. Каждый ее метод доступен в виде отдельного модуля \ модулей, который \ которые можно подключить и использовать; а не “тянуть” всю библиотеку (хотя она и небольшая - 4\22 kB).

Например, подключить только методы для работы с массивами:

var array = require('lodash/array');

Или же подключить только один метод chunk, чтобы уменьшить итоговую сборку:

var chunk = require('lodash/chunk');

Или вот так - нам нужны только два модуля random и foreach для генерации случайный фоновых заливок у коллекции блоков.

Тогда можно поступить так - устанавливаем всего два модуля вместо всей библиотеки lodash:

$ npm i --save lodash.random
$ npm i --save lodash.foreach

… и используем их, ибо только они нам нужны сейчас:

var random = require('lodash.random');
var forEach = require('lodash.foreach');

var items = document.querySelectorAll('.gallery\_\_item');
forEach( items, function (el) {
el.style.backgroundColor = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
});

В этой статье я познакомлюсь с пятью методами LoDash, которые я отложил “на потом” при знакомстве с Underscore.js. Это методы для работы с функциями - .delay(), .once(), .after(), .before(), .debounce().

lodash - .delay()

Метод .delay() очень прост, но тем не менее - весьма полезен на практике. Это возможность запускать функцию с временной задержкой.

Самый простой пример - через 2 секунды в консоли браузера появится сообщение Hello World:

delay(function (text) {
console.log(text);
}, 2000, 'Hello World' );

Справка по этому методу в официальной документации - Lodash - Delay.

lodash - .once()

Весьма полезный метод, который позволяет запустить функцию только один раз. Приведу два примера.

Простой пример с кнопкой - alert выскочит только один раз при нажатии кнопки:

function iAmOnce() {
alert('Hello!');
}
var callMeOnce = once(iAmOnce);
document.querySelector('#once').addEventListener('click', callMeOnce, false);

Второй пример интереснее. Посмотрим на разметку. Затем на javascript-код.

<div class="block"></div>
<div id="secondo" class="block">
    <div id="count"></div>
</div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>

Эта часть кода - украшательство, генерирование случайного цвета для каждого из блоков class=”block” при помощи метода .random():

\$(document).ready( function () {
var blocks = document.querySelectorAll('.block');
forEach( blocks, function (el) {
el.style.backgroundColor = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
});

Здесь функция callOnce() сработает только один раз и при условии, когда блок secondo “поднимется” от нижней границы окна браузера на высоту 85% от общей высоты окна.

iterrator “отщекнется” один раз и изменит содержимое блока id=”count” с 0 на 1:

$(document).ready( function () {
    var second = $('#secondo');
var iterrator = 0;
var counter = \$('#count').text(iterrator);

    var callOnce = once( function () {
        ++iterrator;
        counter.text(iterrator);
    });

    $(window).on('scroll', function () {
        if ( $(window).scrollTop() > second.offset().top - $(window).height() * 0.85 ) {
            callOnce();
        }
    });

});

Весьма полезный на практике прием. Официальная справка по методу .once() находится здесь - LoDash - Once Method.

lodash - .before()

Метод .before() - счетчик выполнения функции; метод запустит функцию менее n-раз (less than n times). В приведенном ниже примере клик по элементу #before сработает 9 раз и запустит функцию 9 раз (и не более того):

var sayHello = function () {
console.log('Hello');
};
document.querySelector('#before').addEventListener('click', before(10, sayHello), false);

Можно сказать и так - метод .before() ограничивает максимальное количество запусков функции - не более n-раз.

Официальная документация по методу .before() находится здесь - LoDash - Before Method.

lodash - .after()

Метод .after() - еще один счетчик выполнения функции. Прямая противоположность методу .before().

Этот метод ограничивает количество попыток запуска функции - после n-попыток функцию можно будет запускать неограниченное количество раз:

var sayAfter = function () {
console.log('After');
};
document.querySelector('#after').addEventListener('click', after(5, sayAfter), false);

Код выше выведет в консоль браузера текст After только после пятой попытки и далее сколько угодно раз. Где на практике можно применить такой подход - не приходит на ум, если честно )

Официальная документация по методу .after() находится здесь - LoDash - After Method.

lodash - .debounce()

Интересный и полезный на практике метод - запустить функцию function с временной задержкой; отсчет временной задержки идет с момента последнего запуска функции function.

Метод имеет большое количество аргументов - ссылка на официальную документацию - Lodash - Debounce Method.

Пример ниже будет выводить окно Call me after 2 second! с задержкой в 2 секунды после события resize - то есть, когда будут изменены размеры окна браузера:

var callMe = function () {
alert('Call me after 2 second!')
};
\$(window).on( 'resize', debounce( callMe, 2000 ) );

Заключение

Вот я в меру своих сил и освоил несколько интересных методов библиотеки LoDash. Наиболее полезными для меня показались методы .debounce() и .once().

Что сказать - библиотека LoDash просто замечательная и чрезвычайно полезная. К тому же она постоянно развивается - ее автор John-David Dalton не устает ее совершенствовать.

Немного пофлудил по JavaScript … останавливаться пока не собираюсь )


Статья посвящена моему знакомству с 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()).


Пример создания калькулятора на основе HTML-элемента input type=”range”. Это слайдер, у которого можно плавно изменять значения при помощи ползунка. Текущее значение хранится в атрибуте value данного элемента.

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

Что будет считать калькулятор? В качестве примера - сумму чаевых в кафе. Есть основная сумма ($), задается процент (%) чаевых для основной суммы.

Нужно посчитать результирующую сумму ($).

HTML разметка

Разметка калькулятора основана на элементах form, input type=”text”, input type=”range”. Она простая и представлена ниже:

<div class="wrapper">
    <h1 class="wrapper__title">javascript slider calculator</h1>
    <form class="calculator">
        <div class="calculator__row">
            <label for="bill">enter the bill amount for your meal: $</label>
            <input type="text" id="bill" class="calculator__bill" value="5" required/>
        </div>
        <div class="calculator__row">
            <label for="tip">tip amount: <span class="tip-amount"></span></label>
            <input type="range" min="0" max="100" value="0" step="1" class="calculator__tip" id="tip" required/>
        </div>
        <div class="calculator__row">
            <h2 class="calculator__info">tip to leave: <span class="calculator__result"></span></h2>
        </div>
    </form>
</div>

CSS стили

Стилизация для калькулятора также не отличается объемом и сложностью. Что-либо отмечать отдельно нет необходимости:

.wrapper {
    width: 800px;
    margin: 50px auto 0;
    padding: 60px 30px;
    border: 1px solid #000;
}

.wrapper__title {
    text-transform: capitalize;
    margin: 0 0 40px;
}

.calculator__row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 0 0 30px;
}

.calculator__row:last-child {
    margin-bottom: 0;
}

.calculator__row label {
    text-transform: capitalize;
    font-weight: 700;
}

.calculator__bill {
    width: 50%;
}

.calculator__tip {
    width: 60%;
}

.calculator__info {
    text-transform: capitalize;
    margin: 0;
}

JavaScript код

Функция для вычисления результирующей суммы “повешена” на элемент input type=”range” и событие change.

Другими словами, при перемещении ползунка слайдера каждый раз будет вызываться функция и производится пересчет на основе изменившегося значения атрибута value.

Принцип работы функции следующий. “Забираем” значения из элементов input type=”text” и input type=”range”. Первое значение - эту основная сумма ($); второе значение - процент (%) от основной суммы ($).

Получаем результирующую сумму путем сложения - основной суммы ($) + процент (%) от основной суммы. Результирующий вывод округляем до двух значений после запятой при помощи метода toFixed().

Помимо этого, проверяем правильность и наличие ввода в поле основной суммы ($) input type=”text”. Условие будет верным, если поле ввода окажется не пустым и содержащим только цифры:

// RANGE FUNCTION
// ----------------------------------------------------------
calculatorTip.on('change', function () {

    if ( calculatorBill.val() === '' || isNaN( calculatorBill.val() ) ) {
        alert('Enter bill amount, please!')
    } else {
        amount = calculatorBill.val() * 1;
    }

    tipAmount.text( calculatorTip.val() + '%' );
    percent = calculatorTip.val() * 1;
    result = amount + amount * ( percent / 100 );
    calculatorResult.text( result.toFixed(2) + '$' );
});

Для “украшательства” можно создать еще одну функцию, которая будет запускаться при загрузке страницы и инициализировать значения суммы ($), процентов (%) от суммы и результирующей суммы ($):

// INIT FUNCTION
// ----------------------------------------------------------
$(window).on('DOMContentLoaded', function () {
    tipAmount.text( calculatorTip.val() + '%' );
    amount = calculatorBill.val() * 1;
    percent = calculatorTip.val() * 1;
    result = amount + amount * ( percent / 100 );
    calculatorResult.text( result.toFixed(2) + '$' );
});

Весь JavaScript-код для обработки калькулятора можно представить ниже:

$(document).ready( function () {


	// VARIABLES
	// ----------------------------------------------------------

	var amount, percent, result;
	var calculator = $('.calculator');
	var calculatorBill = calculator.find('.calculator__bill');
	var calculatorTip = calculator.find('.calculator__tip');
	var calculatorResult = calculator.find('.calculator__result');
	var tipAmount = calculator.find('.tip-amount');


	// INIT FUNCTION
	// ----------------------------------------------------------

	$(window).on('DOMContentLoaded', function () {
	    tipAmount.text( calculatorTip.val() + '%' );
	    amount = calculatorBill.val() * 1;
	    percent = calculatorTip.val() * 1;
	    result = amount + amount * ( percent / 100 );
	    calculatorResult.text( result.toFixed(2) + '$' );
	});


	// RANGE FUNCTION
	// ----------------------------------------------------------

	calculatorTip.on('change', function () {

	    if ( calculatorBill.val() === '' || isNaN( calculatorBill.val() ) ) {
	        alert('Enter bill amount, please!')
	    } else {
	        amount = calculatorBill.val() * 1;
	    }

	    tipAmount.text( calculatorTip.val() + '%' );
	    percent = calculatorTip.val() * 1;
	    result = amount + amount * ( percent / 100 );
	    calculatorResult.text( result.toFixed(2) + '$' );
	});

});

Готовый пример калькулятора-слайдера можно посмотреть здесь - JavaScript Slider Calculator


На этом все. В ближайшее время буду продолжать нести JavaScript-flood )

Обзор посвящен созданию JavaScript-калькулятора. Домашнее задание для каждого начинающего JavaScript-ниндзя! ) В коде используется не чистый JavaScript, а JavaScript + jQuery.

Пример и материал не является оригинальным, основан на зарубежном источнике. Указывать источник не буду, потому что это не имеет смысла - таких кратких видеоуроков просто огромное количество - выбирай любой, какой понравится. )

HTML разметка

HTML-разметка для будущего калькулятора основана на HTML-элементах

1
form
,
1
input
,
1
button
. Ничего сверхестественного в ней нет.

Единственный момент - вопрос компоновки кнопок, куда правильно “засунуть” кнопки с арифметическими операциями:

<!-- CALCULATOR -->
<div class="calculator">
    <!-- CALCULATOR FORM -->
    <form class="calculator__form">
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <input class="calculator__display" id="display" type="text" disabled />
        </div>
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <button type="button" value="c" class="calculator__key calculator__clear"></button>
            <button type="button" value="<--" class="calculator__key calculator__backspace"></button>
            <button type="button" value="^3" class="calculator__key calculator__power"></button>
            <button type="button" value="+" class="calculator__key calculator__button"></button>
        </div>
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <button type="button" value="9" class="calculator__key calculator__button"></button>
            <button type="button" value="8" class="calculator__key calculator__button"></button>
            <button type="button" value="7" class="calculator__key calculator__button"></button>
            <button type="button" value="-" class="calculator__key calculator__button"></button>
        </div>
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <button type="button" value="6" class="calculator__key calculator__button"></button>
            <button type="button" value="5" class="calculator__key calculator__button"></button>
            <button type="button" value="4" class="calculator__key calculator__button"></button>
            <button type="button" value="*" class="calculator__key calculator__button"></button>
        </div>
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <button type="button" value="3" class="calculator__key calculator__button"></button>
            <button type="button" value="2" class="calculator__key calculator__button"></button>
            <button type="button" value="1" class="calculator__key calculator__button"></button>
            <button type="button" value="/" class="calculator__key calculator__button"></button>
        </div>
        <!-- CALCULATOR ROW -->
        <div class="calculator__row">
            <button type="button" value="0" class="calculator__key calculator__button"></button>
            <button type="button" value="." class="calculator__key calculator__button"></button>
            <button type="button" value="=" class="calculator__key calculator__key--equal"></button>
        </div>
    </form>
</div>

CSS стили

Со стилизацией будущего калькулятора тоже проблем не должно возникнуть. Я использовал flexbox для выравнивания кнопок:

.calculator {
    width: 250px;
    height: 350px;
    border: 2px solid black;
    margin: 100px auto 0;
    text-align: center;
    background-color: yellowgreen;
    box-shadow: 0 0 30px grey;
    border-radius: 4px;
}

.calculator__form {
    height: 100%;
    padding: 20px;
}

.calculator__row {
    margin: 10px 0;
    display: flex;
    justify-content: space-between;
}

.calculator__display {
    margin: 0 0 20px;
    width: 100%;
    border: 1px solid darkslateblue;
    padding: 4px 2px;
    text-align: right;
    font: 700 16px/1 Arial, sans-serif;
    color: darkslateblue;
    background-color: whitesmoke;
}

.calculator__key {
    width: 41px;
    height: 35px;
    cursor: pointer;
    border: none;
    transition: all .2s;
    text-transform: uppercase;
}

.calculator__key--equal {
    width: 98px;
}

.calculator__key:hover {
    background-color: yellow;
    font-weight: 700;
}

.calculator__key::-moz-focus-inner {
    border: none
}

JavaScript код

Переходим к самому интересному - созданию JavaScript-кода для калькулятора. В своем примере я использовал JavaScript + jQuery.

Последний применил только из-за удобства манипуляции с DOM. Недавно узнал о существовании библиотеки Underscore.js как альтернативы jQuery, но меньшего размера. Надо опробовать Underscore.js обязательно! )

Первым делом инициализируем кнопки калькулятора. Для этого забираем значения из атрибута

1
value
кнопок
1
button
и динамически вставляем в HTML-разметку:

// INIT CALC KEYS
calcKeys.each(function () {
    var current = $(this).attr('value');
    $(this).text(current);
});

Исходный вид калькулятора подготовили - он теперь выглядит как настоящий калькулятор! Если не принимать во внимание кошмарных цветов и их сочетания ) Ну мы не дизайнеры, нам простительно )

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

Просто нужно забрать у активной кнопки значение ее атрибута

1
value
и передать как значение элемента
1
input
.

Небольшая тонкость здесь - нужно конкатенировать текущее значение элемента

1
button
с текущим значением элемента
1
input
; иначе будет происходить простое замещение предыдущего значения элемента
1
input
текущим значением элемента
1
button
:

// ADD NUMBERS TO INPUT
calcButton.on('click', function () {
    calcDisplay.val( calcDisplay.val() + $(this).attr('value') );
});

Полная очистка экрана калькулятора элементарно проста - достаточно при нажатии на соответствующую кнопку передать в элемент

1
input
пустое значение:

// CLEAR INPUT
calcClear.on('click', function () {
    calcDisplay.val('');
});

Дальше уже немного интереснее - будем оживлять кнопку

1
=
. То есть - ввели в окошко калькулятора, к примеру,
1
2 + 3
. По нажатии на кнопку
1
=
в окошке должен появиться результат этой арифметической операции.

На помощь приходит функция JavaScript под названием

1
eval()
. На javascript.ru эта функция подробно расписана.

В результате код для “оживления” кнопки

1
=
будет выглядеть “скромно”:

// SHOW RESULT
calcEqual.on('click', function () {
    calcDisplay.val( eval( calcDisplay.val() ) );
});

Наш калькулятор почти готов. Осталось добавить жизни к двум последним кнопкам - возведения в степень и посимвольной очистки экрана калькулятора.

Для возведения в степень воспользуемся стандартной JavaScript-библиотекой

1
Math
и ее методом
1
pow()
.

Заберем у элемента

1
input
его текущее значение и передадим в качестве одного из аргументов в метод
1
pow()
. Второй аргумент в нашем случае - это константа 3:

// POWER BUTTON
calcPower.on('click', function () {
    calcDisplay.val( Math.pow( calcDisplay.val(), 3 ) );
});

Последний шаг к успеху создания калькулятора - это “оживление” кнопки посимвольной очистки экрана калькулятора.

В этом случае воспользуемся стандартным методом JavaScript -

1
substring()
. Это метод JavaScript(), который извлекает из строки подстроку и возвращает ее в виде новой строки - почитать с примерами.

В качестве аргументов принимает два параметра - начальную и конечную позицию, которые выступают как начальный и конечный индекс массива. Массивом в данном случае является строка.

Функция (на кнопке посимвольной очистки) в нашем случае будет работать так - забираем у элемента

1
input
его текущее значение. Значение возвращается в виде строки, конечно.

Поэтому находим ее длину (

1
length
) и укорачиваем на один (последний) символ -
1
length - 1
. Таким образом мы динамически укорачиваем текущее значение в окне калькулятора на один символ.

Затем нам нужно заменить текущее значение окна калькулятора укороченным на один символ значением. Для этого берем метод

1
substring()
и с помощью него “обрезаем” текущую строку; этот метод возвращает “обрезанный” вариант. Нам осталось только вставить его в окно калькулятора.

Результат будет выглядеть таким образом:

// BACKSPACE BUTTON
calcSpace.on('click', function () {
    calcDisplay.val( calcDisplay.val().substring(0, calcDisplay.val().length-1) );
});

Вот наш калькулятор и готов - ниже полный JavaScript-код:

$(document).ready(function () {

    // VARIABLES
    var calc = $('.calculator');
    var calcDisplay = calc.find('.calculator__display');
    var calcKeys = calc.find('.calculator__key');
    var calcButton = calc.find('.calculator__button');
    var calcClear = calc.find('.calculator__clear');
    var calcEqual = calc.find('.calculator__key--equal');
    var calcPower = calc.find('.calculator__power');
    var calcSpace = calc.find('.calculator__backspace');

    // INIT CALC KEYS
    calcKeys.each(function () {
        var current = $(this).attr('value');
        $(this).text(current);
    });

    // ADD NUMBERS TO INPUT
    calcButton.on('click', function () {
        calcDisplay.val( calcDisplay.val() + $(this).attr('value') );
    });

    // CLEAR INPUT
    calcClear.on('click', function () {
        calcDisplay.val('');
    });

    // SHOW RESULT
    calcEqual.on('click', function () {
        calcDisplay.val( eval( calcDisplay.val() ) );
    });

    // POWER BUTTON
    calcPower.on('click', function () {
        calcDisplay.val( Math.pow( calcDisplay.val(), 3 ) );
    });

    // BACKSPACE BUTTON
    calcSpace.on('click', function () { // http://www.w3schools.com/jsref/jsref_substring.asp
        calcDisplay.val( calcDisplay.val().substring(0, calcDisplay.val().length-1) );
    });

});

Конечно, примеры кода из этой статьи хоть и не являются укороченными, но не дают полного представления о разрабатываемом нами калькуляторе.

Поэтому привожу ссылку на готовый вариант калькулятора, который создавался в этой статье. На CodePen можно его посмотреть и разобрать детально (при желании).

Как вариант для сравнения, можно взглянуть на более сложный пример создания калькулятора, на чистом JavaScript. Пример был найден мною на просторах CodePen.

Заключение

Рассмотренный в статье пример лично для меня оказался на удивление прост - я ожидал большей (и более запутанной) кучи кода.

На этом все. В дальнейшем буду продолжать флудить на тему JavaScript, ибо для себя с удивлением обнаружил, что мне в последнее вермя доставляет удовольствие разбирать и рассматривать готовые примеры на JavaScript.

Единственное, что меня огорчает - тот факт, что они чужие )


Имеется такая задача - на странице расположен блок. Страница скроллится, то есть наличествует событие scroll.

Нужно сделать так, чтобы при возникновении события scroll и выполнении определенного условия срабатывала некая функция. Но при этом срабатывала только один раз!

Вариантов решения этой задачи может быть несколько. В этой статье будут показаны два из них. Все они будут выполнены на JavaScript + jQuery.

Вариант первый - метод off()

Этот способ основан на методе jQuery - .off(). Как говорится в описании, метод

1
.off()
удаляет событие, вызванное с помощью метода
1
.on()
. То есть, выполняет обратную операцию.

Рассмотрим пример ниже:

var blockScrolled = $('.scrolled');
var spins = $('.spin');

spins.text('0');

$(window).on('scroll', function () {
    if ( $(window).scrollTop() > blockScrolled.offset().top - $(window).height() / 2 ) {
        spins.each( function () {
            var current = $(this);
            $( { Spin: 0 }).animate( { Spin: current.attr('data-stop') }, {
                duration: 2000,
                easing: 'swing',
                step: function ( now ) {
                    current.text( Math.ceil( now ) );
                }
            });
        });
        $(window).off('scroll');
    }
});

Что мы имеем в этом коде? Видим, что на окно

1
window
повешен обработчик ( метод
1
.on()
`) события scroll.

При возникновении этого события выполняется проверка условия. Если условие true, то для каждого из элементов коллекции spins выполнить функцию.

Как только функция закончит свою работу, выполнение кода перемещается на строку

1
$(window).off('scroll')
. Эта строка “говорит” - на объекте
1
window
остановить событие scroll.

Мы получили нужный результат - при событии scroll и выполнении условия функция выполнится только один раз.

Один важный момент - после выполнения строки

1
$(window).off('scroll')
событие scroll будет “прибито” и дальнейшие попытки повесить handler на него не увенчаются успехом.

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

Вариант второй - маркер

Второй способ не использует метод

1
.off()
, но применяет специальный маркер. Код простой, но эффективный. И в этом случае событие scroll не “убивается”, а функция исполняется один раз:

var marker = true;

function count() {
    // function code here ...
    marker = false;
}

$( window ).on('scroll', function () {
    if ( $( window ).scrollTop() > overview.offset().top - $( window ).height() * 0.5 ) {
        if ( marker ) {
            count();
        }
    }
});

Что есть в этом коде? Есть глобальная переменная

1
marker
, у которой изначально значение
1
true
.

При выполнении условия функция

1
count()
запускается и по ходу дела меняет значение
1
marker
на противоположный. Таким образом функция
1
count()
сработает только один раз.

Но событие scroll останется “невредимым”.

Заключение

За бортом остался еще один jQuery-метод - .one(). Его задача - также запустить на исполнение функцию только один раз.

Например, в коде ниже клик мыши на каждом из элементов коллекции likeLinks сработает только один раз:

var likeLinks = $('.overlay__link-heart');
likeLinks.one('click', function () {

// увеличить значение счетчика на единицу при клике
// -----------------------------------------------------------
var currentCount = $(this).find('.overlay__link-count');
currentCount.html( currentCount.html() * 1 + 1 );

Однако, применить метод

1
.one()
для обработки события scroll и вызова на исполнение функции только один раз у меня не получилось. Событие scroll вообще не срабатывало.

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


P.S. Сразу на ум пришло одно у самого себя - заменить

1
$( window )
на переменную. Но уже лень менять. )