Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык JavaScript часть1.pdf
Скачиваний:
204
Добавлен:
22.03.2016
Размер:
8.92 Mб
Скачать

К решению

Кеширующий декоратор

важность: 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».

И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется «утиная типизация».

Задачи