JavaScript с нуля — страница 14 из 30

В предыдущей главе «Строки» и отчасти в главе «О пицце, типах, примитивах и объектах» мы мельком затронули нечто сбивающее с толку. Я несколько раз отмечал, что примитивы очень просты и понятны. В отличие от объектов, они не содержат свойств, которые позволяют обыгрывать значения интересными (и не очень) способами. Действительно, при наглядном рассмотрении всех возможностей использования строк кажется, что наши примитивы таят в себе некую темную сторону:

let greeting = "Hi, everybody!!!";

let shout = greeting.toUpperCase(); // Откуда появился toUpperCase?

Как видно из приведенного фрагмента, переменная greeting, содержащая примитивное значение в форме текста, судя по всему, имеет доступ к методу toUpperCase. Как такое вообще возможно? Откуда появился этот метод? Почему мы здесь? Ответы на подобные непростые вопросы и составят львиную долю информации этой главы.

Поехали!

Строки — это не единственная проблема

Так как строки весьма интересны и в некотором смысле игривы (прямо как золотистый ретривер), их легко выбрать в качестве главного виновника этой путаницы с примитивами и объектами. Но как в итоге выясняется, в их банду также входят и многие другие примитивные типы. Таблица 15.1 показывает популярные встроенные типы Object, включая большинство виновников (Symbol и BigInt отсиживаются в стороне), которые, помимо прочего, замешаны и в связях с примитивами:

Табл. 15.1. Объектные типы, включая те, что представляют примитивы

Тип

Назначение

Array

Помогает хранить, извлекать и управлять наборами данных

Boolean

Выступает в роли обертки для примитива boolean; а также работает с помощью true и false

Date

Упрощает представление дат и работу с ними

Function

Позволяет вызывать заданный код

Math

Умник среди типов, расширяющий возможности работы с числами

Number

Выступает в качестве обертки для примитива number

RegExp

Предоставляет множество возможностей для сопоставления шаблонов в тексте

String

Выступает в качестве обертки для примитива string

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

Давайте все-таки выберем строки

Как и говорилось в предыдущих главах, обычно мы используем строки в форме литералов:

let primitiveText = "Homer Simpson";

Как видно из таблицы, строки тоже могут быть использованы как объекты. Есть несколько способов создания нового объекта, но в случае создания объекта для типа вроде строки чаще всего используется ключевое слово new, сопровождаемое String:

let name = new String("Batman");

String в данной ситуации не просто обычное слово. Оно представляет собой так называемую функцию-конструктор, которая используется исключительно для создания новых объектов. Аналогично наличию нескольких способов создания объектов есть несколько способов создания объектов String. Я же считаю, что достаточно знать один способ, который не следует использовать для их создания.

Как бы то ни было, главное отличие между примитивной и объектной формами строки — это существенное количество лишнего багажа, присущего объекту. На рис. 15.1 — визуальное представление нашего объекта String с именем name.

Рис. 15.1. Углубленный вид объекта String

Переменная name содержит указатель на текст "Homer Simpson". Нам также доступно все множество свойств и методов, присущих объекту String, включая те, что вы уже использовали ранее (indexOf, toUpperCase и пр.).

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

Почему это важно

Давайте вернемся к сбивающему с толку моменту. Наша строка — это примитив. Как может примитивный тип позволить обращаться к его свойствам? Дело в том, что JavaScript — весьма странный язык. Предположим, у нас есть следующая строка:

let game = "Dragon Age: Origins";

Очевидно, что переменная game — это строковый примитив, присвоенный конкретному тексту. Если мы захотим обратиться к length этого текста, то сделаем следующее:

let game = "Dragon Age: Origins";

console.log(game.length);

Как часть вычисления game.length JavaScript преобразует строковый примитив в объект. На короткое время наш приземленный примитив станет прекрасным объектом, чтобы мы могли выяснить его длину. Здесь следует помнить, что все это временно. Так как этот временный объект не имеет основы и ни к чему не привязан, то после выполнения своей миссии он удаляется и остается лишь результат вычисления length (число), а переменная game по-прежнему является строковым примитивом.

Такая трансформация происходит только с примитивами. Если мы создадим объект String, то он так и останется навсегда объектом. Представим следующий пример:

let gameObject = new String("Dragon Age: Origins");

В данном случае переменная gameObject очень четко указывает на что-то имеющее тип Object. Эта переменная продолжит указывать на тип Object, пока вы не измените строку или сделаете что-нибудь, что приведет к изменению ссылки. Способность примитива трансформироваться в объект, а затем обратно в примитив является уникальной. Объекты в такой глупости не участвуют.

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

let game = "Dragon Age: Origins";

console.log("Length is: " + game.length);


