Добавление функций в нашу демонстрацию отбойных шаров - Изучение веб-разработки

Добавление функций в нашу демонстрацию отбойных шаров
=====================================================

* Назад
* Обзор: Objects

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

| Требования: | Перед тем как приступить к этому упражнению нужно выполнить задания из всех статей текущего модуля. |
| --- | --- |
| Цель: | Проверить насколько хорошо вы понимаете объекты и связанные с ними конструкции в языке Javascript. |

Начало
------

Для начала скопируйте файлы index-finished.html, style.css и main-finished.js из предыдущей статьи в новую директорию на вашем компьютере.

Для выполнения упражнения вы можете использовать сайт JSBin или Thimble. Вы можете вставлять HTML, CSS и JavaScript-код в один из этих онлайн-редакторов. Если ваш онлайн-редактор не поддерживает раздельные панели для редактирования JavaScript/CSS кода, то вы можете встроить код в HTML с помощью тегов `<script>`/`<style>`.

**Примечание:** . Если у вас что-то не получается — попросите о помощи. Более подробная информация находится в секции Assessment or further help в конце этой страницы.

Краткое описание проекта
------------------------

Наша весёлая демонстрация шаров - это весело, но теперь мы хотим сделать её немного более интерактивной, добавив контролируемый пользователем злой круг, который будет есть шары, если он их поймает. Мы также хотим проверить ваши навыки создания объектов, создав общий объект `Shape()`, который могут наследовать наши шары и злой круг. Наконец, мы хотим добавить счётчик очков, чтобы отслеживать количество оставшихся шаров для захвата.

Следующий скриншот даёт вам представление о том, как должна выглядеть готовая программа:

![](/ru/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features/bouncing-evil-circle.png)

Чтобы дать вам больше идеи, посмотрите на законченный пример (не заглядывая в исходный код!)

Шаги по завершению
------------------

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

### Создание наших новых объектов

Прежде всего, измените существующий конструктор `Ball()` так, чтобы он стал конструктором `Shape()` и добавил новый конструктор `Ball()`:

1. Конструктор `Shape()` должен определять свойства `x`, `y`, `velX` и `velY`, так же, как и конструктор `Ball()`, но не свойства `color` и `size`.
2. Он также должен определить новое свойство `exists`, которое используется для отслеживания наличия шаров в программе (не было съедено злым кругом). Это должно быть логическое (`true` / `false`).
3. Конструктор `Ball()` должен наследовать свойства `x`, `y`, `velX`, `velY` и `exists` из конструктора `Shape()`.
4. Он также должен определить свойство `color` и `size`, как это сделал оригинальный конструктор `Ball()`.
5. Не забудьте установить `prototype` и `constructor` конструктора `Ball()` соответствующим образом.

Определения меток шара `draw()`, `update()` и `collisionDetect()` должны быть такими же, как и раньше.

Вам также нужно добавить новый параметр в новый вызов конструктора `new Ball() ( ... )` - параметр `exists` должен быть 5-м параметром и ему должно быть присвоено значение `true`.

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

### Определение EvilCircle()

Теперь пришло время встретить плохого парня - `EvilCircle()`! Наша игра будет включать только один злой круг, но мы все ещё будем определять его с помощью конструктора, который наследует от `Shape()`, чтобы дать вам некоторую практику. Возможно, вам захочется добавить ещё один круг в приложение, которое может контролироваться другим игроком или иметь несколько злобных окружений, управляемых компьютером. Вы, вероятно, не собираетесь захватить мир одним злым кругом, но он будет делать для этой оценки.

Конструктор `EvilCircle()` должен наследовать `x`, `y`, `velX`, `velY` и `exists` из `Shape()`, но `velX` и `velY` должны всегда равняться 20.

Вы должны сделать что-то вроде `Shape.call(this, x, y, 20, 20, exists);`

Он также должен определить свои собственные свойства следующим образом:

* `color` — `'white'`
* `size` — `10`

Опять же, не забудьте определить свои унаследованные свойства как параметры в конструкторе и правильно установить свойства `prototype` и `constructor`.

### Defining EvilCircle()'s methods

`EvilCircle()` должен иметь четыре метода, как описано ниже.

#### `draw()`

Этот метод имеет ту же цель, что и метод `draw()` метода `Ball()`: он рисует экземпляр объекта на холсте. Он будет работать очень схожим образом, поэтому вы можете начать с копирования определения `Ball.prototype.draw`. Затем вы должны внести следующие изменения:

* Мы хотим, чтобы злой круг не был заполнен, а скорее имел внешнюю линию (удар). Вы можете добиться этого, обновив `fillStyle` и `fill()` до `strokeStyle` и s `stroke()`.
* Мы также хотим сделать ход немного толще, так что вы можете увидеть злой круг немного легче. Этого можно добиться, установив значение для `lineWidth` где-нибудь после вызова `beginPath()` (3 будем делать).

#### `checkBounds()`

Этот метод будет делать то же самое, что и первая часть функции `Ball()`'s `update()`, чтобы посмотреть, не исчезнет ли злой круг от края экрана и не прекратит это делать. Опять же, вы можете просто скопировать определение `Ball.prototype.update`, но есть несколько изменений, которые вы должны сделать:

* Избавьтесь от последних двух строк - мы не хотим автоматически обновлять позицию злого круга на каждом кадре, потому что мы будем перемещать его каким-то другим способом, как вы увидите ниже.
* Внутри операторов `if()`, если тесты возвращают true, мы не хотим обновлять `velX` / `velY`; мы хотим вместо этого изменить значение `x` / `y`, так что злой круг возвращается на экран немного. Добавление или вычитание (по мере необходимости) свойства `size` злого круга имеет смысл.

#### `setControls()`

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

```js
var _this = this;
window.onkeydown = function (e) {
  if (e.keyCode === 65) {
    _this.x -= _this.velX;
  } else if (e.keyCode === 68) {
    _this.x += _this.velX;
  } else if (e.keyCode === 87) {
    _this.y -= _this.velY;
  } else if (e.keyCode === 83) {
    _this.y += _this.velY;
  }
};
```

Поэтому, когда нажата клавиша, проконсультируется о свойствах keyCode (en-US) объекта события, чтобы увидеть, какая клавиша нажата. Если это один из четырёх, представленных указанными ключевыми кодами, тогда злой круг будет перемещаться влево / вправо / вверх / вниз.

* Для бонусного пункта сообщите нам, к каким ключам относятся указанные коды ключей.
* В другой бонусной точке вы можете сказать нам, почему нам пришлось установить `var _this = this`; в позиции, в которой он находится? Это как-то связано с функциональной областью.

#### `collisionDetect()`

Этот метод будет действовать очень похоже на метод `collisionDetect()` в `Ball()`, поэтому вы можете использовать его в качестве основы для этого нового метода. Но есть несколько отличий:

* В внешнем выражении `if` вам больше не нужно проверять, совпадает ли текущий мяч на итерации с шаром, который выполняет проверку, потому что он больше не является мячиком, это злой круг! Вместо этого вам нужно выполнить проверку, чтобы проверить, существует ли проверенный шар (с каким свойством вы могли бы это сделать?). Если этого не существует, он уже был съеден злым кругом, поэтому нет необходимости проверять его снова.
* Во внутреннем `if`-выражении вы больше не хотите, чтобы объекты меняли цвет при обнаружении столкновения - вместо этого вы хотите установить любые шары, которые сталкиваются с злым кругом, чтобы больше не существовать (опять же, как вы думаете, d сделать это?).
### Приведение злого круга в программу

Теперь мы определили злой круг, нам нужно на самом деле заставить его появиться на нашей сцене. Для этого вам нужно внести некоторые изменения в функцию `loop()`.

* Прежде всего, создайте новый экземпляр объекта злого круга (указав необходимые параметры), затем вызовите его метод `setControls()`. Вам нужно только сделать эти две вещи один раз, а не на каждой итерации цикла.
* В точке, где вы прокручиваете каждый шар и вызываете функции `draw()`, `update()` и `collisionDetect()` для каждого из них, делайте так, чтобы эти функции вызывались только в том случае, если текущий шар существует.
* На каждой итерации цикла вызовите методы draw `draw()`, `checkBounds()` и `collisionDetect()` для злого шарика.
### Реализация счётчика баллов

Чтобы выполнить счётчик счётчиков, выполните следующие действия:

1. В своём HTML-файле добавьте элемент `<p>` непосредственно под элементом `<h1>` (en-US), содержащим текст «Ball count:».
2. В вашем файле CSS добавьте следующее правило внизу:

```css
p {
  position: absolute;
  margin: 0;
  top: 35px;
  right: 5px;
  color: #aaa;
}
```

3. В своём JavaScript сделайте следующие обновления:
    * Создайте переменную, которая хранит ссылку на абзац.
    * Держите подсчёт количества шаров на экране в некотором роде.
    * Увеличьте количество и покажите обновлённое количество шаров каждый раз, когда шар добавляется в сцену.
    * Уменьшите счёт и покажите обновлённое количество мячей каждый раз, когда злой круг ест шарик (его не существует).
