# Простые задачи на яваскрипт [Пропустить теорию и перейти прямо к задачам](#Задачки-на-js) Ссылка на учебник: 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](https://learn.javascript.ru/es-modern)) - новые, их уровень поддержки браузерами описан тут: - http://kangax.github.io/compat-table/es5/ (по отдельным пунктам) - http://caniuse.com/#search=es5 (в целом) - https://kangax.github.io/compat-table/es6/ - http://caniuse.com/#search=es6 На 2016 год писать на ES5, а тем более на ES6 рановато, делать это можно, только если ты пишешь какой-то внутренний проект, например, который будет использоваться только внутри компании и у всех пользователей есть современная версия браузера. Некоторые решают эту проблему *транспайлером* - компилятором, преобразующим ES5/6 код в ES3. И еще: язык Ява (Java) не имеет никакого отношения к яваскрипту, похожи в них только название и часть синтаксиса. ### Строгий режим В новом Javascript есть строгий режим: - http://learn.javascript.ru/strict-mode - http://habrahabr.ru/post/118666/ В нем некоторые ошибки, которые ранее прощались, становятся фатальными. Обязательно используй этот режим при решении задач. ### Функции и замыкания Функции — это объекты. У них есть свойства (например `length`) и методы (например `toSource`, `apply` и `call`). Функции можно хранить в переменных, передавать и возвращать из других функций: ```javascript var a = function (..) { ... }; // создаем новую функцию и поменщаем ссылку в a a(); // вызываем console.log(a.toString()); // вызваем метод у функции (он вернет ее текст) ``` Функция **при создании** привязывается к набору переменных родительской функции и потому видит ее переменные: ```javascript 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: ```javascript 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`, строки. Если ты их присваиваешь переменной, передаешь или возвращаешь из функции, создается новая независимая копия значения: ```javascript var a = "Hello"; var b = a; // В b независимая копия строки. Меняя ее, мы не изменяем то, что в a ``` Объекты (а это в том числе массивы (Array), функции (Function), регулярки (RegExp), даты (Date)) копируются и передаются в/из функции по ссылке: ```javascript 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] ``` ### Сравнение объектов Объекты (а значит и массивы, и функции, так как они тоже ими являются) сравниваются по идентичности, то есть тому, что это ссылки на один и тот же объект: ```javascript 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`: ```javascript 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` выдается ошибка. То есть код ```javascript var a = "abc"; var b = a.length; ``` Превращается внутри в: ```javascript var a = "abc"; var tmp = new String(a); // происходит боксинг, создается временный объект var b = tmp.length; // и идет обращение к свойству этого объекта // после чего объект выкидывается ``` Потому присвоить свойство примитиву можно, но оно не сохранится — ведь оно создалось на временном объекте. ```javascript var x = 1; x.test = 2; console.log('test' in x); // false — такого свойства у x нету ``` Это легко объяснить, если записать что происходит с учетом боксинга: ```javascript var x = 1; var tmp1 = new Number(x); tmp1.test = 2; // свойство присвоилось временному объекту var tmp2 = new Number(x); console.log('test' in tmp2); // а ищем мы его уже в другом объекте, естественно его там нет ``` Боксинг сделан для того, чтобы с примитивами можно было работать как с объектами, например, вызывая у них методы. Само разделение на примитивы и объекты сделано из-за соображений производительности: если бы числа и строки были настоящими объектами, все работало бы медленнее. Если тебе понадобится (100% что не понадобится), ты можешь сделать боксинг и анбоксинг вручную: ```js 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. Генераторов можно создать сколько угодно. ```javascript 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`) раз и возвращает массив с результатами вызовов. Она нам пригодится для отладки: ```javascript var gen2 = sequence(0, 2); console.log(take(gen2, 5)); // [0, 2, 4, 6, 8 ] ``` 3. Напиши функцию `map(fn, array)`, которая принимает на вход функцию и массив, и обрабатывает каждый элемент массива этой функцией, возвращая новый массив. Пример: ```javascript function square(x) { return x * x; } // возведение в квадрат console.log(map(square, [1, 2, 3, 4])); // [1, 4, 9, 16] console.log(map(square, [])); // [] ``` Обрати внимание: функция не должна изменять переданный ей массив: ```javascript 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`. Пример: ```javascript 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` можно было указать функцию с аргументами, и при вызове ```javascript 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`. Аргументов может быть любое количество. 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, ....)`, которая позволяет зафиксировать один или несколько аргументов функции. Пример: ```javascript 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 ``` Есть функция с аргументами: ```javascript f1(a, d, c, d) ``` Мы можем с помощью `partial` сделать из нее функцию с меньшим числом аргументов, заранее задав значения для нескольких из них, например: ```javascript var f2 = partial(f1, 1, 2); // фиксируем a = 1, b = 2 ``` И вызов: ```javascript f2(x, y) ``` будет равносилен вызову: ```javascript f1(1, 2, x, y) ``` Кстати, имеющийся в новых версиях JS метод `bind()` тоже может делать частичное применение: http://frontender.info/partial-application-in-javascript-using-bind/ Но ты должен обойтись без его использования, и написать свой велосипед. 6. Наша функция `partial` позволяет фиксировать только первые аргументы. Усовершенствуй ее, чтобы зафиксировать можно было любые аргументы, пропущенные аргументы обозначаются с помощью undefined. Обрати внимание, что теперь мы переименовали ее в `partialAny`, чтобы не путать с предыдущей: ```javascript 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`) к функции: ```javascript 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`, которая берет массив объектов и возвращает массив значений определенного поля: ```javascript 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. ```javascript var input = [1, 2, 3, 4, 5, 6]; function isEven(x) { return x % 2 == 0; } // проверяет на четность console.log(filter(input, isEven)); // [2, 4, 6] ``` Функция не должна изменять исходный массив: ```javascript console.log(input); // [1, 2, 3, 4, 5, 6] ``` Аналог из lodash: http://lodash.com/docs#filter В новых браузерах у массивов есть метод `filter`. 10. Напиши функцию, считающую число свойств в объекте: ```javascript 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](http://usejsdoc.org/) описанию (то есть содержать указанные методы, которые принимают и возвращают данные указанного типа и выбрасыают исключения указанного типа. Комментарии ниже можно скопировать в свой код): ```js /** * Класс, объекты которого описывают параметры гамбургера. * * @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.getPrice = function () ... /** * Узнать калорийность * @return {Number} Калорийность в калориях */ Hamburger.prototype.getCalories = function () ... /** * Представляет информацию об ошибке в ходе работы с гамбургером. * Подробности хранятся в свойстве message. * @constructor */ function HamburgerException (...) { ... } ``` **Комментарии**. Эта задача вызывает много непонимания, потому внимательно прочти эти комментарии перед решением. Это задача на ООП. Тебе надо сделать класс, который получает на вход информацию о гамбургере, и на выходе дает информацию о весе и цене. Никакого взаимодействия с пользователем и внешним миром класс делать не должен - все нужные данные ты передаешь ему явно. Ни спрашивать ничего, ни выводить. Почему? Потому что каждый должен заниматься своим делом, класс должен только обсчитывать гамбургер, а вводом-выводом пусть занимаются другие. Иначе мы получим кашу, где разные функции смешаны вместе. Типы начинок, размеры надо сделать константами. Никаких [магических строк](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%BE_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)#.D0.9F.D0.BB.D0.BE.D1.85.D0.B0.D1.8F_.D0.BF.D1.80.D0.B0.D0.BA.D1.82.D0.B8.D0.BA.D0.B0_.D0.BF.D1.80.D0.BE.D0.B3.D1.80.D0.B0.D0.BC.D0.BC.D0.B8.D1.80.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D1.8F) не должно быть. Переданную информацию о параметрах гамбургера класс хранит внутри в своих полях. Вот как может выглядеть использование этого класса: ```javascript // маленький гамбургер с начинкой из сыра 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 ) ```javascript // не передали обязательные параметры var h2 = new Hamburger(); // => HamburgerException: no size given // передаем некорректные значения, добавку вместо размера var h3 = new Hamburger(Hamburger.TOPPING_SAUCE, Hamburger.TOPPING_SAUCE); // => 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' ``` Обрати внимание в коде выше на такие моменты: - класс не взаимодействует с внешним миром. Это не его дело, этим занимается другой код, а класс живет в изоляции от мира - обязательные параметры (размер и начинка) мы передаем через конструктор, чтобы нельзя было создать объект, не указав их - необязательные (добавка) добавляем через методы - имена методов начинаются с глагола и имеют вид «сделайЧтоТо»: `countCalories()`, `addTopping()` - типы начинок обозначены "константами" с понятными именами (на самом деле просто свойствами, написанным заглавными буквами, которые мы договорились считать "константами") - об исключительных ситуациях сообщаем с помощью исключений - объект создается через конструктор - функцию, которая задает начальные значения полей. Имя конструктора пишется с большой буквы и обычно является существительным: `new Hamburger(...)` - "константы" вроде могут иметь значение, являющееся строкой или числом. От смены значения константы ничего не должно меняться (то есть эти значения не должны где-то еще быть записаны). - в свойствах объекта гамбургера логично хранить исходные данные (размер, тип начинки), а не вычисленные из них (цена, число калорий и т.д.). Рассчитывать цену и калории логично в тот момент, когда это потребуется, а не заранее. В дополнение, вот еще инструкция, как решать задачи на ООП. Когда ты решаешь задачу на ООП, ты должен ответить на вопросы: - какие есть сущности, для которых мы сделаем классы? (*Гамбургер*). - какие у них есть свойства (*размер, начинка, добавки*). Цена или калории не являются свойствами так как они вычисляются из других свойств и хранить их не надо. - что мы хотим от них получить (какие у них должны быть методы). *Например, сколько стоит гамбургер?* - как сущности связаны? *У нас одна сущность «Гамбургер» и она ни с чем не связана*. Заметь также, что в моем примере класс не взаимодействует с внешним миром. За это отвечает внешний относительно него код. Потому наш класс унивесален: ты можешь использовать его в консоли, выводя данные через `console.log`, а можешь приделать навороченный HTML-интерфейс с кнопками для запуска на планшете с тачскрином. Именно в таком стиле ты должен писать ООП код. **Послесловие**. Если ты внимательно читал учебник по JS, то наверно знаешь, что в JS нет классов, а до версии ES5 нет и констант (~~а как же решать задачу?~~). Классы в JS имитируются разными споcобами, самый общеупотребимый - через добавление методов в прототипы объекта: - https://learn.javascript.ru/prototype - https://learn.javascript.ru/classes Если ты знаешь ООП в каком-то другом языке (например 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 - новые, их уровень поддержки описан тут: - http://kangax.github.io/compat-table/es5/ - https://kangax.github.io/compat-table/es6/ - http://caniuse.com/#search=es5 - http://caniuse.com/#search=es6 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`. Псевдомассивом например является `arguments`, а также DOM-коллекции (с которыми ты столкнешься позже). В 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(...)` 15\. Напиши функцию неглубокого копирования объектов и массивов. По умолчанию, как ты наверно, знаешь, при копировании объектов или массивов (которые являются объекты) мы копируем лишь ссылку на тот же самый объект: ```javascript var a = [1, 2, 3]; var b = a; // b указывает на тот же массив b.push(4); console.log(a); // [1, 2, 3, 4] ``` Функция неглубокого копирования должна создавать новый массив/объект, и копировать в него элементы из старого. При этом сами элементы копируются по ссылке: ```javascript 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`, надо создавать копию того же типа. ```javascript var с = 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\. Напиши функцию глубокого копирования объектов и массивов. Она должна делать не только копию переданного объекта/массива, но и копии вложенных них объектов/массивов. Также, копироваться должны объекты `Date` ```javascript var a = { x: 1, y: 2, z: [1, 2, 3], 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: - http://lodash.com/docs#isArray - http://lodash.com/docs#cloneDeep ## DOM, который построил Джек Дальше идут задания на работу с DOM и событиями. DOM = Document Object Model — это набор объектов, которые соответствуют содержимому HTML-страницы, и позволяют взаимодействовать с и изменять содержимое страницы в браузере. Обычно каждому тегу на странице соответствует отдельный узел дерева DOM. Узлы образуют дерево, и для каждого узла можно получить родительский узел, список узлов-детей, соседние узлы. CSSOM = CSS Object Model — это свойства и методы этих объектов, которые позволяют изменять CSS-стили элементов, а также получать информацию об их размерах и положении. События в браузере — это события движения мыши, нажатия клавиш, прокрутки страницы. Ты можешь подписываться на эти события, и указанная тобой функция будет вызваться в случае их возникновения. Теория для изучения (читать можно параллельно с решением задач): - http://javascript.ru/tutorial/dom - http://javascript.ru/unsorted/w3c - http://learn.javascript.ru/document - http://learn.javascript.ru/events-and-interfaces - http://learn.javascript.ru/css-for-js - Информация о поддержке в разных браузерах тех или иных фич: http://caniuse.com/ (англ.) - Таблица свойств CSSOM: http://www.quirksmode.org/dom/w3c_cssom.html (англ.) - Таблица видов событий: http://www.quirksmode.org/dom/events/ (англ.) Задачки надо решать без использования сторонних библиотек (вроде JQuery). Задачки: ### 1.Работа с классами Дан узел DOM. Сделай функции `dom.hasClass(node, klass)`, a`dom.addClass(node, klass)`, `dom.removeClass(node, klass)` (`dom` — это обычный объект, созданный командой `var dom = {};`), которые позволяют проверить, есть ли у элемента заданный CSS-класс, добавить к нему класс (если его еще нет) и удалить класс. Учти, что у элемента может быть несколько классов, которые могут быть разделены одним или нескольким пробельными символами (пробел, `\t`, `\f`, `\r`, перевод строки `\n` — все эти символы ищутся с помощью `\s` в регулярке). Ты можешь спросить, что за идиот придумал разделять классы с помощью непонятных спецсимволов типа `\f`? Не знаю, но так написано в стандарте. Если удалены все классы, то удалять аттрибут `class=""` не надо, пусть остается. Примеры: ```javascript // вспомогательная функция для создания ноды 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`: - https://developer.mozilla.org/en-US/docs/Web/API/Element.classList (англ.) - http://html5.by/blog/javascript-classlist-api/ Но решение должно работать и в браузерах без classList. Вот тебе в помощь код таких функций: - из jQuery: https://github.com/jquery/jquery/blob/master/src/attributes/classes.js - из блога: http://www.avoid.org/javascript-addclassremoveclass-functions/ (англ.) ### 2. Поле Сделай поле из белых клеточек (клеточка может иметь размер около 28×28 пикселей). При клике на клеточку она должна менять цвет на черный. Под таблицей должна быть кнопка «поменять цвета». При ее нажатии все цвета клеточек меняются на противоположные. Делать поле удобно с помощью элемента ``. Саму таблицу надо не вставить в исходный код, а сгенерировать и добавить в 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 Как создать всплывающее окошко? Идея такая: делаем шаблон окошка и встраиваем его в страницу, примерно так: ```html ``` Заметь, я использовал тег `script` чтобы шаблон воспринимался браузером не как часть HTML-кода страницы, а как текст который не надо никак интерпретировать (браузер не будет пытаться выполнить код как яваскрипт, так как в аттрибуте `type` стоит не `text/javascript`. Буква `x` нужна так как мы придумали свое, нестандартное значение). Затем, когда требуется вывести окошко, берем текст этого шаблона, заменяем в нем конструкции вроде `{{text}}` на нужное нам значение, создаем элемент `div`, вставляем в него получившийся HTML и добавляем `div` в DOM. Для закрытия окошка — удаляем этот `div`. Ну и разумеется требуется написать CSS-код чтобы окошко например выводилось по центру экрана. Почему я предлагаю встроить шаблон в страницу, а не поместить его в переменную в JS коде, как здесь? ```javascript var template = '