let gameObject = new String("Dragon Age: Origins");


console.log(typeof game); // строка

console.log(typeof game.length); // число

console.log(typeof gameObject); // объект

Думаю, вы по достоинству оцените эти знания.

КОРОТКО О ГЛАВНОМ

Надеюсь, это краткое объяснение поможет вам осознать, почему примитивы при необходимости ведут себя как объекты. Здесь может возникнуть другой вопрос, а именно: «Зачем вообще кто-то мог решить разработать язык с такими странностями?» В конце концов, если примитив превращается в объект при необходимости, то почему бы ему так и не оставаться им навсегда? Ответ на этот вопрос будет связан с потреблением памяти.

Как я уже недавно упоминал, объектная форма объекта примитива несет на себе существенно больше багажа, чем обычный примитив. В итоге это требует дополнительных ресурсов для поддержания функциональности. Решением в этом случае послужил компромисс. Все литеральные значения вроде текста, чисел и логических значений хранятся в виде примитивов, если изначально таковыми создаются и/или используются. Только при необходимости они преобразовываются в соответствующие им формы Object. Чтобы обеспечить минимальное потребление памяти приложением, эти преобразованные объекты быстро удаляются (сборщиком мусора), как только выполнят свою задачу.

Есть вопросы? Задавайте их на форуме https://forum.kirupa.com и получайте развернутые оперативные ответы от единомышленников.

Глава 16. Числа

В JavaScript приходится часто иметь дело с числами. Даже если вы не будете работать непосредственно с числами, то будете косвенно сталкиваться с ними во многих базовых и не только задачах вроде ведения подсчета чего-либо, работы с массивами и т. д.

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

Поехали!

Использование чисел

Используются числа очень просто. Ниже приведен простой пример, в котором я объявляю переменную stooges, инициализированную как число 3:

let stooges = 3;

Вот и все. Ничего сложного. Если вы захотите использовать более сложные числа, то просто используйте их, как обычно:

let pi = 3.14159;

let color = 0xFF;

let massOfEarth = 5.9742e+24;

В этом примере вы видите десятичное, шестнадцатеричное, а также очень большое значение, в котором используется экспонента. В итоге ваш браузер автоматически сделает то, что потребуется. Имейте в виду, что при этом также могут быть использованы и отрицательные значения. Для этого достаточно добавить знак минуса (-) перед числом:

let temperature = -42;

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

Числа в JavaScript

Вам любопытно, почему работать с числами так легко? Отвечаю! JavaScript не богат на численные типы. Вам не придется объявлять числа с типами int, double, byte, float и пр., как это делается в некоторых других языках. Единственное исключение представлено в виде типа BigInt, который вы будете использовать, если вам понадобится действительно огромное или малое число. Об этом типе мы поговорим позже.

Отмечу еще, что в JavaScript все числа конвертируются в 64-битные числа с плавающей точкой.

Операторы

Ни одно введение в тему чисел нельзя считать полноценным, не показав, как используются математические операторы для реализации задач первого класса школьной программы по математике.

В текущем разделе мы рассмотрим распространенные операторы.

Простые математические действия

В JavaScript вы можете создавать простые математические выражения, используя +, -. *, / и % для сложения, вычитания, умножения, деления и нахождения остатка (модуля) чисел соответственно. Если вы умеете пользоваться калькулятором, то сможете производить простые вычисления и в JavaScript.

Вот некоторые примеры с применением перечисленных операторов:

let total = 4 + 26;

let average = total / 2;

let doublePi = 2*3.14159;

let subtractItem = 50–25;

let remainder = total % 7;

let more = (1 + average * 10) / 5;

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

JavaScript производит вычисление выражений в следующем порядке:

1. Скобки.

2. Экспоненты.

3. Умножение.

4. Деление.

5. Сложение.

6. Вычитание.

Для запоминания этого порядка иногда используют соответствующие мнемонические схемы. В начальных классах меня научили вот такому: Please Excuse My Dear Aunt Sally[2].

Увеличение и уменьшение

Нередко в отношении чисел вы будете производить увеличение и уменьшение переменной на определенную величину. Ниже представлен пример увеличения переменной i на 1:

let i = 4;

i = i + 1;

Вам не обязательно увеличивать или уменьшать именно на 1. Вы можете использовать произвольное число:

let i = 100;

i = i — 2;

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

let i = 100;

i = i / 2;

Здесь стоит разглядеть шаблон. Независимо от того, какой оператор используете, вы заметите, что всегда изменяете переменную i. В связи с частым использованием этого шаблона существуют специальные операторы для упрощения процесса (табл. 16.1).

Табл. 16.1. Операторы, упрощающие увеличение и уменьшение

Выражение

Действие

i++