Советы и подсказки
------------------

* Эта оценка довольно сложная. Делайте каждый шаг медленно и осторожно.
* Может быть, идея сохранить отдельную копию демонстрации после того, как вы получите каждый этап работы, так что вы можете вернуться к ней, если позже окажетесь в беде.
Assessment
----------

Если вы проводите эту оценку в рамках организованного курса, вы должны уметь отдать свою работу своему учителю / наставнику для маркировки. Если вы самообучаетесь, то вы можете получить руководство по маркировке довольно легко, задав тему обсуждения для этого упражнения или в IRC-канале #mdn в Mozilla IRC. Сначала попробуйте упражнение - ничего не выиграть от обмана!

* Назад
* Обзор: Objects
In this module
--------------

* Object basics
* Object-oriented JavaScript for beginners (en-US)
* Object prototypes
* Inheritance in JavaScript
* Working with JSON data
* Object building practice
* Adding features to our bouncing balls demo


Наследование в JavaScript - Изучение веб-разработки

Наследование в JavaScript
=========================

* Назад
* Обзор: Objects
* Далее

Теперь, когда объясняется большая часть подробностей OOJS, эта статья показывает, как создавать «дочерние» классы объектов (конструкторы), которые наследуют признаки из своих «родительских» классов. Кроме того, мы дадим некоторые советы о том, когда и где вы можете использовать OOJS , и посмотрим, как классы рассматриваются в современном синтаксисе ECMAScript.

| Необходимые знания: | Базовая компьютерная грамотность, понимание основ HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Структурные элементы) and основы Объектно-ориентированного JS (см. Введение в объекты). |
| --- | --- |
| Цель: | Понять, как можно реализовать наследование в JavaScript. |

Прототипное наследование
------------------------

До сих пор мы видели некоторое наследование в действии - мы видели, как работают прототипы и как элементы наследуются, поднимаясь по цепочке. Но в основном это связано с встроенными функциями браузера. Как создать объект в JavaScript, который наследует от другого объекта?

Давайте рассмотрим, как это сделать на конкретном примере.

Начало работы
-------------

Прежде всего сделайте себе локальную копию нашего файла oojs-class-inheritance-start.html (он также работает в режиме реального времени). В файле вы найдёте тот же пример конструктора `Person()`, который мы использовали на протяжении всего модуля, с небольшим отличием - мы определили внутри конструктора только лишь свойства:

```js
function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
}
```

*Все* методы определены в прототипе конструктора. Например:

```js
Person.prototype.greeting = function () {
  alert("Hi! I'm " + this.name.first + ".");
};
```

**Примечание:** . В исходном коде вы также увидите определённые методы `bio()` и `farewell()`. Позже вы увидите, как они могут быть унаследованы другими конструкторами.

Скажем так, мы хотели создать класс `Teacher`, подобный тому, который мы описали в нашем первоначальном объектно-ориентированном определении, которое наследует всех членов от `Person`, но также включает в себя:

1. Новое свойство, `subject` - оно будет содержать предмет, который преподаёт учитель.
2. Обновлённый метод `greeting()`, который звучит немного более формально, чем стандартный метод `greeting()` — более подходит для учителя, обращающегося к некоторым ученикам в школе.
Определение функции-конструктора Teacher()
------------------------------------------

Первое, что нам нужно сделать, это создать конструктор `Teacher()` - добавьте ниже следующий код:

```js
function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}
```

Это похоже на конструктор Person во многих отношениях, но здесь есть что-то странное, что мы не видели раньше - функцию `call()`. Эта функция в основном позволяет вам вызывать функцию, определённую где-то в другом месте, но в текущем контексте. Первый параметр указывает значение `this`, которое вы хотите использовать при выполнении функции, а остальные параметры - те, которые должны быть переданы функции при её вызове.

Мы хотим, чтобы конструктор `Teacher()` принимал те же параметры, что и конструктор `Person()`, от которого он наследуется, поэтому мы указываем их как параметры в вызове `call()`.

Последняя строка внутри конструктора просто определяет новое свойство `subject`, которое будут иметь учителя, и которого нет у Person().

В качестве примечания мы могли бы просто сделать это:

```js
function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last,
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}
```

Но это просто переопределяет свойства заново, а не наследует их от `Person()`, так что теряется смысл того, что мы пытаемся сделать. Он также занимает больше строк кода.

### Наследование от конструктора без параметров

Обратите внимание, что если конструктор, от которого вы наследуете, не принимает значения своего свойства из параметров, вам не нужно указывать их в качестве дополнительных аргументов в `call()`. Так, например, если у вас было что-то действительно простое:

```js
function Brick() {
  this.width = 10;
  this.height = 20;
}
```

Вы можете наследовать свойства `width` и `height`, выполнив это (как и другие шаги, описанные ниже, конечно):

```js
function BlueGlassBrick() {
  Brick.call(this);

  this.opacity = 0.5;
  this.color = "blue";
}
```

Обратите внимание, что мы указали только `this` внутри `call()` - никаких других параметров не требуется, поскольку мы не наследуем никаких свойств родителя, которые задаются через параметры.

Установка Teacher()'s prototype и конструктор ссылок
----------------------------------------------------

Пока все хорошо, но у нас есть проблема. Мы определили новый конструктор и у него есть свойство `prototype`, которое по умолчанию просто содержит ссылку на саму конструкторскую функцию. Он не содержит методов свойства `prototype` конструктора `Person`. Чтобы увидеть это, введите `Object.getOwnPropertyNames(Teacher.prototype)` в поле ввода текста или в вашу консоль JavaScript. Затем введите его снова, заменив `Teacher` на `Person`. Новый конструктор *не наследует* эти методы. Чтобы увидеть это, сравните выводы в консоль `Person.prototype.greeting` и `Teacher.prototype.greeting`. Нам нужно заставить `Teacher()` наследовать методы, определённые на прототипе `Person()`. Итак, как мы это делаем?

1. Добавьте следующую строку ниже своего предыдущего добавления:

```js
Teacher.prototype = Object.create(Person.prototype);
```

Здесь наш друг `create()` снова приходит на помощь. В этом случае мы используем его для создания нового объекта и делаем его значением `Teacher.prototype`. Новый объект имеет свой прототип `Person.prototype` и, следовательно, наследует, если и когда это необходимо, все доступные методы `Person.prototype`.
2. Нам нужно сделать ещё одну вещь, прежде чем двигаться дальше. После добавления последней строки, `Teacher.prototype.constructor` стало равным `Person()`, потому что мы просто устанавливаем `Teacher.prototype` для ссылки на объект, который наследует его свойства от `Person.prototype`! Попробуйте сохранить код, загрузите страницу в браузере и введите `Teacher.prototype.constructor` в консоль для проверки.
3. Это может стать проблемой, поэтому нам нужно сделать это правильно. Вы можете сделать это, вернувшись к исходному коду и добавив следующие строки внизу:
```

Object.defineProperty(Teacher.prototype, 'constructor', {
    value: Teacher,
    enumerable: false, // false, чтобы данное свойство не появлялось в цикле for in
    writable: true });
```

4. Теперь, если вы сохраните и обновите, введите `Teacher.prototype.constructor`, чтобы вернуть `Teacher()`, плюс мы теперь наследуем `Person()`!
Предоставление Teacher() новой функции greeting()
-------------------------------------------------

Чтобы завершить наш код, нам нужно определить новую функцию `greeting()` в конструкторе `Teacher()`.

Самый простой способ сделать это - определить его на прототипе `Teacher()` - добавить в нижнюю часть кода следующее:

```js
Teacher.prototype.greeting = function () {
  var prefix;

  if (
    this.gender === "male" ||
    this.gender === "Male" ||
    this.gender === "m" ||
    this.gender === "M"
  ) {
    prefix = "Mr.";
  } else if (
    this.gender === "female" ||
    this.gender === "Female" ||
    this.gender === "f" ||
    this.gender === "F"
  ) {
    prefix = "Mrs.";
  } else {
    prefix = "Mx.";
  }

  alert(
    "Hello. My name is " +
      prefix +
      " " +
      this.name.last +
      ", and I teach " +
      this.subject +
      ".",
  );
};
```

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

Попробуйте пример
-----------------

Теперь, когда вы ввели весь код, попробуйте создать экземпляр объекта из `Teacher()`, поставив ниже вашего JavaScript-кода (или что-то похожее по вашему выбору):

```js
var teacher1 = new Teacher(
  "Dave",
  "Griffiths",
  31,
  "male",
  ["football", "cookery"],
  "mathematics",
);
```

Теперь сохраните, обновите, и попробуйте получить доступ к свойствам и методам вашего нового объекта `teacher1`, например:

```js
teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();
```

Все должно работать нормально. Запросы в строках 1, 2, 3 и 6 унаследованные от общего конструктора `Person()` (класса). Запрос в строке 4 обращается к `subject`, доступному только для более специализированного конструктора (класса) `Teacher()`. Запрос в строке 5 получил бы доступ к методу `greeting()`, унаследованному от `Person()`, но `Teacher()` имеет свой собственный метод `greeting()` с тем же именем, поэтому запрос обращается к этому методу.

