dronni3de
5/19/2018 - 6:23 PM

Задания на яваскрипт (простые)

Задания на яваскрипт (простые)

Простые задачи на яваскрипт

Пропустить теорию и перейти прямо к задачам

Ссылка на учебник: http://learn.javascript.ru

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

Версии яваскрипта

На 2016 год есть 3 версии: ES3, ES5 (он же ES Harmony), ES6 (aka ES Next или ES 2015). ES значит «ECMAScript», а название «JavaScript» это защищенная торговая марка и ее нельзя так просто использовать. ES3 это версия которая работает во всех браузерах с 2000 года, включая древний IE6. ES5, ES6 (подробнее на learn.JS) - новые, их уровень поддержки браузерами описан тут:

На 2016 год писать на ES5, а тем более на ES6 рановато, делать это можно, только если ты пишешь какой-то внутренний проект, например, который будет использоваться только внутри компании и у всех пользователей есть современная версия браузера. Некоторые решают эту проблему транспайлером - компилятором, преобразующим ES5/6 код в ES3.

И еще: язык Ява (Java) не имеет никакого отношения к яваскрипту, похожи в них только название и часть синтаксиса.

Строгий режим

В новом Javascript есть строгий режим:

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

Функции и замыкания

Функции — это объекты. У них есть свойства (например length) и методы (например toSource, apply и call). Функции можно хранить в переменных, передавать и возвращать из других функций:

var a = function (..) { ... }; // создаем новую функцию и поменщаем ссылку в a
a(); // вызываем
console.log(a.toString()); // вызваем метод у функции (он вернет ее текст)

Функция при создании привязывается к набору переменных родительской функции и потому видит ее переменные:

function f1() {
    var a = 1;
    var b = 2;

    function f2() {
        var d = 3;
        var e = 4;
        
        ...(код 2)..
        
        return function () {
            var f = 5;
            ...(код 3)...
        };
    }
    
    ...(код 1)...
}
  • Код 1 видит переменные a, b и функцию f2 (и f1 тоже)
  • Код 2 видит свои переменные d, e, а также родительские a, b, и f2 (и f1 тоже)
  • Код 3 видит переменную f, а также d, e, a, b, f2 (и f1 тоже)

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

Внешняя функция не видит переменных внутренней. Код, находящийся вне функций, в глобальном контексте, не видит внутренние (локальные) переменные.

Порядок создания переменных

Локальные переменные (объявленные через var) создаются при входе в функцию, до выполнения ее кода. При этом им изначально присваивается undefined:

function test() {
    console.log(a); // undefined

    var a = 2; 
    console.log(a); // 2
}

test();

Этот код выполняется так:

  • создать переменную a и присвоить ей undefined
  • выполнить первый console.log
  • присвоить a значение 2
  • выполнить второй console.log

Копирование по значению и по ссылке

Примитивные значения дублируются при копировании, копирование объектов просто копирует ссылку на один и тот же объект. Примитивные значения — это не-объекты, то есть null, undefined, числа, true/false, строки. Если ты их присваиваешь переменной, передаешь или возвращаешь из функции, создается новая независимая копия значения:

var a = "Hello";
var b = a; // В b независимая копия строки. Меняя ее, мы не изменяем то, что в a

Объекты (а это в том числе массивы (Array), функции (Function), регулярки (RegExp), даты (Date)) копируются и передаются в/из функции по ссылке:

var a = { x: 1, y: 2 };
var b = a; // в b ссылка на тот же самый объект, что и в a. Проверим:
b.x = 10;
console.log(a.x); // 10

var с = [];
function changeArray(arr) { arr.push(1); } 
changeArray(с); // в функцию передается не копия, а ссылка на тот же массив. 
console.log(c); // [1]

Сравнение объектов

Объекты (а значит и массивы, и функции, так как они тоже ими являются) сравниваются по идентичности, то есть тому, что это ссылки на один и тот же объект:

var a = {};
var b = a;
console.log(a === b);  // true
var c = {};
var d = {};
conslole.log(c === d); // false

Во втором случае у нас 2 разных объекта и получается false. {} всегда создает новый объект. По этой причине {} === {}, [] === [], function(){} === function(){} всегда дают false. И вообще, любое сравнение объекта с [] или {} даст false.

Подробнее: http://javascript.ru/comparison-operators

Ложные и правдивые значения

Falsy (ложных? лживеньких?) значений ровно 7, их надо знать наизусть: 0, -0 (да, в программировании есть отрицательный ноль), NaN, null, undefined, '' (пустая строка), false. Все остальные значения truthy, в том числе '0' (строка из символа 0). При преобразовании в логический (булев) тип falsy значения преовращаются в false, а все остальные — в true:

console.log(0 ? "truthy" : "falsy"); // falsy
console.log('0' ? "truthy" : "falsy"); // truthy
console.log({} ? "truthy" : "falsy"); // truthy
console.log([] ? "truthy" : "falsy"); // truthy
console.log('' ? "truthy" : "falsy"); // falsy
if (1) { console.log('truthy'); } else { console.log('falsy'); } // truthy

Боксинг

У примитивных значений (примитивные = не-объекты, то есть числа, строки, true/false/null/undefined) нет свойств и методов (они есть только у объектов). При попытке обратиться к свойствам/методам примитивов происходит боксинг: яваскрипт создает временный объект из примитива и обращается к нему (сам примитив остается неизменным). Для чисел создается объект «класса» Number, для true/false Boolean, для строк — String (вот мы и узнали, зачем были нужны эти встроенные классы). Для null и undefined выдается ошибка. То есть код

var a = "abc";
var b = a.length;

Превращается внутри в:

var a = "abc";
var tmp = new String(a); // происходит боксинг, создается временный объект
var b = tmp.length; // и идет обращение к свойству этого объекта
// после чего объект выкидывается

Потому присвоить свойство примитиву можно, но оно не сохранится — ведь оно создалось на временном объекте.

var x = 1;
x.test = 2;
console.log('test' in x); // false — такого свойства у x нету

Это легко объяснить, если записать что происходит с учетом боксинга:

var x = 1;
var tmp1 = new Number(x);
tmp1.test = 2; // свойство присвоилось временному объекту
var tmp2 = new Number(x);
console.log('test' in tmp2); // а ищем мы его уже в другом объекте, естественно его там нет

Боксинг сделан для того, чтобы с примитивами можно было работать как с объектами, например, вызывая у них методы. Само разделение на примитивы и объекты сделано из-за соображений производительности: если бы числа и строки были настоящими объектами, все работало бы медленнее. Если тебе понадобится (100% что не понадобится), ты можешь сделать боксинг и анбоксинг вручную:

var a = "abc";
var aInABox = new String(a); // ручной боксинг

var fiveInABox = new Number(5);
var five = fiveInABox.valueOf(); // ручной анбоксинг, в five лежит примитив - число 5

Статья по теме на англ.: http://www.jisaacks.com/javascript-boxing/

Задачки на JS