Увеличивает i на 1 (i = i + 1)

i

Уменьшает i на 1 (i = i — 1)

i += n

Увеличивает i на n (i = i + n)

i — = n

Уменьшает i на n (i = i — n)

i *= n

Умножает i на n (i = i * n)

i /= n

Делит i на n (i = i / n)

i %= n

Находит остаток iпри делении на n (i = i % n)

i **= n

Экспоненциальный оператор, где i возводится в степень n

Если я использую эти операторы для трех примеров, приведенных ранее, то код будет выглядеть так:

i++;

i — = 2;

i /= 2;

Прежде чем мы здесь закончим, есть одна хитрость, о которой вам следуем знать. Она касается операторов — и ++ для увеличения и уменьшения значения на 1. Тут важно определить оператор перед переменной или после нее.

Рассмотрим пример:

let i = 4;

let j = i++;

После выполнения этих двух строк значением i будет 5, как вы и могли ожидать. Значением j будет 4. Обратите внимание, что в этом примере оператор используется после переменной.

Если же мы расположим его перед ней, то результат будет несколько иным:

let i = 4;

let j = ++i;

В этом случае значением i по-прежнему будет 5. Но при этом удивительным образом значением j теперь также будет 5.

Эти два примера отличаются лишь расположением оператора, которое определяет, будет ли возвращено увеличенное значение или значение, имевшееся до увеличения.

Шестнадцатеричные и восьмеричные значения

Помимо обычных десятичных значений вы можете использовать шестнадцатеричные (основание 16) и восьмеричные (основание 8). При работе с восьмеричными обязательно начинайте числа с 0:

let leet = 0°2471;

При использовании шестнадцатеричных начинайте с 0х:

let leet = 0x539;

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

Делается это с помощью функции parseInt:

let hexValue = parseInt('FFFFFF', 16);

let octalValue = parseInt('011', 8);

Функция parseInt получает шестнадцатеричное или восьмеричное значение, сопровождаемое основанием, из которого вы производите преобразование.

Особые значения — Infinity и NaN

Последним, что мы рассмотрим, будут два глобальных свойства, с которыми вам предстоит сталкиваться и которые не являются числовыми значениями. Это Infinity (бесконечность) и NaN (не число):

Infinity

Вы можете использовать значения Infinity и — Infinity для определения бесконечно больших и бесконечно малых чисел:

let myLoveForYou = Infinity * 2;

На деле вы вряд ли будете часто использовать Infinity. Чаще такие значения могут быть возвращены в результате выполнения кодом каких-то задач. Например, если вы разделите на 0, то в качестве результата будет возвращено именно значение Infinity.

NaN

Ключевое слово NaN обозначает «не число» и возвращается, когда вы пытаетесь произвести недопустимую вычислительную операцию. Например:

let nope = 1920 / "blah";

В данном случае будет возвращено NaN, так как нельзя делить число на строку. Существуют простые случаи, в которых это будет происходить, и некоторые из них мы рассмотрим позднее.

Получение числа из строки