**Примечание:** . Если вам не удаётся заставить это работать, сравните свой код с нашей готовой версией (см. также рабочее демо).

Методика, которую мы здесь рассмотрели, - это не единственный способ создания наследующих классов в JavaScript, но он работает нормально и это даёт вам представление о том, как реализовать наследование в JavaScript.

Вам также может быть интересно узнать некоторые из новых функций ECMAScript, которые позволяют нам делать наследование более чисто в JavaScript (см. Classes). Мы не рассматривали их здесь, поскольку они пока не поддерживаются очень широко в браузерах. Все остальные конструкторы кода, которые мы обсуждали в этом наборе статей, поддерживаются ещё в IE9 или ранее и есть способы добиться более ранней поддержки, чем это.

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

Дальнейшее упражнение
---------------------

В нашем руководстве по Объектно-ориентированному JavaScript для начинающих (en-US) мы также включили класс `Student` как концепцию, которая наследует все особенности `Person`, а также имеет другой метод `greeting()` от `Person`, который гораздо более неформален, чем приветствие `Teacher`. Посмотрите, как выглядит приветствие ученика в этом разделе, и попробуйте реализовать собственный конструктор `Student()`, который наследует все функции `Person()` и реализует другую функцию `greeting()`.

**Примечание:** . Если вам не удаётся заставить это работать, сравните свой код с нашей готовой версией (см. также рабочее демо).

Object member summary
---------------------

Подводя итог, вы в основном получили три типа свойств / методов, о которых нужно беспокоиться:

