
- •Введение
- •Введение в 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

К решению
Кеширующий декоратор
важность: 5
Создайте декоратор makeCaching(f), который берет функцию fи возвращает обертку, которая кеширует её результаты.
Вэтой задаче функция fимеет только один аргумент, и он является числом.
1.При первом вызове обертки с определенным аргументом — она вызывает fи запоминает значение.
2.При втором и последующих вызовах с тем же аргументом возвращается запомненное значение.
Должно работать так:
function f(x) {
return Math.random() * x; // random для удобства тестирования
}
function makeCaching(f) { /* ваш код */ }
f = makeCaching(f);
var a, b;
a = f(1); b = f(1);
alert( a == b ); // true (значение закешировано)
b = f(2);
alert( a == b ); // false, другой аргумент => другое значение
Открыть песочницу с тестами для задачи.
К решению
Некоторые другие возможности
Различные возможности JavaScript, которые достаточно важны, но не заслужили отдельного раздела.
Типы данных: [[Class]], instanceof и утки
Время от времени бывает удобно создавать так называемые «полиморфные» функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
Для реализации такой возможности нужен способ определить тип переменной.
Оператор typeof
Мы уже знакомы с простейшим способом — оператором typeof.
Оператор typeofнадежно работает с примитивными типами, кроме null, а также с функциями. Он возвращает для них тип в виде строки:
alert( typeof 1 ); |
// 'number' |
alert( typeof true ); |
// 'boolean' |
alert( typeof "Текст" ); |
// 'string' |
alert( typeof undefined ); // 'undefined' |
|
alert( typeof null ); |
// 'object' (ошибка в языке) |
alert( typeof alert ); |
// 'function' |
…Но все объекты, включая массивы и даты для typeof— на одно лицо, они имеют один тип
'object':
alert( typeof {} ); // 'object' alert( typeof [] ); // 'object' alert( typeof new Date ); // 'object'
Поэтому различить их при помощи typeofнельзя, и в этом его основной недостаток.
Секретное свойство [[Class]]
Для встроенных объектов есть одна «секретная» возможность узнать их тип, которая связана с методом toString.
Во всех встроенных объектах есть специальное свойство [[Class]], в котором хранится информация о его типе или конструкторе.
Оно взято в квадратные скобки, так как это свойство — внутреннее. Явно получить его нельзя, но можно прочитать его «в обход», воспользовавшись методом toStringстандартного объекта
Object.
Его внутренняя реализация выводит [[Class]]в небольшом обрамлении, как "[object
значение]".
Например:

var toString = {}.toString;
var arr = [1, 2];
alert( toString.call(arr) ); // [object Array]
var date = new Date;
alert( toString.call(date) ); // [object Date]
var user = { name: "Вася" };
alert( toString.call(user) ); // [object Object]
В первой строке мы взяли метод toString, принадлежащий именно стандартному объекту {}. Нам пришлось это сделать, так как у Dateи Array— свои собственные методы toString, которые работают иначе.
Затем мы вызываем этот toStringв контексте нужного объекта obj, и он возвращает его внутреннее, невидимое другими способами, свойство [[Class]].
Для получения [[Class]]нужна именно внутренняя реализация toStringстандартного объекта Object, другая не подойдёт.
К счастью, методы в JavaScript — это всего лишь функции-свойства объекта, которые можно скопировать в переменную и применить на другом объекте через call/apply. Что мы и делаем
для {}.toString.
Метод также можно использовать с примитивами:
alert( {}.toString.call(123) ); // [object Number] alert( {}.toString.call("строка") ); // [object String]
Вызов {}.toStringв консоли может выдать ошибку
При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку {}.toString.call(...)— будет ошибка. С другой стороны, вызов alert(
{}.toString... )— работает.
Эта ошибка возникает потому, что фигурные скобки { }в основном потоке кода интерпретируются как блок. Интерпретатор читает {}.toString.call(...)так:
{ } // пустой блок кода
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка!
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки ( {}.toString... )тоже сработает нормально.
Для большего удобства можно сделать функцию getClass, которая будет возвращать только сам
[[Class]]:
function getClass(obj) {
return {}.toString.call(obj).slice(8, 1);
}
alert( getClass(new Date) ); // Date alert( getClass([1, 2, 3]) ); // Array
Заметим, что свойство [[Class]]есть и доступно для чтения указанным способом — у всех
встроенных объектов. Но его нет у объектов, которые создают наши функции. Точнее, оно есть, но равно всегда "Object".
Например:
function User() {}
var user = new User();
alert( {}.toString.call(user) ); // [object Object], не [object User]
Поэтому узнать тип таким образом можно только для встроенных объектов.
Метод Array.isArray()
Для проверки на массивов есть специальный метод: Array.isArray(arr). Он возвращает true только если arr— массив:
alert( Array.isArray([1,2,3]) ); // true alert( Array.isArray("not array")); // false
Но этот метод — единственный в своём роде.
Других аналогичных, типа Object.isObject, Date.isDate— нет.
Оператор instanceof
Оператор instanceofпозволяет проверить, создан ли объект данной функцией, причём работает для любых функций — как встроенных, так и наших.
function User() {}
var user = new User();
alert( user instanceof User ); // true
Таким образом, instanceof, в отличие от [[Class]]и typeofможет помочь выяснить тип для новых объектов, созданных нашими конструкторами.
Заметим, что оператор instanceof— сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к instanceofв главе Проверка класса: «instanceof».