Иногда у вас будут числа, заключенные внутри строк. Чтобы подробно ознакомиться с этой темой, прочтите статью «Получение числа из строки» (https://www.kirupa.com/html5/going_from_a_string_to_a_number.htm).

Объект Math

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

Скукота!

Буду с вами честен. Разбор всех предлагаемых объектом Math возможностей был бы скучен. Если вы не фанат этой темы, то я предлагаю пробежаться по следующим разделам и возвращаться к ним уже по мере необходимости. Объект Math никуда не уйдет — друзей у него нет, поэтому он будет преданно ждать вас и никуда не денется.

Константы

Чтобы избавить вас от необходимости определять такие математические постоянные, как число π, постоянная Эйлера, натуральный логарифм и т. д., объект Math определяет большинство распространенных констант за вас (табл. 16.2).

Табл. 16.2. Константы

Использование

Что обозначает

Math.E

Постоянная Эйлера

Math.LN2

Натуральный логарифм 2

Math.LN10

Натуральный логарифм 10

Math.LOG2E

Log E по основанию 2

Math.LOG10E

Log E по основанию 10

Math.PI

3,14159 (это все, что я помню, и мне лень искать остальное!)

Math.SQRT1_2

1

2


Math.SQRT2

2


Из всех этих констант я чаще всего использовал Math.PI:

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

function getCircumference(radius) {

return 2 * Math.PI * radius;

}


console.log(getCircumference(2));

Используется Math.PI и все прочие константы так же, как и любая переменная с именем.

Округление чисел

Ваши числа часто будут содержать ненужную точность:

let position = getPositionFromCursor(); // 159.3634493939

Чтобы облегчить округление таких чисел до разумного целочисленного значения, используются функции Math.round(), Math.ceil() и Math.floor(), в которые число передается в виде аргумента (табл. 16.3).

Табл. 16.3. Функции округления

Функция

Действие

Math.round()

Возвращает число, округленное до ближайшего целого числа. При этом округление происходит вверх, если аргумент больше или равен 0,5. Если аргумент меньше 0,5, округление производится до текущего целого числа

Math.ceil()

Возвращает число, которое больше или равно вашему аргументу

Math.floor()

Возвращает число, которое меньше или равно вашему аргументу

Легче всего понять эту таблицу, посмотрев функции в действии:

Math.floor(.5); // 0

Math.ceil(.5); // 1

Math.round(.5); // 1


Math.floor(3.14); // 3

Math.round(3.14); // 3

Math.ceil(3.14); // 4


Math.floor(5.9); // 5

Math.round(5.9); // 6

Math.ceil(5.9); // 6

Эти функции всегда округляют до целого числа. Если вы хотите произвести округление до точного набора цифр, то ознакомьтесь со второй половиной статьи «Округление чисел в JavaScript» (https://www.kirupa.com/html5/rounding_numbers_in_javascript.htm).

Тригонометрические функции

Больше всего мне нравится, что объект Math дает удобный способ обращаться почти что к любым тригонометрическим функциям, которые могут понадобиться (табл. 16.4).

Для их использования просто передайте число в качестве аргумента:

Math.cos(0); // 1

Math.sin(0); // 0

Math.tan(Math.PI / 4); // 1

Math.cos(Math.PI); // 1

Math.cos(4 * Math.PI); // 1

Табл. 16.4. Тригонометрические функции

Функция

Действие

Math.cos()

Вычисляет косинус аргумента

Math.sin()

Вычисляет синус аргумента

Math.tan()

Вычисляет тангенс аргумента

Math.acos ()

Вычисляет арккосинус аргумента (крутое название, да?)

Math.asin()

Вычисляет арксинус аргумента

Math.atan ()

Вычисляет арктангенс аргумента

Эти функции получают значения в виде радиан. Если же ваши числа представлены в виде градусов, то сначала преобразуйте их в радианы.

Степени и квадратные корни

В табл. 16.5 даны еще несколько функций, присущих объекту Math, а именно Math.pow(), Math.exp() и Math.sqrt().

Табл. 16.5. Функции для вычисления степеней и квадратных корней

Функция

Действие

Math.pow()

Возводит число в заданную степень

Math.exp()

Возводит постоянную Эйлера в заданную степень

Math.sqrt()

Возвращает квадратный корень заданного аргумента

Теперь взглянем на несколько примеров:

Math.pow(2, 4); //эквивалент 2^4 (или 2 * 2 * 2 * 2)

Math.exp(3); //эквивалент Math.E^3

Math.sqrt(16); //4

Обратите внимание, что Math.pow() получает два аргумента. Это, вероятно, первая рассмотренная нами встроенная функция, получающая два аргумента, что даже несколько вдохновляет.

Получение абсолютного значения

Если вам понадобится получить абсолютное значение, просто используйте функцию Math.abs():

Math.abs(37); //37

Math.abs(-6); //6

На этом все.

Случайные числа

Для генерации случайных чисел между 0 и чуть меньше, чем 1, можно использовать функцию Math.random(). Эта функция не получает аргументы, но вы можете легко использовать ее как часть выражения:

let randomNumber = Math.random() * 100;

При каждом вызове этой функции вы увидите случайное число, возвращаемое для Math.random(). Все подробности ее использования для генерации случайных чисел вы можете найти в статье «Случайные числа в JS» (https://www.kirupa.com/html5/random_numbers_js.htm).

КОРОТКО О ГЛАВНОМ

На этом ознакомительная глава, посвященная числам и объекту Math в JavaScript, окончена. Как вы видите, легче уже некуда. JS предоставляет максимально простой подход для работы с этими элементами, а эта глава лишь мельком показала горизонты их возможностей на случай, если вы решите направиться к ним.

Ниже представлены дополнительные ресурсы с примерами, которые помогут вам лучше понять возможности использования чисел в JavaScript:

• Получение числа из строки: http://bit.ly/kirupaStrToNum

• Случайные числа в JS: http://bit.ly/kirupaRandom

• Продвинутые случайные числа в JS: http://bit.ly/AdvRandom

• Почему мои числа не складываются: http://bit.ly/kirupaFPG

• Случайные цвета в JS: http://bit.ly/kirupaRandomColors

Числа в JavaScript — это занятная тема, которая местами может быть запутывающей. Если у вас вдруг возникнут трудности, то прояснить ситуацию вы можете, обратившись на форум https://forum.kirupa.com.

Глава 17. Методы получения и изменения данных