1. Те, которые определены внутри функции-конструктора, которые присваиваются экземплярам объекта. Их довольно легко заметить - в вашем собственном коде они представляют собой элементы, определённые внутри конструктора, используя строки `this.x = x`; в встроенном коде браузера они являются членами, доступными только для экземпляров объектов (обычно создаются путём вызова конструктора с использованием ключевого слова `new`, например `var myInstance = new myConstructor ()`.
2. Те, которые определяются непосредственно самим конструктором, которые доступны только для конструктора. Они обычно доступны только для встроенных объектов браузера и распознаются путём непосредственной привязки к конструктору, а не к экземпляру. Например, `Object.keys()`.
3. Те, которые определены в прототипе конструктора, которые наследуются всеми экземплярами и наследуют классы объектов. К ним относятся любой член, определённый в свойстве прототипа конструктора, например. `myConstructor.prototype.x()`.

Если вы не уверены, что это такое, не беспокойтесь об этом, пока вы ещё учитесь и знание придёт с практикой.

Когда вы используете наследование в JavaScript?
-----------------------------------------------

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

В некотором смысле вы используете наследование все время. Всякий раз, когда вы используете различные функции веб-API или методы/свойства, определённые во встроенном объекте браузера, который вы вызываете в своих строках, массивах и т.д., вы неявно используете наследование.

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

**Примечание:** . Из-за того, как работает JavaScript, с цепочкой прототипов и т.д., совместное использование функций между объектами часто называется **делегированием**. Специализированные объекты делегируют функциональность универсальному типу объекта.

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

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

Резюме
------

В этой статье мы рассмотрели оставшуюся часть основной теории и синтаксиса OOJS, которые, как мы думаем, вам следует знать сейчас. На этом этапе вы должны понимать основы JavaScript, ООП, прототипы и прототипное наследование, как создавать классы (конструкторы) и экземпляры объектов, добавлять функции в классы и создавать подклассы, которые наследуются от других классов.

В следующей статье мы рассмотрим, как работать с JavaScript Object Notation (JSON), общим форматом обмена данными, написанным с использованием объектов JavaScript.

See also
--------

* ObjectPlayground.com — A really useful interactive learning site for learning about objects.
* Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
* You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.
* Назад
* Обзор: Objects
* Далее


Работа с JSON - Изучение веб-разработки

Работа с JSON
=============

* Назад
* Обзор: Objects
* Далее

Обозначение объектов JavaScript (JSON - JavaScript Object Notation) - стандартный текстовый формат для представления структурированных данных на основе синтаксиса объекта JavaScript. Он обычно используется для передачи данных в веб-приложениях (например, отправка некоторых данных с сервера клиенту,таким образом чтобы это могло отображаться на веб-странице или наоборот). Вы будете сталкиваться с этим довольно часто, поэтому в этой статье мы даём вам все, что вам нужно для работы с JSON используя JavaScript, включая парсинг JSON, чтобы вы могли получить доступ к данным внутри него при создании JSON.

| Необходимые знания: | Базовая компьютерная грамотность, базовые знания HTML и CSS, знакомство с основами JavaScript (см. First steps и Building blocks) и основами OOJS (see Introduction to objects (en-US)). |
| --- | --- |
| Цель: | Понять, как работать с данными, хранящимися в JSON, и создавать свои собственные объекты JSON. |

Нет, действительно, что такое JSON?
-----------------------------------

JSON - текстовый формат данных, следующий за синтаксисом объекта JavaScript, который был популяризирован Дугласом Крокфордом. Несмотря на то, что он очень похож на буквенный синтаксис объекта JavaScript, его можно использовать независимо от JavaScript, и многие среды программирования имеют возможность читать (анализировать) и генерировать JSON.

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

**Примечание:** Преобразование строки в родной объект называется десериализацией (преобразование из последовательной формы в параллельную\*)\*, в то время как преобразовании родного объекта в строку, таким образом ,чтобы он мог быть передан через сеть, называется сериализацией(преобразование в последовательную форму).

Объект JSON может быть сохранён в собственном файле, который в основном представляет собой текстовый файл с расширением `.json` и MIME type `application/json`.

### Структура JSON

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

```json
{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}
```

Если бы мы загрузили этот объект в программу JavaScript, создали переменную с названием `superHeroes`, мы могли бы затем получить доступ к данным внутри неё, используя те же самые точечную и скобочную нотации, которые мы рассмотрели в статье JavaScript object basics. Например:

```js
superHeroes.homeTown;
superHeroes["active"];
```

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

```js
superHeroes["members"][1]["powers"][2];
```

1. Сначала у нас есть имя переменной - `superHeroes`.
2. Внутри мы хотим получить доступ к свойству `members`, поэтому мы используем `['members']`.
3. `members` содержат массив, заполненный объектами. Мы хотим получить доступ ко второму объекту внутри массива, поэтому мы используем `[1]`.
4. Внутри этого объекта мы хотим получить доступ к свойству `powers`, поэтому мы используем `['powers']`.
5. Внутри свойства `powers` находится массив, содержащий сверхспособности выбранного героя. Нам нужен третий, поэтому мы используем `[2]`.

**Примечание:** . Мы сделали JSON, видимый выше, доступным внутри переменной в нашем примере JSONTest.html (см. исходный код). Попробуйте загрузить это, а затем получить доступ к данным внутри переменной через консоль JavaScript вашего браузера.

### Массивы как JSON

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

```json
[
  {
    "name": "Molecule Man",
    "age": 29,
    "secretIdentity": "Dan Jukes",
    "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
  },
  {
    "name": "Madame Uppercut",
    "age": 39,
    "secretIdentity": "Jane Wilson",
    "powers": [
      "Million tonne punch",
      "Damage resistance",
      "Superhuman reflexes"
    ]
  }
]
```

Вышесказанное вполне справедливо для JSON. Вам просто нужно получить доступ к элементам массива (в его анализируемой версии), начиная с индекса массива, например `[0]["powers"][0]`.

### Другие примечания

* JSON - это чисто формат данных - он содержит только свойства, без методов.
* JSON требует двойных кавычек, которые будут использоваться вокруг строк и имён свойств. Одиночные кавычки недействительны.
* Даже одна неуместная запятая или двоеточие могут привести к сбою JSON-файла и не работать. Вы должны быть осторожны, чтобы проверить любые данные, которые вы пытаетесь использовать (хотя сгенерированный компьютером JSON с меньшей вероятностью включает ошибки, если программа генератора работает правильно). Вы можете проверить JSON с помощью приложения вроде JSONLint.
* JSON может принимать форму любого типа данных, допустимого для включения в JSON, а не только массивов или объектов. Так, например, одна строка или номер будут действительным объектом JSON.
* В отличие от кода JavaScript, в котором свойства объекта могут не заключаться в двойные кавычки, в JSON в качестве свойств могут использоваться только строки заключённые в двойные кавычки.
Активное обучение: Работа с примером JSON
-----------------------------------------

Итак, давайте рассмотрим пример, чтобы показать то, как мы можем использовать некоторые данные JSON на веб-сайте.

### Начало работы

Для начала создайте локальные копии наших файлов heroes.html и style.css. Последний содержит простой CSS для стилизации нашей страницы, в то время как первый содержит очень простой HTML-код сущности:

```html
<header></header>

<section></section>
```

Плюс `<script>`, чтобы содержать код JavaScript, который мы будем писать в этом упражнении. На данный момент он содержит только две строки, которые захватывают ссылки на элементы `<header>` и `<section>` и сохраняют их в переменных:

```js
var header = document.querySelector("header");
var section = document.querySelector("section");
```

Мы предоставили данные JSON на нашем GitHub, на https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json.

Мы собираемся загрузить его на нашу страницу и использовать некоторые изящные манипуляции DOM, чтобы отобразить их, например:

![](/ru/docs/Learn/JavaScript/Objects/JSON/json-superheroes.png)

### Получение JSON

Чтобы получить JSON, мы будем использовать API, называемый `XMLHttpRequest` (часто называемый **XHR**). Это очень полезный объект JavaScript, который позволяет нам делать сетевые запросы для извлечения ресурсов с сервера через JavaScript (например, изображения, текст, JSON, даже фрагменты HTML), что означает, что мы можем обновлять небольшие разделы контента без необходимости перезагрузки всей страницы. Это привело к более отзывчивым веб-страницам и звучит захватывающе, но, к сожалению, выходит за рамки этой статьи, чтобы изучить это гораздо более подробно.

1. Начнём с того, что мы собираемся сохранить URL-адрес JSON, который мы хотим получить в переменной. Добавьте нижеследующий код JavaScript:

```js
var requestURL =
  "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
```

2. Чтобы создать запрос, нам нужно создать новый экземпляр объекта запроса из конструктора `XMLHttpRequest`, используя ключевое слово `new`. Добавьте следующую ниже свою последнюю строку:

```js
var request = new XMLHttpRequest();
```

3. Теперь нам нужно открыть новый запрос, используя метод `open()`. Добавьте следующую строку:

```js
request.open("GET", requestURL);
```

Это занимает не менее двух параметров - есть другие доступные параметры. Нам нужно только два обязательных для этого простого примера:
    * Метод HTTP, который следует использовать при выполнении сетевого запроса. В этом случае `GET` самый подходящий, так как мы просто извлекаем некоторые простые данные.
    * URL-адрес для запроса - это URL-адрес файла JSON, который мы сохранили ранее.
4. Затем добавьте следующие две строки: здесь мы устанавливаем `responseType` в JSON, так что XHR знает, что сервер будет возвращать JSON и, что это должно быть преобразовано за кулисами в объект JavaScript. Затем мы отправляем запрос методом `send()`:

```js
request.responseType = "json";
request.send();
```

5. Последний бит этого раздела предполагает ожидание ответа на возврат с сервера, а затем работы с ним. Добавьте следующий код ниже вашего предыдущего кода:

```js
request.onload = function () {
  var superHeroes = request.response;
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

Здесь мы сохраняем ответ на наш запрос (доступный в свойстве `response`) в переменной `superHeroes`; эта переменная теперь будет содержать объект JavaScript, основанный на JSON! Затем мы передаём этот объект двум вызовам функций - первый из них заполнит `<header>` правильными данными, а второй создаст информационную карту для каждого героя в команде и вставляет её в `<section>`.

Мы свернули код в обработчик событий, который запускается, когда событие загрузки запускается в объекте запроса (см. `onload` (en-US)) - это связано с тем, что событие загрузки запускается, когда ответ успешно возвращается; поступая таким образом,это гарантия того, что `request.response` определённо будет доступен, когда мы начнём работу с ним.

Заполнение заголовка

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

```js
function populateHeader(jsonObj) {
  var header = document.querySelector("header");
  var myH1 = document.createElement("h1");
  myH1.textContent = jsonObj["squadName"];
  header.appendChild(myH1);

  var myPara = document.createElement("p");
  myPara.textContent =
    "Hometown: " + jsonObj["homeTown"] + " // Formed: " + jsonObj["formed"];
  header.appendChild(myPara);
}
```

Мы назвали параметр `jsonObj`, чтобы напомнить себе, что этот объект JavaScript возник из JSON. Здесь мы сначала создаём элемент `<h1>` (en-US) с `createElement()`, устанавливаем его `textContent` равным свойству `squadName` объекта, а затем добавляем его в заголовок с помощью `appendChild()`. Затем мы выполняем очень похожую операцию с абзацем: создаём его, устанавливаем его текстовое содержимое и добавляем его в заголовок. Единственное различие заключается в том, что его текст задан, как конкатенированная строка, содержащая как `homeTown`, так и `formed` свойства объекта.

### Создание информационных карт героя

Затем добавьте следующую функцию внизу кода, которая создаёт и отображает карты супергероев:

```js
function showHeroes(jsonObj) {
  var section = document.querySelector("section");
  var heroes = jsonObj["members"];

  for (var i = 0; i < heroes.length; i++) {
    var myArticle = document.createElement("article");
    var myH2 = document.createElement("h2");
    var myPara1 = document.createElement("p");
    var myPara2 = document.createElement("p");
    var myPara3 = document.createElement("p");
    var myList = document.createElement("ul");

    myH2.textContent = heroes[i].name;
    myPara1.textContent = "Secret identity: " + heroes[i].secretIdentity;
    myPara2.textContent = "Age: " + heroes[i].age;
    myPara3.textContent = "Superpowers:";

    var superPowers = heroes[i].powers;
    for (var j = 0; j < superPowers.length; j++) {
      var listItem = document.createElement("li");
      listItem.textContent = superPowers[j];
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}
```

Для начала сохраним свойство `members` объекта JavaScript в новой переменной. Этот массив содержит несколько объектов, которые содержат информацию для каждого героя.

Затем мы используем for loop для циклического прохождения каждого объекта в массиве. Для каждого из них мы:

1. Создаём несколько новых элементов: `<article>`, `<h2>`, три `<p>` и `<ul>`.
2. Устанавливаем `<h2>`, чтобы содержать `name` текущего героя.
3. Заполняем три абзаца своей `secretIdentity`, `age` и строкой, в которой говорится: «Суперспособности:», чтобы ввести информацию в список.
4. Сохраняем свойство `powers` в другой новой переменной под названием `superPowers` - где содержится массив, в котором перечислены сверхспособности текущего героя.
5. Используем другой цикл `for`, чтобы прокрутить сверхспособности текущего героя , для каждого из них мы создаём элемент `<li>`, помещаем в него сверхспособности, а затем помещаем `listItem` внутри элемента `<ul>` (`myList`) с помощью `appendChild()`.
6. Последнее, что мы делаем, это добавляем `<h2>`, `<p>` и `<ul>` внутри `<article>` (`myArticle`), а затем добавляем `<article>` в `<section>`. Важное значение имеет порядок, в котором добавляются элементы, так как это порядок, который они будут отображать внутри HTML.

**Примечание:** . Если вам не удаётся заставить этот пример работать, попробуйте обратиться к нашему исходному коду heroes-finished.html (см. также он работает в режиме live).

**Примечание:** . Если у вас возникли проблемы после нотации точек / скобок, которые мы используем для доступа к объекту JavaScript, в этом поможет открытие файла superheroes.json на другой вкладке или в текстовом редакторе ,и обращаться к нему каждый раз, когда вам нужен JavaScript. Вы также можете обратиться к нашей статье JavaScript objectbasics чтобы получить дополнительную информацию о нотации точек и скобок.

Преобразование между объектами и текстом
----------------------------------------

Вышеприведённый пример был прост с точки зрения доступа к объекту JavaScript, потому что мы задали XHR-запрос для прямого преобразования ответа JSON в объект JavaScript, используя:

```js
request.responseType = "json";
```

Но иногда нам не так везёт - иногда мы получаем сырую строку JSON и нам нужно преобразовать её в объект самостоятельно. И когда мы хотим отправить объект JavaScript по сети, нам нужно преобразовать его в JSON (строку) перед отправкой. К счастью, эти две проблемы настолько распространены в веб-разработке, что встроенный объект JSON доступен в браузерах, которые содержат следующие два метода:

* `parse()`: принимает строку JSON в качестве параметра и возвращает соответствующий объект JavaScript.
* `stringify()`: принимает объект, как параметр и возвращает эквивалентную строковую JSON строку.

Вы можете увидеть первый метод в действии в нашем примере heroes-finished-json-parse.html (см. исходный код) - это то же самое, что и в примере, который мы создали ранее, за исключением того, что мы установили XHR для возврата сырого JSON текста, затем используется `parse()`, чтобы преобразовать его в фактический объект JavaScript. Ключевой фрагмент кода находится здесь:

```js
request.open("GET", requestURL);
request.responseType = "text"; // now we're getting a string!
request.send();

request.onload = function () {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
  showHeroes(superHeroes);
};
```

Как вы могли догадаться, `stringify()` работает обратным образом. Попробуйте ввести следующие строки в консоль JavaScript браузера один за другим, чтобы увидеть его в действии:

```js
var myJSON = { name: "Chris", age: "38" };
myJSON;
var myString = JSON.stringify(myJSON);
myString;
```

Здесь мы создаём объект JavaScript, затем проверяем, что он содержит, а затем преобразуем его в строку JSON, используя `stringify()` , сохраняя возвращаемое значение в новой переменной, а затем снова проверяем его.

Резюме
------

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

Смотрите также
--------------

* JSON object reference page
* XMLHttpRequest object reference page
* Using XMLHttpRequest
* HTTP request methods
* Official JSON web site with link to ECMA standard
* Назад
* Обзор: Objects
* Далее


Основы объектов в JavaScript - Изучение веб-разработки

Основы объектов в JavaScript
============================

* Обзор: Objects
* Далее

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

| Необходимые знания: | Элементарная компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Структурные элементы). |
| --- | --- |
| Цель: | Понимать основу теории перед началом объектно-ориентированного программирования, как это связано с JavaScript ("большинство сущностей являются объектами"), и как начать работу с объектами JavaScript. |

Основы объектов
---------------

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

Чтобы начать, скопируйте себе oojs.html файл. В нём содержится очень мало: `<script>` элемент для написания в нём исходного кода. Мы будем использовать это как основу для изучения основ синтаксиса объектов. Во время работы с этим примером у вас должна быть открытая консоль JavaScript инструментов разработчика, готовая к вводу некоторых команд.

Как и во многих случаях в JavaScript, создание объекта часто начинается с определения и инициализации переменной. Попробуйте ввести следующий код JavaScript в ваш файл, а затем сохраните файл и обновите страницу браузера:

```js
const person = {};
```

Если вы введёте `person` в текстовое JS консоль и нажмёте клавишу Enter, должен получиться следующий результат:

```js
Object { }
```

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

```js
const person = {
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  bio: function () {
    alert(
      this.name[0] +
        " " +
        this.name[1] +
        " is " +
        this.age +
        " years old. He likes " +
        this.interests[0] +
        " and " +
        this.interests[1] +
        ".",
    );
  },
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};
```

После сохранения и обновления, попробуйте ввести что-нибудь следующее в консоль JavaScript браузера:

```js
person.name;
person.name[0];
person.age;
person.interests[1];
person.bio();
person.greeting();
```

Как видите, наш объект содержит некоторые данные, а также несколько методов. У нас же с помощью простого синтаксиса есть доступ к ним.

**Примечание:** Если у вас возникли проблемы с применением файла в работе, попробуйте сравнить ваш код с нашей версией — см. oojs-finished.html (также see it running live). Одна из распространённых ошибок, когда вы начинаете с объектами ставить запятую в конце последнего члена — это приводит к ошибке.

Итак что здесь происходит? Объект состоит из нескольких элементов, каждый из которых имеет своё название (пример `name` и `age` выше), и значение (пример `['Bob', 'Smith']` и `32`). Каждая пара название/значение должны быть разделены запятой, а название и значение в каждом случае разделяются двоеточием. Синтаксис всегда следует этому образцу:

```js
const objectName = {
  member1Name: member1Value,
  member2Name: member2Value,
  member3Name: member3Value,
};
```

Значение члена объекта может быть чем угодно — в нашем объекте person есть строка, число, два массива, и две функции. Первые четыре элемента это элементы данных, относящиеся к **свойствам** объекта. Последние два элемента являются функциями, которые позволяют объекту что-то сделать с элементами данных, и называются **методами** объекта.

Такие объекты называются **литералами объекта** (**object literal**) — мы буквально вписали все содержимое объекта для его создания. Этот способ сильно отличается от объектов реализованных классами, которые мы рассмотрим позже.

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

Точечная запись (Dot notation)
------------------------------

Выше вы получили доступ к свойствам и методам используя **точечную запись (dot notation).** Имя объекта (person) действует как **пространство имён (namespace)** — оно должно быть введено первым, для того чтобы получить доступ ко всему что заключено (**encapsulated)** внутри объекта. Далее вы пишете точку, затем элемент, к которому хотите получить доступ — это может быть имя простого свойства, элемент массива, или вызов одного из методов объекта, например:

```js
person.age;
person.interests[1];
person.bio();
```

### Внутренние пространства имён (Sub-namespaces)

Можно даже сделать значением элемента объекта другой объект. Например, попробуйте изменить значение свойства name с такого

```js
name: ['Bob', 'Smith'],
```

на такое

```js
name : {
  first: 'Bob',
  last: 'Smith'
},
```

Здесь мы фактически создаём **внутреннее пространство имён (sub-namespace).** Это звучит сложно, но на самом деле это не так — для доступа к этим элементам вам нужно сделать один дополнительный шаг с ещё одной точкой. Попробуйте в консоли браузера следующее:

```js
person.name.first;
person.name.last;
```

**Важно**: На этом этапе вам также нужно будет пересмотреть код метода и изменить все экземпляры с

```js
name[0];
name[1];
```

на

```js
name.first;
name.last;
```

Иначе ваши методы больше не будут работать.

Скобочная запись (Bracket notation)
-----------------------------------

Существует другой способ получить свойства объекта — использовать скобочную запись (bracket notation). Вместо написания этого кода:

```js
person.age;
person.name.first;
```

Вы можете использовать следующий

```js
person["age"];
person["name"]["first"];
```

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

Запись элементов в объект
-------------------------

До сих пор мы рассматривали только возврат (или получение) элементов объекта — вы так же можете установить (обновить) значение элемента объекта просто объявив элемент, который вы хотите установить (используя точечную или скобочную запись), например:

```js
person.age = 45;
person["name"]["last"] = "Cratchit";
```

Попробуйте ввести эти строки, а затем снова верните элементы, чтобы увидеть, как они изменились

```js
person.age;
person["name"]["last"];
```

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

```js
person["eyes"] = "hazel";
person.farewell = function () {
  alert("Bye everybody!");
};
```

Теперь вы можете проверить ваши новые элементы:

```js
person["eyes"];
person.farewell();
```

Одним из полезных аспектов скобочной записи является то, что с её помощью можно динамически задавать не только значения элементов, но и их имена. Предположим, что мы хотим, чтобы пользователи могли хранить пользовательские типы данных, введя имя и значение элемента в два следующих поля? Мы могли бы получить эти значения следующим образом:

```js
let myDataName = nameInput.value;
let myDataValue = nameValue.value;
```

Затем мы можем добавить имя и значение этого нового элемента в объект `person` таким образом:

```js
person[myDataName] = myDataValue;
```

Чтобы проверить это, попробуйте добавить следующие строки в свой код, после закрывающей скобки объекта `person` :

```js
let myDataName = "height";
let myDataValue = "1.75m";
person[myDataName] = myDataValue;
```

Теперь попробуйте сохранить и обновить, затем введите следующее в консоль браузера:

```js
person.height;
```

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

Что такое "this"?
-----------------

Возможно, вы заметили что-то странное в наших методах. Посмотрите на этот пример:

```js
greeting: function() {
  alert('Hi! I\'m ' + this.name.first + '.');
}
```

Вы, вероятно, задаётесь вопросом, что такое "this"? Ключевое слово `this`, ссылается на текущий объект, внутри которого пишется код — поэтому в нашем случае `this` равен объекту `person`. Но почему просто не написать `person`? Как вы увидите в статье Object-oriented JavaScript for beginners (en-US) (Объектно-ориентированный JavaScript для начинающих), когда мы начинаем создавать конструкторы и т.д., `this` очень полезен — он всегда будет гарантировать, что используется верное значение, когда контекст элемента изменяется (например, два разных экземпляра объекта `person` могут иметь разные имена, но захотят использовать своё собственное имя при приветствии.

Давайте проиллюстрируем, что мы имеем в виду, с упрощённой парой объектов `person` :

```js
const person1 = {
  name: "Chris",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};

const person2 = {
  name: "Brian",
  greeting: function () {
    alert("Hi! I'm " + this.name + ".");
  },
};
```

В этом случае, `person1.greeting()` выведет "Hi! I'm Chris.". `person2.greeting()`, с другой стороны, выведет "Hi! I'm Brian.", хотя код метода одинаковый в обоих случаях. Как мы сказали ранее, `this` равен объекту, внутри которого находится код — это не очень полезно, когда вы пишите литералы объектов вручную, но оно действительно помогает, когда вы генерируете объекты динамически (например используя конструкторы). Это станет понятнее чуть позже.

Все это время вы использовали объекты
-------------------------------------

Пока вы проходили эти примеры, вы вероятно заметили, что точечная запись, которую вы использовали, выглядит очень знакомо. Это потому, что вы использовали её на протяжении всего курса! Каждый раз, когда мы работаем над примером, использующим встроенный API браузера или объект JavaScript, мы использовали объекты, потому что такие функции построены с использованием тех же структур объектов, которые мы здесь рассматривали, хотя и более сложные, чем наши собственные пользовательские примеры.

Поэтому, когда вы использовали строковые методы, такие как:

```js
myString.split(",");
```

Вы использовали метод доступный в экземпляре класса `String`. Каждый раз создавая строку в вашем коде, эта строка автоматически создаётся как экземпляр `String`, и поэтому имеет несколько общих методов/свойств, доступных на нем.

Когда вы обращались к объектной модели документа (DOM), используя следующие строки:

```js
const myDiv = document.createElement("div");
const myVideo = document.querySelector("video");
```

Вы использовали методы доступные в экземпляре класса `Document`. Для каждой загруженной веб-страницы создаётся экземпляр `Document`, называемый `document`, который представляет всю структуру страницы, её содержимое и другие функции, такие как URL-адрес. Опять же, это означает, что он имеет несколько общих методов/свойств, доступных на нем.

То же самое относится и к любому другому встроенному объекту/API, который вы использовали — `Array`, `Math`, и т. д.

Обратите внимание, что встроенные объекты/API не всегда создают экземпляры объектов автоматически. Как пример, Notifications API — который позволяет новым браузерам запускать системные уведомления, требует, чтобы вы создавали новый экземпляр объекта с помощью конструктора для каждого уведомления, которое вы хотите запустить. Попробуйте ввести следующее в консоль JavaScript:

```js
const myNotification = new Notification("Hello!");
```

Опять же, мы рассмотрим конструкторы в следующей статье.

**Примечание:** Полезно подумать о том, как объекты взаимодействуют посредством передачи сообщений - когда объекту требуется другой объект для выполнения какого-либо действия, он часто отправляет сообщение другому объекту через один из его методов и ждёт ответа, который мы знаем как возвращаемое (return) значение.

Резюме
------

Поздравляем, вы достигли конца нашей первой статьи о объектах JS, теперь у вас должно быть хорошее представление о том, как работать с объектами в JavaScript - в том числе создавать свои собственные простые объекты. Вы также должны понимать, что объекты очень полезны в качестве структур для хранения связанных данных и функциональности - если бы мы пытались отслеживать все свойства и методы в нашем объекте `person` как отдельные переменные и функции, это было неэффективно, и мы бы рисковали столкнуться с другими переменными и функциями с такими же именами. Объекты позволяют нам безопасно хранить информацию в своём собственном блоке, вне опасности.

В следующей статье мы начнём рассматривать теорию объектно-ориентированного программирования (ООП) и как эти техники могут быть использованы в JavaScript

* Обзор: Objects
* Далее


Прототипы объектов - Изучение веб-разработки

Прототипы объектов
==================

* Назад  (en-US)
* Обзор: Objects
* Далее

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

| Необходимые знания: | Базовая компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Строительные блоки) и основы OOJS (см. Введение в объекты). |
| --- | --- |
| Цель: | Понять прототипы объектов JavaScript, как работают прототипные цепочки и как добавить новые методы в `prototype` свойство. |

Язык основанный на прототипах?
------------------------------

JavaScript часто описывают как язык **прототипного наследования** — каждый объект, имеет **объект-прототип**, который выступает как шаблон, от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется **цепочкой прототипов** и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.

Точнее, свойства и методы определяются в свойстве `prototype` функции-конструктора объектов, а не в самих объектах.

В JavaScript создаётся связь между экземпляром объекта и его прототипом (свойство `__proto__`, которое является производным от свойства `prototype` конструктора), а свойства и методы обнаруживаются при переходе по цепочке прототипов.

**Примечание:** Важно понимать, что существует различие между прототипом объекта (который доступен через `Object.getPrototypeOf(obj)` или через устаревшее свойство `__proto__`) и свойством `prototype` в функциях-конструкторах. Первое свойство является свойством каждого экземпляра, а второе - свойством конструктора. То есть `Object.getPrototypeOf(new Foobar())` относится к тому же объекту, что и `Foobar.prototype`.

Давайте посмотрим на пример, чтобы стало понятнее.

Понимание прототипа объектов
----------------------------

Вернёмся к примеру, когда мы закончили писать наш конструктор `Person()`- загрузите пример в свой браузер. Если у вас ещё нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).

В этом примере мы определили конструктору функцию, например:

```js
function Person(first, last, age, gender, interests) {
  // Определения методов и свойств
  this.name = {
    first: first,
    last: last,
  };
  this.age = age;
  this.gender = gender;
  //...см. Введение в объекты для полного определения
}
```

Затем мы создаём экземпляр объекта следующим образом:

```js
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
```

Если вы наберёте «`person1.`» в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:

![](/ru/docs/Learn/JavaScript/Objects/Object_prototypes/object-available-members.png)

В этом списке вы увидите элементы, определённые в конструкторе person 1 — Person() — `name`, `age`, `gender`, `interests`, `bio`, и `greeting`. Однако вы также увидите некоторые другие элементы — `watch`, `valueOf` и т. д. — они определены в объекте прототипа Person (), который является `Object`.

![](/ru/docs/Learn/JavaScript/Objects/Object_prototypes/mdn-graphics-person-person-object-2.png)

Итак, что произойдёт, если вы вызываете метод в `person1`, который фактически определён в `Object`? Например:

```js
person1.valueOf();
```

Этот метод — `Object.valueOf()`наследуется `person1`, потому что его конструктором является `Person()`, а прототипом `Person()` является `Object()`. `valueOf()` возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:

* Сначала браузер проверяет, имеет ли объект `person1` доступный в нем метод `valueOf()`, как определено в его конструкторе `Person()`.
* Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта (`Object()`) конструктора `Person()` доступный в нем метод `valueOf()`. Так оно и есть, поэтому он вызывается, и все хорошо!

**Примечание:** Мы хотим повторить, что методы и свойства **не** копируются из одного объекта в другой в цепочке прототипов - к ним обращаются, поднимаясь по цепочке, как описано выше.

**Примечание:** Официально нет способа получить доступ к объекту прототипа объекта напрямую - «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом `[[prototype]]` в спецификации для языка JavaScript ( см. ECMAScript). Однако у большинства современных браузеров есть свойство, доступное для них под названием `__proto__` (это 2 подчёркивания с обеих сторон), который содержит объект-прототип объекта-конструктора. Например, попробуйте `person1.__proto__` и `person1.__proto__.__proto__`, чтобы увидеть, как выглядит цепочка в коде!

С ECMAScript 2015 вы можете косвенно обращаться к объекту прототипа объекта `Object.getPrototypeOf (obj)`.

Свойство prototype: Где определены унаследованные экземпляры
------------------------------------------------------------

Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу со ссылкой `Object`, вы увидите в левой части большое количество свойств и методов - это намного больше, чем количество унаследованных членов, доступных для объекта `person1`. Некоторые из них унаследованы, а некоторые нет - почему это?

Как упоминалось выше, наследованные свойства это те, что определены в свойстве `prototype` (вы можете называть это подпространством имён), то есть те, которые начинаются с `Object.prototype.`, а не те, которые начинаются с простого `Object`. Значение свойства `prototype` - это объект, который в основном представляет собой контейнер для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.

Таким образом `Object.prototype.watch()`, `Object.prototype.valueOf()` и т. д. доступны для любых типов объектов, которые наследуются от `Object.prototype`, включая новые экземпляры объектов, созданные из конструктора `Person()` .

`Object.is()`, `Object.keys()` и другие члены, не определённые в контейнере `prototype`, не наследуются экземплярами объектов или типами объектов, которые наследуются от `Object.prototype`. Это методы / свойства, доступные только в конструкторе `Object()`.

**Примечание:** Это кажется странным - как у вас есть метод, определённый для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта - см. Ссылку на конструктор `Function()`, если вы нам не верите.

1. Вы можете проверить существующие свойства прототипа для себя - вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:

```js
Person.prototype;
```

2. Результат покажет вам не много, ведь мы ничего не определили в прототипе нашего конструктора! По умолчанию `prototype` конструктора всегда пуст. Теперь попробуйте следующее:

```js
Object.prototype;
```

Вы увидите большое количество методов, определённых для свойства `prototype` `Object`'а , которые затем доступны для объектов, которые наследуются от `Object`, как показано выше.

Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript - попробуйте найти методы и свойства, определённые на прототипе глобальных объектов `String`, `Date`, `Number` и `Array`, например. Все они имеют несколько элементов, определённых на их прототипе, поэтому, например, когда вы создаёте строку, вот так:

```js
var myString = "This is my string.";
```

В `myString` сразу есть множество полезных методов, таких как `split()`, `indexOf()`, `replace()` и т. д.

**Предупреждение:** **Важно**: Свойство `prototype` является одной из наиболее противоречивых названий частей JavaScript - вы можете подумать, что `this` указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ `__proto__`, помните ?). `prototype` вместо этого - свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.

Снова create()
--------------

Ранее мы показали, как метод `Object.create()` может использоваться для создания нового экземпляра объекта.

1. Например, попробуйте это в консоли JavaScript предыдущего примера:

```js
var person2 = Object.create(person1);
```

2. На самом деле `create()` создаёт новый объект из указанного объекта-прототипа. Здесь `person2` создаётся с помощью `person1` в качестве объекта-прототипа. Это можно проверить, введя в консоли следующее:

```js
person2.__proto__;
```

Это вернёт объект person1.

Свойство constructor
--------------------

Каждая функция-конструктор имеет свойство `prototype`, значением которого является объект, содержащий свойство `constructor`. Это свойство `constructor` указывает на исходную функцию-конструктор. Как вы увидите в следующем разделе, свойства, определённые в свойстве `Person.prototype` (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор `Person()`. Следовательно, свойство конструктора также доступно для объектов `person1` и `person2`.

1. Например, попробуйте эти команды в консоли:

```js
person1.constructor;
person2.constructor;
```

Они должны возвращать конструктор `Person()`, поскольку он содержит исходное определение этих экземпляров.
 Хитрый трюк заключается в том, что вы можете поместить круглые скобки в конец свойства `constructor` (содержащие любые требуемые параметры) для создания другого экземпляра объекта из этого конструктора. Конструктор - это функция в конце концов, поэтому её можно вызвать с помощью круглых скобок; вам просто нужно включить ключевое слово `new`, чтобы указать, что вы хотите использовать эту функцию в качестве конструктора.
2. Попробуйте это в консоли:

```js
var person3 = new person1.constructor("Karen", "Stephenson", 26, "female", [
  "playing drums",
  "mountain climbing",
]);
```

3. Теперь попробуйте получить доступ к функциям вашего нового объекта, например:

```js
person3.name.first;
person3.age;
person3.bio();
```

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

Свойство `constructor` имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:

```js
instanceName.constructor.name;
```

Например, попробуйте это:

```js
person1.constructor.name;
```

**Примечание:** Значение `constructor.name` может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. д.), Поэтому для более сложных примеров вы захотите использовать оператор `instanceof`.

Изменение прототипов
--------------------

Давайте рассмотрим пример изменения свойства `prototype` функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.

1. Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство `prototype` конструктора:

```js
Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

2. Сохраните код и загрузите страницу в браузере и попробуйте ввести следующее в текстовый ввод:

```js
person1.farewell();
```

Должно появиться всплывающее окно, с именем пользователя, определённым в конструкторе. Это действительно полезно, но ещё более полезно то, что вся цепочка наследования обновляется динамически, автоматически делая этот новый метод доступным для всех экземпляров объектов, полученных из конструктора.

Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаём экземпляр объекта из конструктора, *затем* добавляем новый метод к прототипу конструктора:

```js
function Person(first, last, age, gender, interests) {
  // определения свойств и методов
}

var person1 = new Person("Tammi", "Smith", 32, "neutral", [
  "music",
  "skiing",
  "kickboxing",
]);

Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};
```

Но метод `farewell()` *по-прежнему* доступен в экземпляре объекта `person1` - его элементы были автоматически обновлены, чтобы включить недавно определённый метод `farewell()`.

**Примечание:** Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).

Вы редко увидите свойства, определённые в свойстве `prototype`, потому что они не очень гибки при таком определении. Например, вы можете добавить свойство следующим образом:

```js
Person.prototype.fullName = "Bob Smith";
```

Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав `fullName` из `name.first` и `name.last`:

```js
Person.prototype.fullName = this.name.first + " " + this.name.last;
```

Однако это не работает, поскольку в этом случае `this` будет ссылаться на глобальную область, а не на область функции. Вызов этого свойства вернёт `undefined undefined`. Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.

Фактически, довольно распространённый шаблон для большего количества определений объектов - это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:

```js
// Определение конструктора и его свойств

function Test(a, b, c, d) {
  // определение свойств...
}

// Определение первого метода

Test.prototype.x = function() { ... };

// Определение второго метода

Test.prototype.y = function() { ... };

//...и так далее
```

Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.

Резюме
------

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

В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.

* Назад  (en-US)
* Обзор: Objects
* Далее


Практика построения объектов - Изучение веб-разработки

Практика построения объектов
============================

* Назад
* Обзор: Objects
* Далее

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

| Необходимые знания: | Базовая компьютерная грамотность, базовые знания HTML и CSS, знакомство с основами JavaScript (see First steps and Building blocks) и основами OOJS (см. Introduction to objects (en-US)). |
| --- | --- |
| Цель: | Получение некоторой практики в использовании объектов и объектно-ориентированных методов в реальном мире. |

Давайте подбросим несколько мячей
---------------------------------

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

![](/ru/docs/Learn/JavaScript/Objects/Object_building_practice/bouncing-balls.png)

В этом примере будет использоваться Canvas API (en-US) для рисования шаров на экране и API requestAnimationFrame для анимации всего экрана - вам не нужно иметь никаких предыдущих знаний об этих API, и мы надеемся, что к тому моменту, когда вы закончите эту статью, вам будет интересно изучить их больше. По пути мы воспользуемся некоторыми изящными объектами и покажем вам пару хороших приёмов, таких как отскоки шаров от стен и проверка того, попали ли они друг в друга (иначе известный как **обнаружение столкновения**).

Начало работы
-------------

Для начала создайте локальные копии наших файлов`index.html`, `style.css` и `main.js`. Они содержат следующее:

1. Очень простой HTML-документ, содержащий элемент `<h1>` (en-US), элемент `<canvas>` для рисования наших шаров и элементы для применения нашего CSS и JavaScript в нашем HTML.
2. Некоторые очень простые стили, которые в основном служат для стилизации и позиционирования `<h1>`, и избавляются от любых полос прокрутки или отступы по краю страницы (так что это выглядит красиво и аккуратно).
3. Некоторые JavaScript, которые служат для настройки элемента `<canvas>` и предоставляют общую функцию, которую мы собираемся использовать.

Первая часть скрипта выглядит так:

```js
var canvas = document.querySelector("canvas");

var ctx = canvas.getContext("2d");

var width = (canvas.width = window.innerWidth);
var height = (canvas.height = window.innerHeight);
```

Этот скрипт получает ссылку на элемент `<canvas>`, а затем вызывает метод `getContext()`, чтобы дать нам контекст, по которому мы можем начать рисовать. Результирующая переменная (ctx) - это объект, который непосредственно представляет область рисования холста и позволяет рисовать на ней 2D-фигуры.

Затем мы устанавливаем переменные, называемые `width` и `height`, а также ширину и высоту элемента canvas (представленные свойствами `canvas.width` и `canvas.height`), чтобы равняться ширине и высоте окна просмотра браузера (область, на которой отображается веб-страница - это можно получить из свойств `Window.innerWidth` (en-US) и `Window.innerHeight`).

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

Последний бит исходного скрипта выглядит следующим образом:

```js
function random(min, max) {
  var num = Math.floor(Math.random() \* (max - min + 1)) + min;
  return num;
}
```

Эта функция принимает два числа в качестве аргументов и возвращает случайное число в диапазоне между ними.

Моделирование мяча в нашей программе
------------------------------------

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

```js
function Ball(x, y, velX, velY, color, size) {
  this.x = x;
  this.y = y;
  this.velX = velX;
  this.velY = velY;
  this.color = color;
  this.size = size;
}
```

Здесь мы включаем некоторые параметры, которые определяют свойства, которым должен соответствовать каждый шар в нашей программе:

* `x` и `y` координаты - горизонтальные и вертикальные координаты, где мяч будет запускаться на экране. Координаты могут находиться в диапазоне от 0 (верхний левый угол) до ширины и высоты окна просмотра браузера (нижний правый угол).
* горизонтальная и вертикальная скорость (`velX` и `velY`) - каждому шару задана горизонтальная и вертикальная скорость; в реальном выражении эти значения будут регулярно добавляться к значениям координат `x`/`y`, когда мы начнём анимировать шары, чтобы их перемещать на каждом кадре.
* `color` - каждый мяч получает цвет.
* `size` - каждый мяч получает размер - это будет его радиус в пикселях.

Этим мы сортируем свойства, но что насчёт методов? Мы хотим заставить эти шары на самом деле сделать что-то в нашей программе.

### Рисование шара

Сначала добавьте следующий метод `draw()` к `Ball()`'s `prototype`:

```js
Ball.prototype.draw = function () {
  ctx.beginPath();
  ctx.fillStyle = this.color;
  ctx.arc(this.x, this.y, this.size, 0, 2 \* Math.PI);
  ctx.fill();
};
```

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

* Во-первых, мы используем `beginPath()`, чтобы указать, что мы хотим нарисовать фигуру на бумаге.
* Затем мы используем `fillStyle` для определения того, какой цвет нам нужен, - мы устанавливаем его в свойство цвета нашего шара.
* Затем мы используем метод `arc()` для отслеживания формы дуги на бумаге. Его параметры:
    + Положение `x` и `y` центра дуги - мы указываем свойства `x` и `y` нашего шара.
    + Радиус нашей дуги - мы указываем свойство `size` шарика.
    + Последние два параметра определяют начальное и конечное число градусов по кругу, по которому проходит дуга. Здесь мы указываем 0 градусов и `2 * PI`, что эквивалентно 360 градусам в радианах (досадно, вы должны указать это в радианах). Это даёт нам полный круг. Если вы указали только `1 * PI`, вы получите полукруг (180 градусов).
* В последнем случае мы используем метод `fill()`, который в основном утверждает: «Закончите рисование пути, начатого с `beginPath()`, и заполните область, которую он занимает с цветом, указанным ранее в `fillStyle`».

Теперь вы можете начать тестирование своего объекта..

1. Сохраните код и загрузите HTML-файл в браузер.
2. Откройте консоль JavaScript браузера, а затем обновите страницу, чтобы размер холста изменился в соответствии с новой шириной и высотой окна просмотра браузера после открытия консоли.
3. Чтобы создать новый экземпляр шара, введите следующее:

```js
var testBall = new Ball(50, 100, 4, 4, "blue", 10);
```

4. Попробуйте вызвать его свойства и методы:

```js
testBall.x;
testBall.size;
testBall.color;
testBall.draw();
```

5. После введения последней строки, вы должны увидеть, как мяч нарисовался где-то на вашем холсте.
### Обновление данных мяча

Мы можем нарисовать мяч в нужном положении, но чтобы начать движение мяча, нам нужна функция обновления. Добавьте следующий код внизу вашего файла JavaScript, чтобы добавить метод `update()` к `Ball()`'s `prototype`:

```js
Ball.prototype.update = function () {
  if (this.x + this.size >= width) {
    this.velX = -this.velX;
  }

  if (this.x - this.size <= 0) {
    this.velX = -this.velX;
  }

  if (this.y + this.size >= height) {
    this.velY = -this.velY;
  }

  if (this.y - this.size <= 0) {
    this.velY = -this.velY;
  }

  this.x += this.velX;
  this.y += this.velY;
};
```

Первые четыре части функции проверяют, достиг ли шар края холста. Если это так, мы изменяем полярность соответствующей скорости, чтобы заставить шар двигаться в противоположном направлении. Так, например, если мяч двигался вверх (положительный `velY`), то вертикальная скорость изменяется так, что он начинает двигаться вниз (отрицательная величина `velY`).

В этих четырёх случаях мы:

* Проверяем больше ли координата `x`, чем ширина холста (мяч уходит с правого края).
* Проверяем будет ли координата `x` меньше 0 (мяч уходит с левого края).
* Проверяем будет ли координата `y` больше высоты холста (мяч уходит с нижнего края).
* Проверяем будет ли координата `y` меньше 0 (мяч уходит с верхнего края).

В каждом случае мы включаем `size` шарика в расчёт, потому что координаты `x`/`y` находятся в центре шара, но мы хотим, чтобы край шара отскакивал от периметра - мы не хотим, чтобы мяч на половину заходил за границу экрана прежде чем он начнёт возвращаться назад.

Последние две строки добавляют значение `velX` к координате `x`, а значение `velY` - координате `y` - шар фактически перемещается при каждом вызове этого метода.

На сейчас этого достаточно, давайте продолжим анимацию!

Анимация мяча
-------------

Теперь давайте приступать к веселью! Сейчас мы начнём добавлять шары к холсту и анимировать их.

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

```js
var balls = [];
```

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

```js
function loop() {
  ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
  ctx.fillRect(0, 0, width, height);

  while (balls.length < 25) {
    var ball = new Ball(
      random(0, width),
      random(0, height),
      random(-7, 7),
      random(-7, 7),
      "rgb(" +
        random(0, 255) +
        "," +
        random(0, 255) +
        "," +
        random(0, 255) +
        ")",
      random(10, 20),
    );
    balls.push(ball);
  }

  for (var i = 0; i < balls.length; i++) {
    balls[i].draw();
    balls[i].update();
  }

  requestAnimationFrame(loop);
}
```

Наша функция `loop()` выполняет следующие действия:
    * Устанавливает цвет заливки на полупрозрачный чёрный, затем рисует прямоугольник цвета по всей ширине и высоте холста, используя `fillRect()` (четыре параметра обеспечивают начальную координату, а ширину и высоту для рисованного прямоугольника ). Это позволяет скрыть рисунок предыдущего кадра до того, как будет нарисован следующий. Если вы этого не сделаете, вы увидите, как длинные змеи пробираются вокруг холста, а не шары! Цвет заливки устанавливается на полупрозрачный, `rgba(0,0,0,0,25)`, чтобы позволить нескольким кадрам слегка просвечивать, создавая маленькие тропы за шариками по мере их перемещения. Если вы изменили 0.25 на 1, вы больше не увидите их. Попробуйте изменить это число, чтобы увидеть эффект, который он имеет.
    * Создаёт новый экземпляр нашего `Ball()`, используя случайные значения, сгенерированные с помощью нашей функции `random()`, затем `push()` на конец нашего массива шаров, но только в том случае, когда количество шаров в массиве меньше 25. Итак когда у нас есть 25 мячей на экране, больше не появляется шаров. Вы можете попробовать изменить число в `balls.length < 25`, чтобы получить больше или меньше шаров на экране. В зависимости от того, сколько вычислительной мощности имеет ваш компьютер / браузер, если указать несколько тысяч шаров, это может довольно существенно повлиять на производительность анимации.
    * перебирает все шары в массиве `balls` и запускает каждую функцию `draw()` и `update()` для рисования каждого из них на экране, а затем выполняет необходимые обновления по положению и скорости во времени для следующего кадра.
    * Выполняет функцию снова с помощью метода `requestAnimationFrame()` - когда этот метод постоянно запускается и передаётся одно и то же имя функции, он будет запускать эту функцию определённое количество раз в секунду для создания плавной анимации. Обычно это делается рекурсивно - это означает, что функция вызывает себя каждый раз, когда она запускается, поэтому она будет работать снова и снова.
3. И последнее, но не менее важное: добавьте следующую строку в конец вашего кода - нам нужно вызвать функцию один раз, чтобы начать анимацию.

```js
loop();
```

Вот и все для основы - попробуйте сохранить и освежить, чтобы проверить свои прыгающие шары!

Добавление обнаружения столкновений
-----------------------------------

Теперь немного поиграем, давайте добавим в нашу программу обнаружение конфликтов, поэтому наши мячи узнают, когда они ударят по другому шару.

1. Прежде всего, добавьте следующее определение метода ниже, где вы определили метод `update()` (т.е. блок `Ball.prototype.update`).

```js
Ball.prototype.collisionDetect = function () {
  for (var j = 0; j < balls.length; j++) {
    if (!(this === balls[j])) {
      var dx = this.x - balls[j].x;
      var dy = this.y - balls[j].y;
      var distance = Math.sqrt(dx \* dx + dy \* dy);

      if (distance < this.size + balls[j].size) {
        balls[j].color = this.color =
          "rgb(" +
          random(0, 255) +
          "," +
          random(0, 255) +
          "," +
          random(0, 255) +
          ")";
      }
    }
  }
};
```

Этот метод немного сложный, поэтому не беспокойтесь, если вы не понимаете, как именно это работает. Ниже приводится объяснение:
    * Для каждого шара нам нужно проверить каждый другой шар, чтобы увидеть, столкнулся ли он с текущим мячом. Чтобы сделать это, мы открываем ещё один цикл `for` через все шары в массиве `balls[]`.
    * Сразу же в нашем цикле for мы используем оператор `if`, чтобы проверить, проходит ли текущий шарик, тот же самый шар, что и тот, который мы сейчас проверяем. Мы не хотим проверять, что мяч столкнулся с самим собой! Для этого мы проверяем, является ли текущий мяч (т.е. мяч, метод которого вызван методом collisionDetect) такой же, как шар петли (т.е. шар, на который ссылается текущая итерация цикла for в collisionDetect метод). Затем мы используем `!` чтобы отменить проверку, чтобы код внутри оператора if выполнялся только в том случае, если они **не** совпадают.
    * Затем мы используем общий алгоритм для проверки столкновения двух окружностей. Мы в основном проверяем, перекрывается ли какая-либо из областей круга. Это объясняется далее 2D collision detection (en-US).
    * Если обнаружено столкновение, выполняется код внутри внутреннего оператора `if`. В этом случае мы просто устанавливаем свойство `color` обоих кругов на новый случайный цвет. Мы могли бы сделать что-то гораздо более сложное, например, заставить шары отскакивать друг от друга реалистично, но это было бы гораздо сложнее реализовать. Для такого моделирования физики разработчики склонны использовать игры или библиотеку физики, такие как PhysicsJS, matter.js, Phaser и т.д.
2. Вы также должны вызвать этот метод в каждом кадре анимации. Добавьте следующий код после строки `balls[i].update();`

```js
balls[i].collisionDetect();
```

3. Сохраните и обновите демо снова, и вы увидите, как ваши мячи меняют цвет, когда они сталкиваются!

**Примечание:** . Если вам не удаётся заставить этот пример работать, попробуйте сравнить код JavaScript с нашей готовой версией (также смотрите, как он работает в прямом эфире).

Резюме
------

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

Вот и все для предметных статей - все, что осталось сейчас, - это проверить свои навыки в оценке объекта.

Смотрите также
--------------

* Canvas tutorial — руководство для новичков по использованию 2D-холста.
* requestAnimationFrame()
* 2D collision detection (en-US)
* 3D collision detection (en-US)
* 2D breakout game using pure JavaScript — a great beginner's tutorial showing how to build a 2D game.
* 2D breakout game using Phaser — explains the basics of building a 2D game using a JavaScript game library.
* Назад
* Обзор: Objects
* Далее