Утиная типизация
Альтернативный подход к типу — «утиная типизация», которая основана на одной известной пословице: «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)".
В переводе: «Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)".
Смысл утиной типизации — в проверке необходимых методов и свойств.
Например, мы можем проверить, что объект — массив, не вызывая Array.isArray, а просто уточнив наличие важного для нас метода, например splice:
var something = [1, 2, 3];
if (something.splice) {
alert( 'Это утка! То есть, массив!' );
}
Обратите внимание — в ifмы не вызываем метод something.splice(), а пробуем получить само свойство something.splice. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте true.
Проверить на дату можно, определив наличие метода getTime:
var x = new Date();
if (x.getTime) { alert( 'Дата!' );
alert( x.getTime() ); // работаем с датой
}
С виду такая проверка хрупка, ее можно «сломать», передав похожий объект с тем же методом.
Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле).
То есть, мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным.
Проверка интерфейса
Если говорить словами «классического программирования», то «duck typing» — это проверка реализации объектом требуемого интерфейса. Если реализует — ок, используем его. Если нет
— значит это что-то другое.
Пример полиморфной функции
Пример полиморфной функции — sayHi(who), которая будет говорить «Привет» своему
аргументу, причём если передан массив — то «Привет» каждому:
function sayHi(who) {
if (Array.isArray(who)) { who.forEach(sayHi);
}else {
alert( 'Привет, ' + who );
}
}
//Вызов с примитивным аргументом sayHi("Вася"); // Привет, Вася
//Вызов с массивом
sayHi(["Саша", "Петя"]); // Привет, Саша... Петя
// Вызов с вложенными массивами тоже работает!
sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саша..Петя..Маша..Юля
Проверку на массив в этом примере можно заменить на «утиную» — нам ведь нужен только метод forEach:
function sayHi(who) {
if (who.forEach) { // если есть forEach
who.forEach(sayHi); // предполагаем, что он ведёт себя "как надо" } else {
alert( 'Привет, ' + who );
}
}
Итого
Для написания полиморфных (это удобно!) функций нам нужна проверка типов.
●Для примитивов с ней отлично справляется оператор typeof.
Унего две особенности:
1.Он считает nullобъектом, это внутренняя ошибка в языке.
2.Для функций он возвращает function, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
●Для встроенных объектов мы можем получить тип из скрытого свойства [[Class]], при помощи вызова {}.toString.call(obj).slice(8, 1). Не работает для конструкторов, которые объявлены нами.
●Оператор obj instanceof Funcпроверяет, создан ли объект objфункцией Func, работает для любых конструкторов. Более подробно мы разберём его в главе Проверка класса: «instanceof».
●И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется «утиная типизация».
Задачи