Проверялка для первых 10 задачек от @dkab: http://dkab.github.io/jasmine-tests/ (робот не совершенен и может ошибаться. Если ты думаешь, что он не прав, напиши на codedokode@gmail.com и не забудь приложить свою программу — мы разберемся и вправим ему мозги).

  1. Напиши функцию создания генератора sequence(start, step). Она при вызове возвращает другую функцию-генератор, которая при каждом вызове дает число на 1 больше, и так до бесконечности. Начальное число, с которого начинать отсчет, и шаг, задается при создании генератора. Шаг можно не указывать, тогда он будет равен одному. Начальное значение по умолчанию равно 0. Генераторов можно создать сколько угодно.

    var generator = sequence(10, 3);
    var generator2 = sequence(7, 1);
    
    console.log(generator()); // 10
    console.log(generator()); // 13
    
    console.log(generator2()); // 7
    
    console.log(generator()); // 16
    
    console.log(generator2()); // 8
    
  2. Также, нужна функция take(gen, x) которая вызвает функцию gen заданное число (x) раз и возвращает массив с результатами вызовов. Она нам пригодится для отладки:

    var gen2 = sequence(0, 2);
    console.log(take(gen2, 5)); // [0, 2, 4, 6, 8 ]
    
  3. Напиши функцию map(fn, array), которая принимает на вход функцию и массив, и обрабатывает каждый элемент массива этой функцией, возвращая новый массив. Пример:

    function square(x) { return x * x; } // возведение в квадрат
    console.log(map(square, [1, 2, 3, 4])); // [1, 4, 9, 16]
    console.log(map(square, [])); // []
    

    Обрати внимание: функция не должна изменять переданный ей массив:

    var arr = [1, 2, 3];
    console.log(map(square, arr)); // [1, 4, 9]
    console.log(arr); // [1, 2, 3]
    

    Это аналог array_map из PHP.

  4. Напиши функцию fmap(a, gen), которая принимает на вход 2 функции, a и gen, где gen — функция-генератор вроде той, что была в первом задании. fmap возвращает новую функцию-генератор, которая при каждом вызове берет следующее значение из gen и пропускает его через функцию a. Пример:

    var gen = sequence(1, 1);
    function square(x) { return x * x; }
    var squareGen = fmap(square, gen);
    
    console.log(squareGen()); // 1
    console.log(squareGen()); // 4
    console.log(squareGen()); // 9
    console.log(squareGen()); // 16
    

    А, еще, сделай тогда, чтобы в качестве gen можно было указать функцию с аргументами, и при вызове

    function add(a, b) { 
        return a + b; 
    }
    
    // Мы получаем новую функцию, которая вызвает add, и результат пропускает через функцию square
    var squareAdd = fmap(square, add);
    console.log(squareAdd(2, 3)); // 25 = (2 + 3) ^ 2
    console.log(squareAdd(5, 7)); // 144 = (5 + 7) ^ 2
    

    Эти аргументы бы передавались функции gen. Аргументов может быть любое количество.

    Подсказка: если непонятно, как сделать функцию, принимающую произвольное число аргументов, то стоит погуглить про псевдопеременную arguments (псевдопеременная значит, что она выглядит как переменная, но формально ей не является). Чтобы понять, как вызвать функцию с заранее неизвестным числом аргументов, можно погуглить Function.prototype.call и Function.prototype.apply. В JS функции - это объекты, и у них есть полезные методы и свойства.

  5. Частичное применение (partial application)

    вики: http://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5

    Напиши функцию partial(fn, a1, a2, ....), которая позволяет зафиксировать один или несколько аргументов функции. Пример:

    function add(a, b) { return a + b; }
    function mult(a, b, c, d) { return a * b * c * d; }
    
    var add5 = partial(add, 5); // Мы получили функцию с 1 аргументом, которая прибавляет к любому числу 5
    
    console.log(add5(2)); // 7
    console.log(add5(10)); // 15
    console.log(add5(8)); // 13
    
    var mult23 = partial(mult, 2, 3); // мы зафиксировали первые 2 аргумента mult() как 2 и 3
    
    console.log(mult23(4, 5)); // 2*3*4*5 = 120
    console.log(mult23(1, 1)); // 2*3*1*1 = 6
    

    Есть функция с аргументами:

    f1(a, d, c, d)
    

    Мы можем с помощью partial сделать из нее функцию с меньшим числом аргументов, заранее задав значения для нескольких из них, например:

    var f2 = partial(f1, 1, 2); // фиксируем a = 1, b = 2
    

    И вызов:

    f2(x, y)
    

    будет равносилен вызову:

    f1(1, 2, x, y)
    

    Кстати, имеющийся в новых версиях JS метод bind() тоже может делать частичное применение: http://frontender.info/partial-application-in-javascript-using-bind/ Но ты должен обойтись без его использования, и написать свой велосипед.

  6. Наша функция partial позволяет фиксировать только первые аргументы. Усовершенствуй ее, чтобы зафиксировать можно было любые аргументы, пропущенные аргументы обозначаются с помощью undefined. Обрати внимание, что теперь мы переименовали ее в partialAny, чтобы не путать с предыдущей:

    function test(a, b, c) { return 'a=' + a + ',b=' + b + ',c=' + c; }
    var test1_3 = partialAny(test, 1, undefined, 3);
    console.log(test1_3(5)); // a=1,b=5,c=3
    
  7. напиши функцию bind, которая позволяет привязать контекст (значение this) к функции:

    window.x = 1;
    var ctx = { x: 2 };
    
    function testThis(a) { console.log("x=" + this.x + ", a=" + a); }
    console.log(testThis(100)); // x=1, a=100
    var boundFunction = bind(testThis, ctx);
    console.log(boundFunction(100)); // x=2, a= 100
    

    В новых браузерах и функций есть метод bind(), делающий аналогичную вещь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

    В библиотеках тоже есть такой метод: http://lodash.com/docs#bind

  8. напиши функцию pluck, которая берет массив объектов и возвращает массив значений определенного поля:

    var characters = [
      { 'name': 'barney', 'age': 36 },
      { 'name': 'fred', 'age': 40 }
    ];
    
    console.log(pluck(characters, 'name')); // ['barney', 'fred']
    

    Такая функция была в lodash: http://lodash.com/docs#pluck но теперь вместо нее советуют использовать map: https://lodash.com/docs/4.15.0#map

    Функция не должна изменять исходный массив.

  9. напиши функцию filter, которая принимает функцию-предикат и массив. Возвращает она массив значений, для которых предикат вернет true.

    var input = [1, 2, 3, 4, 5, 6];
    function isEven(x) { return x % 2 == 0; } // проверяет на четность
    console.log(filter(input, isEven)); // [2, 4, 6]
    

    Функция не должна изменять исходный массив:

    console.log(input); // [1, 2, 3, 4, 5, 6]
    

    Аналог из lodash: http://lodash.com/docs#filter В новых браузерах у массивов есть метод filter.

  10. Напиши функцию, считающую число свойств в объекте:

    var a = { a: 1, b: 2 };
    console.log(count(a)); // 2
    var b = function () {};
    console.log(count(b)); // 0
    var c = [1, 2, 3];
    console.log(count(c)); // 3
    var d = [];
    d[100] = 1;
    console.log(count(d)); // 1
    

    Кстати, в новых браузерах с поддержкой Javascript ES5 есть метод Object.keys(x), возвращающий массив ключей у объекта.

  11. дан список вида «страна, город, население»: http://ru.wikipedia.org/wiki/%D0%A1%D0%B0%D0%BC%D1%8B%D0%B5_%D0%BD%D0%B0%D1%81%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D1%8B%D0%B5_%D0%B3%D0%BE%D1%80%D0%BE%D0%B4%D1%81%D0%BA%D0%B8%D0%B5_%D0%B0%D0%B3%D0%BB%D0%BE%D0%BC%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8#.D0.98.D1.81.D0.BF.D0.BE.D0.BB.D1.8C.D0.B7.D0.BE.D0.B2.D0.B0.D0.BD.D0.BD.D1.8B.D0.B5_.D0.BC.D0.B5.D1.82.D0.BE.D0.B4.D1.8B

    Можешь взять оттуда первые 5-10 городов и перенести в код. Города в списке могут идти в произвольном порядке. Напиши программу, которая отберет и выведет N самых населенных городов по убыванию числа жителей.

  12. Некая сеть фастфудов предлагает несколько видов гамбургеров:

    • маленький (50 тугриков, 20 калорий)
    • большой (100 тугриков, 40 калорий)

    Гамбургер может быть с одним из нескольких видов начинок (обязательно):

    • сыром (+ 10 тугриков, + 20 калорий)
    • салатом (+ 20 тугриков, + 5 калорий)
    • картофелем (+ 15 тугриков, + 10 калорий)

    Дополнительно, гамбургер можно посыпать приправой (+ 15 тугриков, 0 калорий) и полить майонезом (+ 20 тугриков, + 5 калорий). Напиши программу, расчиытвающую стоимость и калорийность гамбургера. Используй ООП подход (подсказка: нужен класс Гамбургер, константы, методы для выбора опций и рассчета нужных величин).

    Код должен быть защищен от ошибок. Представь, что твоим классом будет пользоваться другой программист. Если он передает неправильный тип гамбургера, например, или неправильный вид добавки, должно выбрасываться исключение (ошибка не должна молча игнорироваться).

    Написанный класс должен соответствовать следующему jsDoc описанию (то есть содержать указанные методы, которые принимают и возвращают данные указанного типа и выбрасывают исключения указанного типа. Комментарии ниже можно скопировать в свой код):

    /**
    * Класс, объекты которого описывают параметры гамбургера. 
    * 
    * @constructor
    * @param size        Размер
    * @param stuffing    Начинка
    * @throws {HamburgerException}  При неправильном использовании
    */
    function Hamburger(size, stuffing) { ... } 
    
    /* Размеры, виды начинок и добавок */
    Hamburger.SIZE_SMALL = ...
    Hamburger.SIZE_LARGE = ...
    Hamburger.STUFFING_CHEESE = ...
    Hamburger.STUFFING_SALAD = ...
    Hamburger.STUFFING_POTATO = ...
    Hamburger.TOPPING_MAYO = ...
    Hamburger.TOPPING_SPICE = ...
    
    /**
    * Добавить добавку к гамбургеру. Можно добавить несколько
    * добавок, при условии, что они разные.
    * 
    * @param topping     Тип добавки
    * @throws {HamburgerException}  При неправильном использовании
    */
    Hamburger.prototype.addTopping = function (topping) ...
    
    /**
     * Убрать добавку, при условии, что она ранее была 
     * добавлена.
     * 
     * @param topping   Тип добавки
     * @throws {HamburgerException}  При неправильном использовании
     */
    Hamburger.prototype.removeTopping = function (topping) ...
    
    /**
     * Получить список добавок.
     *
     * @return {Array} Массив добавленных добавок, содержит константы
     *                 Hamburger.TOPPING_*
     */
    Hamburger.prototype.getToppings = function () ...
    
    /**
     * Узнать размер гамбургера
     */
    Hamburger.prototype.getSize = function () ...
    
    /**
     * Узнать начинку гамбургера
     */
    Hamburger.prototype.getStuffing = function () ...
    
    /**
     * Узнать цену гамбургера
     * @return {Number} Цена в тугриках
     */
    Hamburger.prototype.calculatePrice = function () ...
    
    /**
     * Узнать калорийность
     * @return {Number} Калорийность в калориях
     */
    Hamburger.prototype.calculateCalories = function () ...
    
    /**
     * Представляет информацию об ошибке в ходе работы с гамбургером. 
     * Подробности хранятся в свойстве message.
     * @constructor 
     */
    function HamburgerException (...) { ... }
    

    Комментарии. Эта задача вызывает много непонимания, потому внимательно прочти эти комментарии перед решением.

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

    Почему? Потому что каждый должен заниматься своим делом, класс должен только обсчитывать гамбургер, а вводом-выводом пусть занимаются другие. Иначе мы получим кашу, где разные функции смешаны вместе.

    Типы начинок, размеры надо сделать константами. Никаких магических строк не должно быть.

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

    // маленький гамбургер с начинкой из сыра
    var hamburger = new Hamburger(Hamburger.SIZE_SMALL, Hamburger.STUFFING_CHEESE);
    // добавка из майонеза
    hamburger.addTopping(Hamburger.TOPPING_MAYO);
    // спросим сколько там калорий
    console.log("Calories: %f", hamburger.calculateCalories());
    // сколько стоит
    console.log("Price: %f", hamburger.calculatePrice());
    // я тут передумал и решил добавить еще приправу
    hamburger.addTopping(Hamburger.TOPPING_SPICE);
    // А сколько теперь стоит? 
    console.log("Price with sauce: %f", hamburger.calculatePrice());
    // Проверить, большой ли гамбургер? 
    console.log("Is hamburger large: %s", hamburger.getSize() === Hamburger.SIZE_LARGE); // -> false
    // Убрать добавку
    hamburger.removeTopping(Hamburger.TOPPING_SPICE);
    console.log("Have %d toppings", hamburger.getToppings().length); // 1
    

    При неправильном использовании класс сообщает об этом с помощью выброса исключения: (урок на примере PHP: https://gist.github.com/codedokode/65d43ca5ac95c762bc1a , учебник: https://learn.javascript.ru/exception )

    // не передали обязательные параметры
    var h2 = new Hamburger(); // => HamburgerException: no size given
    
    // передаем некорректные значения, добавку вместо размера
    var h3 = new Hamburger(Hamburger.TOPPING_SPICE, Hamburger.TOPPING_SPICE); 
    // => HamburgerException: invalid size 'TOPPING_SAUCE'
    
    // добавляем много добавок
    var h4 = new Hamburger(Hamburger.SIZE_SMALL, Hamburger.STUFFING_CHEESE);
    hamburger.addTopping(Hamburger.TOPPING_MAYO);
    hamburger.addTopping(Hamburger.TOPPING_MAYO); 
    // HamburgerException: duplicate topping 'TOPPING_MAYO'
    

    Обрати внимание в коде выше на такие моменты:

    • класс не взаимодействует с внешним миром. Это не его дело, этим занимается другой код, а класс живет в изоляции от мира
    • обязательные параметры (размер и начинка) мы передаем через конструктор, чтобы нельзя было создать объект, не указав их
    • необязательные (добавка) добавляем через методы
    • имена методов начинаются с глагола и имеют вид «сделайЧтоТо»: calculateCalories(), addTopping()
    • типы начинок обозначены "константами" с понятными именами (на самом деле просто свойствами, написанным заглавными буквами, которые мы договорились считать "константами")
    • об исключительных ситуациях сообщаем с помощью исключений
    • объект создается через конструктор - функцию, которая задает начальные значения полей. Имя конструктора пишется с большой буквы и обычно является существительным: new Hamburger(...)
    • "константы" вроде могут иметь значение, являющееся строкой или числом. От смены значения константы ничего не должно меняться (то есть эти значения не должны где-то еще быть записаны). К сожалению, в отличие от других языков (Java, PHP, C#) при опечатке в имени такой "константы" интепретатор JS не укажет на ошибку
    • в свойствах объекта гамбургера логично хранить исходные данные (размер, тип начинки), а не вычисленные из них (цена, число калорий и т.д.). Рассчитывать цену и калории логично в тот момент, когда это потребуется, а не заранее.
    • в JS нет синтаксиса, чтобы пометить свойство или метод приватным (доступным для использования только внутри класса), потому некоторые разработчики начинают их имена с подчеркивания и договариваются, что извне класса к ним никто не обращается. Вообще, в JS нет нормальных классов, потому многое основано на таких договоренностях.

    В дополнение, вот еще инструкция, как решать задачи на ООП. Когда ты решаешь задачу на ООП, ты должен ответить на вопросы:

    • какие есть сущности, для которых мы сделаем классы? (Гамбургер).
    • какие у них есть свойства (размер, начинка, добавки). Цена или калории не являются свойствами так как они вычисляются из других свойств и хранить их не надо.
    • что мы хотим от них получить (какие у них должны быть методы). Например, сколько стоит гамбургер?
    • как сущности связаны? У нас одна сущность «Гамбургер» и она ни с чем не связана.

    Заметь также, что в моем примере класс не взаимодействует с внешним миром. За это отвечает внешний относительно него код. Потому наш класс унивесален: ты можешь использовать его в консоли, выводя данные через console.log, а можешь приделать навороченный HTML-интерфейс с кнопками для запуска на планшете с тачскрином. Именно в таком стиле ты должен писать ООП код.

    Послесловие. Если ты внимательно читал учебник по JS, то наверно знаешь, что в JS нет классов, а до версии ES5 нет и констант (а как же решать задачу?). Классы в JS имитируются разными споcобами, всякими костылями самый общеупотребимый - через добавление методов в прототипы объекта:

    Если ты знаешь ООП в каком-то другом языке (например PHP, Java, Python), вот список соответствий между другими языками и JS:

    • класс = функция-конструктор + прототип
    • конструктор = функция с именем с большой буквы
    • поле объекта = создается в конструкторе через this.x = 1;
    • метод = функция на прототипе
    • константа класса = свойство, добавленное к функции-конструктору и написанное большими буквами: Hamburger.SOMETHING = 'something';
    • статические поля и методы = свойства добавленные к функции: Hamburger.somStaticMethod = function () { .. };
    • private/public = нету, приватные свойства и методы можно обозначать подчеркиванием в начале
    • интерфейсы, абстрактные классы и методы = нету
    • наследование = в ES5 делается через Object.create, в ES3 через хак с прототипами (описан в learn.javascript.ru)

    В ES5 константы можно реализовывать через свойства только для чтения. В ES6 добавлен синтаксис для классов (с константами), но для начала научись делать все по старинке, на ES3, так как такой код встретится в 99% библиотек. Вот новый синтаксис: http://frontender.info/es6-classes-final/

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

    ES3, ES5, ES6 - это версии JS. ES3 это версия которая работает во всех браузерах с 2000 года. ES5, 6 - новые, их уровень поддержки описан тут:

13. В одном городе есть электрическая сеть. К ней могут быть подключены:

  • электростанции, вырабатывают мощность от 1 до 100 мегаватт
  • солнечные панели, генерируют от 1 до 5 мегаватт днем (в зависимости от вида панели, то есть некоторые панели генерируют 1 мегаватт, некоторые 2 и так далее) и 0 ночью
  • жилые дома, в них от 1 до 400 квартир, потребляют 4 кВт на квартиру днем и 1 кВт ночью.
  • линии электропередач, ведущие в другие города, по ним может подаваться недостающая или отдаваться лишняя энергия. У линий есть свойство «мощность», которая определяет, сколько мегаватт можно передать по ней, а также «цена мегаватта», которое показывает сколько можно получить или придется заплатить за переданный/полученный мегаватт. На разных линиях может быть разная цена.

Дан список всех элементов электросети. Напиши программу, рассчитывющую, сколько электричества необходимо закупить (или можно продать) днем и ночью для обеспечения баланса и сколько это будет стоить (или принесет прибыли). Используй продвинутый ООП подход для решения задачи.

14. напиши функцию, определяющую тип переменной. Результат должен быть одной из строк: 'undefined', 'boolean' (для true/false), 'null', 'number', 'string', 'function', 'array', 'array-like', 'object'

array-like — это псевдомассив, то есть объект, у которого есть неотрицательное свойство length и элементы с 0 до length - 1. Примеры псевдомассивов:

  • { length: 2, 0: 'a', 1: 'b', hello: 'world'}
  • псевдопеременная arguments
  • коллекции, которые возвращают функции работы с DOM, вроде document.images, document.getElementsByTagName(..), document.body.children

В JS есть оператор typeof, но у него есть подвохи:

  • typeof null дает 'object'
  • typeof [] дает 'object'

Определение массива через [] instanceof Array не сработает, если массив был создан в друго вкладке или фрейме браузера, так как в каждой вкладке свой объект window и свой window.Array.

В новых браузерах появился метод Array.isArray ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray ), но хорошо бы иметь универсальное решение.

Обычно для решения этой задачи используется хак с Object.prototype.toString.call(...): https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/toString#Example:_Using_toString_to_detect_object_type (обрати внимание: JS позволяет с помощью call() и apply() вызвать метод на произольном объекте, передав в качестве this что угодно).

15. Напиши функцию неглубокого копирования объектов и массивов.

По умолчанию, как ты наверно, знаешь, при копировании объектов или массивов (которые являются объекты) мы копируем лишь ссылку на тот же самый объект:

var a = [1, 2, 3];
var b = a; // b указывает на тот же массив
b.push(4);
console.log(a); // [1, 2, 3, 4]

Функция неглубокого копирования должна создавать новый массив/объект, и копировать в него элементы из старого. При этом сами элементы копируются по ссылке:

var a = { x: 1, y: 2, z: [1, 2, 3] };
var b = shallowCopy(a); // b — это отдельный объект
b.x = 10;
console.log(a.x); // 1

// Но a.z и b.z указывают на один и тот же массив: 
b.z.push(4);
console.log(a.z); // [1, 2, 3, 4]

Если в функцию копирования передан объект Date, надо создавать копию того же типа.

var c = new Date(2014, 1, 1);
var d = shallowCopy(c);
d.setFullYear(2015);
console.log(c.getFullYear()); // 2014

В библиотеке lodash для неглубокого копирования есть функция clone: http://lodash.com/docs#clone

16. Напиши функцию глубокого копирования объектов и массивов. Она должна делать не только копию переданного объекта/массива, но и копии вложенных них объектов/массивов. Также, копироваться должны объекты встроенного в JS конструктора Date

var a = { x: 1, y: 2, z: [1, 2, 3], u: undefined, v: null, w: new Date(2014, 1, 1, 12, 0, 0) };
var b = deepCopy(a); // b — это отдельный объект
b.x = 10;
console.log(a.x); // 1

// a.z и b.z указывают на разные массивы: 
b.z.push(4);
console.log(a.z); // [1, 2, 3]

// a.w и b.w независимы друг от друга
b.w.setFullYear(2015);
console.log(a.w.getFullYear()); // 2014

Решать можешь потом, так как сейчас не факт что ты сможешь их решить. Правильные ответы можно увидеть например в коде библиотеки lodash:

DOM, который построил Джек

Дальше идут задания на работу с DOM и событиями. DOM = Document Object Model — это набор объектов, которые соответствуют содержимому HTML-страницы, и позволяют взаимодействовать с и изменять содержимое страницы в браузере. Обычно каждому тегу на странице соответствует отдельный узел дерева DOM. Узлы образуют дерево, и для каждого узла можно получить родительский узел, список узлов-детей, соседние узлы.

CSSOM = CSS Object Model — это свойства и методы этих объектов, которые позволяют изменять CSS-стили элементов, а также получать информацию об их размерах и положении.

События в браузере — это события движения мыши, нажатия клавиш, прокрутки страницы. Ты можешь подписываться на эти события, и указанная тобой функция будет вызваться в случае их возникновения.

Теория для изучения (читать можно параллельно с решением задач):

Задачки надо решать без использования сторонних библиотек (вроде JQuery).

Задачки:

1.Работа с классами

Дан узел DOM. Сделай функции hasClass(node, klass), addClass(node, klass), removeClass(node, klass), которые позволяют проверить, есть ли у элемента заданный CSS-класс, добавить к нему класс (если его еще нет) и удалить класс.

Учти, что у элемента может быть несколько классов, которые могут быть разделены одним или нескольким пробельными символами (пробел, \t, \f, \r, перевод строки \n — все эти символы ищутся с помощью \s в регулярке). Ты можешь спросить, что за идиот придумал разделять классы с помощью непонятных спецсимволов типа \f? Не знаю, но так написано в стандарте.

Если удалены все классы, то удалять аттрибут class="" не надо, пусть остается.

Примеры:

// вспомогательная функция для создания ноды
function createNode(name, klasses) {  
    var n = document.createElement(name);
    n.className = klasses;
    return n;
}

function l(x) {
    console.log(x);
}

l(hasClass(createNode('div', 'test'), 'test')); // true
l(hasClass(createNode('div', 'test'), 'tes')); // false

l(hasClass(createNode('div', 'test1 test2'), 'tes')); // false
l(hasClass(createNode('div', 'test1 test2'), 'test1')); // true

В современных браузерах и HTML 5 у узлов DOM есть свойство classList:

Но решение должно работать и в браузерах без classList. Вот тебе в помощь код таких функций:

2. Поле

Сделай поле из белых клеточек (клеточка может иметь размер около 28×28 пикселей). При клике на клеточку она должна менять цвет на черный. Под таблицей должна быть кнопка «поменять цвета». При ее нажатии все цвета клеточек меняются на противоположные.

Делать поле удобно с помощью элемента <table>. Саму таблицу надо не вставить в исходный код, а сгенерировать и добавить в DOM страницы яваскриптом.

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

Чтобы поменять цвета всех клеточек сразу, необязательно обходить их в цикле. Если помечать нажатые клетки определенным классом, то перекрасить их все одновременно можно, поменяв класс на самой таблице.

Ты можешь заметить, что событие click срабатывает после отпускания левой кнопки мыши, а mousedown — при нажатии (любой) и с ним получается ощущение более быстрого отклика.

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

Информация по событиям: http://learn.javascript.ru/events

3. Сапер

Сделал поле из предыдущей задачи? Отлично, давай превратим его в игру «Сапер». Wiki: http://ru.wikipedia.org/wiki/%D0%A1%D0%B0%D0%BF%D1%91%D1%80_(%D0%B8%D0%B3%D1%80%D0%B0)

Идея игры такая: на игровом поле где-то спрятаны мины. Игрок кликает по клеткам, открывая их. Если в клетке была мина, игрок проиграл. Если нет, то в клетке выводится цифра, показывающая общее число мин в соседних 8 клетках. Если игрок открыл все клетки, кроме заминированных, он победил. Если игрок открывает клетку, рядом с которой нет мин, то все соседние клетки открываются автоматически (если на них тоже нет мин, то процесс продолжается).

Правой кнопкой мыши на неоткрытых клетках можно расставлять флажки.

Надпись «Вы победили» или «Вы проиграли» должна выводиться в окошке поверх игрового поля и содержать кнопку «Новая игра».

В качестве иконки для бомбы и флажка можешь взять какой-нибудь юникодный символ отсюда: http://unicode-table.com/ru/#miscellaneous-symbols

Как создать всплывающее окошко? Идея такая: делаем шаблон окошка и встраиваем его в страницу, примерно так:

<script type="text/x-template" id="template-popup">
    <div class="popup-body">
        {{text}}
        <button type="button">Новая игра</button>
    </div>
</script>

Заметь, я использовал тег script чтобы шаблон воспринимался браузером не как часть HTML-кода страницы, а как текст который не надо никак интерпретировать (браузер не будет пытаться выполнить код как яваскрипт, так как в аттрибуте type стоит не text/javascript. Буква x нужна так как мы придумали свое, нестандартное значение). Затем, когда требуется вывести окошко, берем текст этого шаблона, заменяем в нем конструкции вроде {{text}} на нужное нам значение, создаем элемент div, вставляем в него получившийся HTML и добавляем div в DOM. Для закрытия окошка — удаляем этот div.

Ну и разумеется требуется написать CSS-код чтобы окошко например выводилось по центру экрана.

Почему я предлагаю встроить шаблон в страницу, а не поместить его в переменную в JS коде, как здесь?

var template = '<div class="popup-body">\n\
        {{text}}....\n\
';

Потому что, во-первых, писать HTML внутри JS-строки очень неудобно, а во-вторых, HTML-разметка должна храниться в HTML-файле, а не в JS.

Если ты решил эту задачу, то почитай более сложную версию, где требуется решить ту же задачу с применением подхода MVC: https://github.com/codedokode/pasta/blob/master/js/minesweeper-mvc.md

Задача снабжена подробными комментариями, она поможет освоить тебе разделение кода по MVC, отслеживание изменений в модели, реализацию отмены сделанных ходов, а полученные знания наверняка пригодятся тебе при написании более сложных приложений. Упоминаются knockout, angular, react.

4. Поиск по селектору

Напиши функцию dom.find(selector, context) которая ищет все элементы, соответствующие селектору selector внутри элемента context (если он не указан, то во всем документе). selector может иметь такой вид:

  • для поиска всех тегов с определенным именем: div
  • для поиска по классу: .some-class
  • для поиска по id: #element

Функция должна быть максимально кроссбраузерной. В современных браузерах есть такие функции для поиска: querySelectorAll(), getElementsByClassName(), getElementsByTagName(), document.getElementById(). В старых браузерах первая и вторая могут отсутствовать, и возможно для них придется просто искать элемент полным обходом дерева DOM (что конечно медленнее, чем использовать готовую функцию).

Стоит учесть, что функция document.getElementById() не ищет отсоединенные элементы и ветки элементов DOM, не вставленные в документ с помощью функций вроде appendChild().

Собственно, если querySelectorAll() есть, то можно сразу передать ей селектор, так как эта функция ищет все элементы, соответствующие указанному CSS селектору.

Информация: http://learn.javascript.ru/searching-elements-dom

Решил задачу и хочешь усложнить себе жизнь? Сделай дополнительно такие возможности:

  • можно указать несколько условий для элемента: div.class1.class2#id3
  • можно искать элемент, находящийся внутри другого: div.class1 a.class2

Для этого я советую разобрать селектор на массив условий вида { tagName: null, classes: ['a', 'b'], id: null } с которыми работать будет проще. То есть селектор #id1 div.class1.class2 a превратится в массив:

[
    { tagName: null, id: 'id1', classes: [] },
    { tagName: 'div', id: null, classes: ['class1', 'class2']  },
    { tagName: 'a', id: null, classes: [] }
]

Распарсить можно например регулярными выражениями.

Функция поиска по селектору (причем с более мощными возможностями) есть во многих библиотеках, например, в JQuery, где она вызывается так:

var elements = $('div.class1 span.class2');
console.log(elements.length); // число найденных элементов
console.log(elements[0]); // Первый из найденных элементов

5. Всплывающее окно (сложная)

Напиши JS-код, который позволит привязать к любому элементу высплывающее окно (попап), которое будет появляться по клику (а для полей ввода — при установке курсора в поле). Повторный клик убирает окно (для поля ввода надо убрать курсор из поля).

Вот как это может выглядеть:

Естественно, мы хотим, чтобы добавить такое окно на страницу можно было как можно проще. Для этого я предлагаю сделать такую разметку. На кнопке или поле ввода мы указываем текст пояснения и сторону, с которой оно должно появиться (left|right|above|below, по умолчанию below):

<input type="email" 
    data-popup="Этот адрес будет использоваться...."
    data-popup-side="right">

Для окошка, содержащего сложную разметку, мы помещаем HTML-разметку в блок script, а в аттрибуте указщываем id этого скрипта:

<button type="button"
    data-popup-id="template-login-popup"
    data-popup-side="below">войти на сайт</button>

<!-- HTML-разметка для окошка -->
<script type="text/x-template" id="template-login-popup">
    <div>.....введите ваш логин и пароль ....
</script>

Размер всплывающего окошка должен определяться содержимым. Вставлять DOM-элемент окошка лучше всего перед кнопкой. Тебе наверно понадобятся CSSOM-свойсва и функции: offsetWidth, offsetHeight, clientWidth, clientHeight, getBoundingClientRect(), scrollLeft, scrollTop, offsetLeft, offsetTop. Надо понимать, что кнопка может быть спозиционирована по-разному, и ее положение например может меняться при изменении размера окна браузера.

Ловить события клика по кнопкам можно одним обработчиком на документе. Событие focus не всплывает и ловить события фокуса поля ввода на документе придется по-хитрому (в IE — событие focusin, в новых браузерах — захватом события focus, это описано по ссылке ниже).

Информация: http://learn.javascript.ru/focus-blur

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

Если места с одной стороны от кнопки недостаточно, окошко может появиться с противоположной стороны. Окошко выравнивается по центру с элементом, но, если места недостаточно, может сдвигаться. То, есть можно использовать такой алгоритм. Допустим, мы хотим показать окошко справа от кнопки.

  • определяем сколько свободного места есть справа и сколько слева
  • создаем див слева и сверху от страницы (чтобы он был не виден), пробуем поместить в него контент и проверяем, какую ширину он занимает. Таким образом мы определим ширину и высоту содержимого окошка.
  • если оно поместится справа, то размещаем окошко справа. Иначе, если слева места больше и оно туда поместится, то слева
  • выравниваем середину окошка с серединой кнопки. Если при этом верхний край окошка уходит за окно браузера, и снизу есть место, сдвигаем окошко вниз. Аналогично в противоположном случае.
  • если окошко уходит за верх или левый край документа, то выдвигаем его оттуда, так как в этом случае его никак не увидеть

DOM + jQuery

jQuery — это популярная библиотека для поиска элементов в DOM, изменения их свойств, анимации, обработки событий, отправки ajax запросов. jQuery это не более, чем удобная обертка над методами DOM и BOM и потому мы сначала изучали сам DOM.

Есть разные мнения за и против использования jQuery. С одной стороны, она экономит время и используется везде, она очень удобна, с другой стороны у нее очень сложный код со множеством хаков, она большая (и ты не можешь взять только нужные компоненты), а некоторые вещи в современных браузерах реализованы нативно лучше, чем в jQuery. Например, jQuery делает анимацию за счет яваскрипта, в то время как в современных браузерах есть более быстрая CSS3-анимация.

Ради исправления особенностей DOM в разных браузерах jQuery использует свои аналоги объектов XMLHttpRequest (для аякс-запросов) и свою реализацию обработчиков событий.

jQuery создает 2 одинаковых глобальных переменных с именами jQuery и $ (в JS доллар можно использовать в имени переменной). Объект jQuery представляет собой псевдомассив, то есть имеет свойство length и хранит DOM-элементы в свойствах 0, 1, 2 и т.д., но при этом не является настоящим массивом:

var nodes1 = $('.class1'); // Ищет все элементы с классом class1
console.log(nodes1[0]); // Выводим первый элемент

var node2 = $('<div>text1</div><div>text2</div>'); // создает указаннаые элементы из HTML
console.log(node2[1].innerHTML); // 'text2'

Далее, с группой созданных или найденных элементов можно делать разные манипуляции. Обычно функции модификации возвращают тот же объект jQuery, и их можно писать в цепочку, например:

nodes1.
    addClass('some-class some-other-class').  // Добавить классы
    hide().                        // Скрыть
    fadeIn();                      // Начать анимацию плавного появления

nodes1.css('margin-left', '+10px');// Увеличить margin-left на 10px
nodes1.text('Hello World');        // Задать внутренний текст


// jQuery также умеет читать значения свойств элемента 
// и определять его размеры и положение
var first = nodes1.first();
console.log(first.css('background-color'));
console.log(first.innerWidth()); // внутренняя ширина
console.log(first.outerWidth()); // внешняя, с паддингом и бордером
console.log(first.offset().top); // расстояние от верхнего края документа

Ты можешь расширять возможности jQuery, добавляя свои методы в объект jQuery.fn, который является прототипом объекта jQuery. Именно так делаются плагины к нему.

Во многих (плохих) статях ты можешь увидеть плохие примеры кода на jQuery, где копируются куски строк. Не копипасть!

// Плохо: копипаста
$('#some-id .some-class a').hide();
$('#some-id .some-class a').fadeIn();

// Хорошо
$('#some-id .some-class a').hide().fadeIn();

// Хорошо
var link = $('#some-id .some-class a');
link.hide();
link.fadeIn();

1. Разбор страниц

jQuery удобно использовать для сбора информации со страницы сайта. Перейди на сайт http://habr.ru/ и открой консоль отладчика (Ctrl + Shift + J в ФФ/Хроме, F12 в IE, в Сафари надо включить инструменты разработчика сначала). Убедись, что jQuery подключен, введя команду $.fn.jquery — она должна вывести версию библиотеки.

Если ты очень не любишь хабр, можешь использовать любой другой сайт с jQuery.

Используя jQuery, напиши (прямо в консоли) код, выводящий в нее следующую информацию:

  • значение аттрибута content у элемента <meta property="og:image" content="...">
  • список ссылок на все картинки на странице размером больше чем 100×100
  • имена всех элементов форм (textarea, input, button, select) на странице в алфавитном порядке
  • список всех постов в формате "Название поста", число просмотров, число комментариев, ссылка
  • допиши к заголовкам постов в скобках число просмотров

2. Плавная прокрутка

Cделай плавную прокрутку при клике на якорь (якорь — это ссылка внутри страницы вроде <a href="#some-id">...</a>. При клике на нее страница прокручивается к элементу с id="some-id").

Подсказки:

  • плавная прокрутка делается анимацией свойства scrollTop на элементах html и body (так как в Хроме за прокрутку страницы отвечает html, а в других браузерах body)
  • не надо вешать обработчик на все ссылки кодом вроде $('a').click так как это неэффективно и не работает для новых добавляемых ссылок. Используй метод on который ставит 1 обработчик на верх документа
  • не забудь предотвратить обработку события браузером, иначе страница будет прыгать
  • пользователь во время прокрутки может успеть нажать на другой якорь. В такой ситуации надо отменить анимацию и начать новую (jQuery по умолчанию не отменяет анимацию, а доигрывает ее до конца и только потом запускает вторую, что в большинстве случаев неправильно).
  • проверь что вместо клика мышкой по ссылке можно подвести курсор кнопкой Tab и нажать Enter
  • сложнее: проверь, работают ли кнопки «Назад»/«Вперед» корректно, перематывают ли она страницу? Сохраняется ли переход в истории, как это происходит без твоего скрипта?
  • сложнее: проверь, меняется ли URL страницы при клике (добавляется ли якорь после #)? Можно ли переслать ссылку которая откроет нужное место страницы? Сохраняется ли положение прокрутки при обновлении страницы? В общем, проверь что с твоим скриптом история в браузере работает так же как и без него.

3. addClearButton

Сделай плагин к jQuery addClearButton, добавляющий справа в поле ввода крестик, нажатие на которое очищает его. Чтобы содержимое поля ввода не накладывалось на крестик, надо задать ему соответствующий padding. Виджет должен работать с полями ввода любых размеров, цветов (в том числе с белым текстом на черном фоне) и с любыми значениями padding. Крестик удобно нарисовать с помощью какого-нибудь юникодного символа, например http://unicode-table.com/ru/274C/

Используется плагин например так:

$('input.with-clear-box').addClearButton();

(это не применит плагин к динамически добавляемым на страницу инпутам. Их появление эффективно отследить невозможно).

Оформи плагин в соответствии с требованиями jQuery:

При очистке поля должны генерироваться события input и change (по идее оно должно генерироваться само при уходе фокуса с инпута, но фокус может уйти раньше, чем поменялись значения в инпуте) так, что если кто-то подписался на события, то узнает о том, что поле изменило значение.

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

Вот описание разных событий которые есть у элементов форм: http://learn.javascript.ru/events-change

Что мы не учли: на Маке в Хроме в полях c type="search" уже есть крестик, глупо добавлять второй. Если на странице текст выводится справа налево (например, арабский), крестик стоило добавлять слева.

Если ты хочешь сделать это задание без jQuery, я не против, хотя особого смысла в этом нет.

4. Фильтр

Сделай плагин для фильтрации списков и таблиц. Допустим, на странице есть таблица или список (или просто набор div с текстом) и поле ввода. Если список большой, то глазами найти в нем нужную строчку нелегко, верно?

Надо сделать плагин, позволяющий при вводе текста в это поле скрывать строки таблицы/элементы списка/узлы, в которых текста нет. Пример использования:

var input = $('#some-input');
var table = $('#some-table');

table.filterWith('input', input); // привязывает input к таблице
table.filterWith('set', 'text');  // задает текст для фильтра
table.filterWith('clear');        // очищает фильтр

(у меня в мыслях был альтернативный вариант: добавить к таблице атрибут data-filter-with="some-input" указывающий на инпут, но по моему это менее гибко).

Фильтр должен быть универсальным, то есть не требовать какой-то особенной структуры HTML-кода, чтобы его можно было подключить на любую страницу. Фильтр должен корректно работать в сочетании с addClearButton из предыдущей задачи.

Для фильтрации таблиц надо как-то предусмотреть возможность запретить скрывать самую первую строчку с заголовками (лучше N первых строчек). Например, можно договориться что содержимое HTML-элементов thead и tfoot не фильтруется (в этом есть логика: зачем скрывать шапку или подвал таблицы?).

Наш фильтр научился скрывать строки в таблице, но им нельзя например, отфильтровать список картинок по тегам (которые указаны например в аттрибуте data-tags на каком-нибудь div). Давай это исправим! Сделай возможность указать функцию getFilterText(node) которая получает по очереди каждый элемент списка и должна вернуть текст, который ему соответствует (по этому тексту проводится фильтрация).

Фильтр должен игнорировать регистр букв, знаки препинания, различия в числе пробелов (то есть фильтру hello world соответствует строка Hello, !!! world).

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

Усложненная версия: сделай возможность подсвечивать найденный текст в списке.

5. Ексель (задание посложнее)

Сделай плагин editableTable который позволяет сделать любую таблицу редактируемой наподобие Excel. После запуска этого виджета на таблице появляется рамка, которую можно перемещать стрелками, клавишами PgUp, PgDn, Home, End и одиночным кликом мыши. При двойном клике по ячейке или нажатии F2 она переходит в режим редактирования и рамка превращается в поле ввода. Также, в режим редактирования можно перейти, нажав букву, символ или цифру, при этом содержимое ячейки очищается. Отменить редактирование можно нажатием Esc, подтвердить перемещением рамки или Enter. Ну в общем, если ты не видел электронных таблиц, то открой Excel или OpenOffice или Google Drive и посмотри сам.

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

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

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

Усложненная версия: при двойном клике курсор в поле ввода должен ставиться в место клика.

6. Галерея

(возможно, эта задача будет в будущем удалена).

Сделай просмотрщик картинок. На странице есть список из нескольких элементов <img>, содержащих превьюшки и ссылку на большую картинку в аттрибуте data-image-src, а над ним — область просмотра картинок. При клике по превьюшке картинка в области просмотра должна плавно заменяться на выбранную.

При отключенном (или не загрузившемся) яваскрипте картинки должны быть доступны для просмотра. Можно для этого, например обернуть превьюшки тегом <a target="_blank">, ссылающимся на полную версию.

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

Большие кнопки «вправо»/«влево» появляются при наведении мыши на правую/левую часть картинки, если она не последняя/первая.

jQuery UI

jQuery UI — популярная библиотека, содержащая нестандартные виджеты, которых нет в HTML. Например, автокомплит для полей ввода, всплывающие окна-попапы, календарик выбора даты, «аккордеон» (который я ненавижу и считаю неудобным с точки зрения юзабилити), меню, слайдер, вкладки, всплывающие тултипы.

С помощью jQuery UI можно делать элементы на странице перетаскиваемыми, например элементы в списке.

Некоторые возможности jQuery UI теперь реализуются на HTML5/CSS3 (календарик, прогрессбар, spinner, slider, частично selectable). Для создания автокомплитов есть аттрибут datalist.

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

jQuery UI довольно большая в полной версии, но ты можешь при скачивании выбрать, какие компоненты тебе необходимы, и собрать уменьшенную версию.

1. Города

Сделай страницу со списком российских (или из любой другой страны) городов, которые можно редактировать, переставлять местами и добавлять новые. При ввода адреса должно работать автодополнение, то есть если набрать например «Сан», появляется «Санкт-Петербург». Вот примерный вид страницы:

Санкт-Петебург                        [x]
Калининград                           [x]

[__________________] [ Добавить (Enter) ]

Двойной клик по городу позволяет его редактировать, нажатие Esc отменяет редактирование, Enter подтверждает. Внутри список должен быть реализован как форма так, что при ее отправке на сервер отправляются id и имена городов.

Для реализации автокомплита используй jQuery UI Autocomplete + АПИ вконтакте (регистрация или создание приложения для этого не требуются): http://vk.com/dev/database.getCities (увы, вконтакте не дает CORS запросы, а только печальный JSONP).

Для перетаскивания стоит использовать jQuery UI Sortable.

Информация о VK API: http://vk.com/dev/apiusage

Бонусное задание: добавь под формой карту страны, на которой появляются отметки выбранных городов (можно использовать АПИ Яндекс карт).

Бонусное задание: реализуй то же самое (выбор нескольких городов, но без перетаскивания) на основе библиотеки Chosen: http://harvesthq.github.io/chosen/

lodash

Lodash — библиотека для удобной работы с коллекциями (массивами и словарями). Это более новая и улучшенная версия известной библиотеки underscore. Вот пример использования lodash:

var items = ['a', 'b', 'c', 'a', 'a'];
console.log(_.uniq(items)); // a, b, c

(задачи может быть будут позже)

Knockout

Knockout — это библиотека-шаблонизатор с поддержкой data-binding. Шаблонизатор — это значит, что ты можешь взять шаблон вида:

Число очков: <span class="score" data-bind="text: score"></span>

и подставить в него конкретное значение переменной score. Байндинг — значит, что при изменении переменной число очков само будет изменяться на странице. Более того, байндинг может быть и двухсторонним — при вводе текста в поле будет изменяться текст в переменной. Также, байндинг поддерживает массивы — при добавлении элемента в массив будет добавляться строчка в списке или таблице.

Knockout подходит для интерактивных страниц, где использование data-binding позволяет сделать код проще и избавиться от большого числа рутинных функций вида:

var score = $('.score');
function updateScore(value) {
    score.text(value);
}

var input = $('#some-input');
var text = input.val();
input.change(function () {
    text = input.val(); 
});

У нас нет задачи на knockout, но его можно использовать в нашей задаче на SPA.

(здесь бы хорошо добавить задачи на работу с формами, валидацию, синхронизацию с сервером и offline mode)

Backbone

Backbone — фреймворк, предоставляющий компоненты для создания SPA (Single Page Applications). Он предоставляет роутинг, модели с событиями, поддержку history API. Реализация view и синхронизации данных с сервером не предоставляется. В качестве view удобно, например, использовать описанный выше Knockout.

У нас нет задачи на backbone, но его можно использовать в нашей задаче на SPA.

Angular

Angular — фреймворк для создания одностраничных приложений (SPA), предоставляющий мощный шаблонизатор c data-binding, который позволяет расширять HTML новыми элементами, роутинг, dependency injection, и другие компоненты.

Мне, кстати, он не нравится, но он очень известен и популярен.

У нас нет задачи на angular, но его можно использовать в нашей задаче на SPA.

Node.JS

Node.JS — платформа для написания серверных приложений на яваскрипте. Он хорошо справляется с поддержанием большого числа одновременных соединений за счет асинхронной их обработки. На нем например, удобно написать систему чатов, в которых может общаться большое число пользователей.

Еще больше

С развитием HTML5 в браузерах появляется все больше возможностей, которые позволяют создавать сложные приложения, сопоставимые по функционалу с обычными. Вот что еще можно посмотреть:

  • HTML5 canvas — это элемент, на котором можно рисовать яваскриптом, например графики. Канвас при грамотном использовании работает быстро, и на нем можно даже сделать какую-то игру. Вот например задачка написать арканоид на канвасе: https://gist.github.com/codedokode/9933897

  • Meteor.JS — фреймворк для разработки интерактивных приложений, использующих активный обмен данными с сервером. Например, приложение, где несколько пользователей могут одновременно редактировать документ (или писать музыку, или рисовать). Meteor берет на себя сохранение изменений на сервер и обмен ими в реальном времени. На сервере поддерживается пока только бекенд на основе Node.JS и MongoDB

  • HTML5 Audio API — стандарт, который позволяет обрабатывать и генерировать звук яваскриптом и строить цепочки эффектов, обрабатывающих звук в реальном времени. Хочешь промодулировать пилу треугольником или сделать драм-машину? Легко. Примеры:

  • WebRTC — стандарт, который позволяет сайтам и рекламным сетям определить твой IP адрес в сети провайдера, даже если ты используешь прокси или VPN (проверить свой браузер: https://ipleak.net/). А вообще, по задумке он должен позволять браузерам соединяться друг с другом напрямую (peer-to-peer) и обмениваться данными, файлами, звуком и видео, в том числе, захваченным с микрофона и веб-камеры. Да, вполне можно сделать свой аналог скайпа, не требующий установки.

  • WebGL позволяет задействовать для отображения страницы возможности видеокарты (если к ней у тебя новые драйвера и она поддерживается). С его помощью можно строить трехмерные сцены в браузере и делать простые трехмерные игры.

  • Asm.js (Firefox) и NaCL (Chrome) позволяют компилировать существующий и новый код на Си так, что он сможет выполняться в браузере с высокой скоростью, при этом не имея доступа к данным пользователя (в то время как обычные приложения могут делать с твоим компьютером все. что захотят)

Связаться с автором

codedokode@gmail.com