- •Введение
- •Введение в JavaScript
- •Справочники и спецификации
- •Редакторы для кода
- •Консоль разработчика
- •Основы JavaScript
- •Привет, мир!
- •Внешние скрипты, порядок исполнения
- •Структура кода
- •Современный стандарт, «use strict»
- •Переменные
- •Правильный выбор имени переменной
- •Шесть типов данных, typeof
- •Основные операторы
- •Операторы сравнения и логические значения
- •Побитовые операторы
- •Взаимодействие с пользователем: alert, prompt, confirm
- •Условные операторы: if, '?'
- •Логические операторы
- •Преобразование типов для примитивов
- •Циклы while, for
- •Конструкция switch
- •Функции
- •Функциональные выражения
- •Именованные функциональные выражения
- •Всё вместе: особенности JavaScript
- •Качество кода
- •Отладка в браузере Chrome
- •Советы по стилю кода
- •Как писать неподдерживаемый код?
- •Автоматические тесты при помощи chai и mocha
- •Структуры данных
- •Введение в методы и свойства
- •Числа
- •Строки
- •Объекты как ассоциативные массивы
- •Объекты: перебор свойств
- •Объекты: передача по ссылке
- •Массивы c числовыми индексами
- •Массивы: методы
- •Массив: перебирающие методы
- •Псевдомассив аргументов «arguments»
- •Дата и Время
- •Замыкания, область видимости
- •Глобальный объект
- •Замыкания, функции изнутри
- •[[Scope]] для new Function
- •Локальные переменные для объекта
- •Модули через замыкания
- •Управление памятью в JavaScript
- •Устаревшая конструкция «with»
- •Методы объектов и контекст вызова
- •Методы объектов, this
- •Преобразование объектов: toString и valueOf
- •Создание объектов через «new»
- •Дескрипторы, геттеры и сеттеры свойств
- •Статические и фабричные методы
- •Явное указание this: «call», «apply»
- •Привязка контекста и карринг: «bind»
- •Функции-обёртки, декораторы
- •Некоторые другие возможности
- •Типы данных: [[Class]], instanceof и утки
- •Формат JSON, метод toJSON
- •setTimeout и setInterval
- •Запуск кода из строки: eval
- •Перехват ошибок, «try..catch»
- •ООП в функциональном стиле
- •Введение
- •Внутренний и внешний интерфейс
- •Геттеры и сеттеры
- •Функциональное наследование
- •ООП в прототипном стиле
- •Прототип объекта
- •Свойство F.prototype и создание объектов через new
- •Встроенные «классы» в JavaScript
- •Свои классы на прототипах
- •Наследование классов в JavaScript
- •Проверка класса: «instanceof»
- •Свои ошибки, наследование от Error
"use strict";
function ask(question, answer, ok, fail) { var result = prompt(question, '');
if (result.toLowerCase() == answer.toLowerCase()) ok(); else fail();
}
var user = { login: 'Василий',
password: '12345',
// метод для вызова из ask loginDone: function(result) {
alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') ); },
checkPassword: function() { ask("Ваш пароль?", this.password,
function() { user.loginDone(true);
}, function() {
user.loginDone(false);
}
);
}
};
var vasya = user; user = null; vasya.checkPassword();
Изменения должны касаться только выделенного фрагмента.
Если возможно, предложите два решения, одно — с использованием bind, другое — без него. Какое решение лучше?
К решению
Функции-обёртки, декораторы
JavaScript предоставляет удивительно гибкие возможности по работе с функциями: их можно передавать, в них можно записывать данные как в объекты, у них есть свои встроенные методы…
Конечно, этим нужно уметь пользоваться. В этой главе, чтобы более глубоко понимать работу с функциями, мы рассмотрим создание функций-обёрток или, иначе говоря, «декораторов».
Декоратор — приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
Декоратор получает функцию и возвращает обертку, которая делает что-то своё «вокруг» вызова основной функции.
bind — привязка контекста
Один простой декоратор вы уже видели ранее — это функция bind:
function bind(func, context) { return function() {
return func.apply(context, arguments); };
}
Вызов bind(func, context)возвращает обёртку, которая ставит thisи передаёт основную работу функции func.
Декоратор-таймер
Создадим более сложный декоратор, замеряющий время выполнения функции.
Он будет называться timingDecoratorи получать функцию вместе с «названием таймера», а
возвращать — функцию-обёртку, которая измеряет время и прибавляет его в специальный объект timerпо свойству-названию.
Использование:
function f(x) {} // любая функция
var timers = {}; // объект для таймеров
// отдекорировали
f = timingDecorator(f, "myFunc");
// запускаем f(1);
f(2);
f(3); // функция работает как раньше, но время подсчитывается
alert( timers.myFunc ); // общее время выполнения всех вызовов f
При помощи декоратора timingDecoratorмы сможем взять произвольную функцию и одним движением руки прикрутить к ней измеритель времени.
Его реализация:
var timers = {};
// прибавит время выполнения f к таймеру timers[timer] function timingDecorator(f, timer) {
return function() {
var start = performance.now();
var result = f.apply(this, arguments); // (*)
if (!timers[timer]) timers[timer] = 0; timers[timer] += performance.now() start;
return result;
}
}
//функция может быть произвольной, например такой: function fibonacci(n) {
return (n > 2) ? fibonacci(n 1) + fibonacci(n 2) : 1;
}
//использование: завернём fibonacci в декоратор
fibonacci = timingDecorator(fibonacci, "fibo");
//неоднократные вызовы...
alert( fibonacci(10) ); // 55 alert( fibonacci(20) ); // 6765
//...
// в любой момент можно получить общее количество времени на вызовы alert( timers.fibo + 'мс' );
Обратим внимание на строку (*)внутри декоратора, которая и осуществляет передачу вызова:
var result = f.apply(this, arguments); // (*)
Этот приём называется «форвардинг вызова» (от англ. forwarding): текущий контекст и аргументы через applyпередаются в функцию f, так что изнутри fвсё выглядит так, как была вызвана она
напрямую, а не декоратор.
Декоратор для проверки типа
В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект.
Например:
function sum(a, b) { return a + b;
}
// передадим в функцию для сложения чисел нечисловые значения
alert( sum(true, { name: "Вася", age: 35 }) ); // true[Object object]
Функция «как-то» отработала, но в реальной жизни передача в sumподобных значений, скорее всего, будет следствием программной ошибки. Всё-таки sumпредназначена для суммирования чисел, а не объектов.
Многие языки программирования позволяют прямо в объявлении функции указать, какие типы
данных имеют параметры. И это удобно, поскольку повышает надёжность кода.
В JavaScript же проверку типов приходится делать дополнительным кодом в начале функции, который во-первых обычно лень писать, а во-вторых он увеличивает общий объем текста, тем самым ухудшая читаемость.
Декораторы способны упростить рутинные, повторяющиеся задачи, вынести их из кода функции.
Например, создадим декоратор, который принимает функцию и массив, который описывает для какого аргумента какую проверку типа применять:
//вспомогательная функция для проверки на число function checkNumber(value) {
return typeof value == 'number';
}
//декоратор, проверяющий типы для f
//второй аргумент checks массив с функциями для проверки function typeCheck(f, checks) {
return function() {
for (var i = 0; i < arguments.length; i++) { if (!checks[i](arguments[i])) {
alert( "Некорректный тип аргумента номер " + i ); return;
}
}
return f.apply(this, arguments);
}
}
function sum(a, b) { return a + b;
}
// обернём декоратор для проверки
sum = typeCheck(sum, [checkNumber, checkNumber]); // оба аргумента числа
// пользуемся функцией как обычно alert( sum(1, 2) ); // 3, все хорошо
// а вот так будет ошибка
sum(true, null); // некорректный аргумент номер 0
sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент номер 1
Конечно, этот декоратор можно ещё расширять, улучшать, дописывать проверки, но… Вы уже поняли принцип, не правда ли?
Один раз пишем декоратор и дальше просто применяем этот функционал везде, где нужно.
Декоратор проверки доступа
И наконец посмотрим ещё один, последний пример.
Предположим, у нас есть функция isAdmin(), которая возвращает true, если у посетителя есть права администратора.
Можно создать декоратор checkPermissionDecorator, который добавляет в любую функцию
проверку прав:
Например, создадим декоратор checkPermissionDecorator(f). Он будет возвращать обертку, которая передает вызов fв том случае, если у посетителя достаточно прав:
function checkPermissionDecorator(f) { return function() {
if (isAdmin()) {
return f.apply(this, arguments);
}
alert( 'Недостаточно прав' );
}
}
Использование декоратора:
function save() { ... }
save = checkPermissionDecorator(save);
// Теперь вызов функции save() проверяет права
Итого
Декоратор — это обёртка над функцией, которая модифицирует её поведение. При этом основную работу по-прежнему выполняет функция.
Декораторы можно не только повторно использовать, но и комбинировать!
Это кардинально повышает их выразительную силу. Декораторы можно рассматривать как своего рода «фичи» или возможности, которые можно «нацепить» на любую функцию. Можно один, а можно несколько.
Скажем, используя декораторы, описанные выше, можно добавить к функции возможности по проверке типов данных, замеру времени и проверке доступа буквально одной строкой, не залезая при этом в её код, то есть (!) не увеличивая его сложность.
Предлагаю вашему вниманию задачи, которые помогут выяснить, насколько вы разобрались в декораторах. Далее в учебнике мы ещё встретимся с ними.
Задачи
Логирующий декоратор (1 аргумент)
важность: 5
Создайте декоратор makeLogging(f, log), который берет функцию fи массив log.
Он должен возвращать обёртку вокруг f, которая при каждом вызове записывает («логирует»)
аргументы в log, а затем передает вызов в f.
В этой задаче можно считать, что у функции fровно один аргумент.
Работать должно так:
function work(a) {
/* ... */ // work произвольная функция, один аргумент
}
function makeLogging(f, log) { /* ваш код */ }
var log = [];
work = makeLogging(work, log);
work(1); // 1, добавлено в log work(5); // 5, добавлено в log
for (var i = 0; i < log.length; i++) {
alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5"
}
Открыть песочницу с тестами для задачи.
К решению
Логирующий декоратор (много аргументов)
важность: 3
Создайте декоратор makeLogging(func, log), для функции funcвозвращающий обёртку, которая при каждом вызове добавляет её аргументы в массив log.
Условие аналогично задаче Логирующий декоратор (1 аргумент), но допускается funcс любым набором аргументов.
Работать должно так:
function work(a, b) {
alert( a + b ); // work произвольная функция
}
function makeLogging(f, log) { /* ваш код */ }
var log = [];
work = makeLogging(work, log);
work(1, 2); // 3 work(4, 5); // 9
for (var i = 0; i < log.length; i++) {
var args = log[i]; // массив из аргументов i го вызова alert( 'Лог:' + args.join() ); // "Лог:1,2", "Лог:4,5"
}
Открыть песочницу с тестами для задачи.