Версии JavaScript: как JavaScript изменился с годами

Версии JavaScript Программирование и разработка

JavaScript — один из самых популярных и распространенных языков программирования в мире. С момента своего создания в 1995 году язык, который в конечном итоге стал известен как JavaScript, прошел несколько итераций и версий.

JavaScript был изобретен Бренданом Эйхом и в 1997 году стал стандартом ECMA. ECMAScript — официальное название языка. Версии ECMAScript включают ES1, ES2, ES3, ES5 и ES6.

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

Веб-сайты до JavaScript

Веб-страницы до появления JavaScript были очень статичными. Списки, даты и ссылки были жестко закодированы в вашем HTML, а любые динамические функции были закодированы в заголовке HTML-документа в виде заголовка.

Одним из наиболее известных примеров дизайна веб-страниц без использования JavaScript, который все еще существует, является противотуманная камера в Сан-Франциско:

Противотуманная камера Сан-Франциско была создана почти 30 лет назад и известна как одна из старейших веб-камер в мире. Это самый ранний способ увидеть «живую» картинку в Интернете.

Время изменилось, а сайт — нет. Он по-прежнему использовал статический HTML и CSS, созданный в 1990-х годах. Страница обновляется каждые 20 секунд с использованием информации в заголовке документа.

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

Другой пример того, как были спроектированы веб-страницы до JavaScript, — это страница Dole / Kemp ’96, построенная в конце девяностых по президентскому предложению сенатора Боба Доула.

Этот сайт по-прежнему является статическим, но для перехода от страницы к странице он использует HTML-маршрутизацию. На этом сайте все было жестко запрограммировано. Инженеры Netscape Communicator увидели эту проблему и решили создать язык сценариев, который позволял бы анимацию, построение форм и более динамическое взаимодействие.

Начало JavaScript

Первая итерация JavaScript на самом деле вообще не называлась JavaScript. Он назывался Мокко . Этот язык был создан как язык более высокого уровня как для дизайнеров, так и для непрограммистов.

Когда Mocha поставлялся с Netscape Navigator 2.0, его производственное название стало LiveScript, а затем, в более поздних версиях, JavaScript.

 

Первая публичная версия JavaScript была интегрирована в Netscape Navigator 2.0 (1995 г.).

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

По юридическим причинам Microsoft создала собственную версию JavaScript под названием JScript. Основная задача этих «диалектов» заключалась в том, чтобы повысить удобство работы и взаимодействие пользователей с веб-сайтами.

Сначала Netscape выиграла эти войны, но с созданием JScript Internet Explorer от Microsoft увеличил долю своих браузеров.

Это сделало стандартизацию действительно сложной. JavaScript продвинулся вперед в войнах языков сценариев из-за его сходства с синтаксисом Java. По мере того, как Java становилась все более популярной, JavaScript также становился все более популярным.

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

JavaScript — это язык сценариев, который используется в браузере в рабочей среде и используется в Node.js вне браузера.

Версии JavaScript

Версия Официальное имя Описание
ES1 ECMAScript 1 (1997) Первое издание
ES2 ECMAScript 2 (1998) Редакционные изменения
ES3 ECMAScript 3 (1999) Добавлены регулярные выражения и попробуйте / поймайте
ES4 ECMAScript 4 Не издан
ES5 ECMAScript 5 (2009) Добавлен «строгий режим», поддержка JSON, методы итерации String.trim (), Array.isArray () и Array.
ES6 ECMAScript 2015 Добавлены let и const, значения параметров по умолчанию, Array.find () и Array.findIndex ()
ES6 ECMAScript 2016 Добавлен экспоненциальный оператор & Array.prototype.includes
ES6 ECMAScript 2017 Добавлены строковые отступы, Object.entries, Object.values, асинхронные функции и разделяемая память.
ES6 ECMAScript 2018 Добавлены свойства отдыха / распространения, асинхронная итерация, Promise.finally () и RegExp.

ECMAScript

В 1997 году Netscape Communicator представила документы в ECMA International, компанию, которая занимается стандартизацией информационных и коммуникационных систем.

ECMA International использовала JavaScript от Netscape и JScript от Microsoft для создания стандартизации под названием ECMAScript, спецификации языка, на которой основаны оба языка. ECMA нельзя было назвать JavaScript, потому что JavaScript был товарным знаком Sun Microsystems (который позже стал Oracle).

ECMAScript 1-4

ECMAScript 1 (ES1) был выпущен в 1997 году, а ES2 — в следующем году. Между двумя версиями мало что изменилось.

ES3

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

  • Строгое равенство: начиная с ES3, оператор строгого равенства ( ===) стал опцией для использования в дополнение к оператору равенства ( ==). Разница между ними заключается в сравнении типа и номера. При строгом равенстве значения одного и того же, но другого типа считаются неравными.
const compareTypeAndValue = (num, str) => {
 return num === str;
}
 
console.log(compareTypeAndValue(8, '8')); //false
console.log(compareTypeAndValue(8, 8)); //true
  • Регулярные выражения: в ES3 стали доступны два типа регулярных выражений: литерал и конструктор.
  • Литеральные выражения: буквальные регулярные выражения выражаются между двумя обратными косыми чертами. Фактическое выражение находится между косой чертой и глобальным игнорированием регистра, а многострочные флаги могут быть включены или выключены после последней обратной косой черты.
/[^abc]/gim
  • Выражения конструктора: регулярные выражения конструктора — это те выражения, которые создаются как экземпляр объекта RegExp. Фактическое регулярное выражение — это первый аргумент, который передается в конструктор RegExp. Вторые, если необходимо, — это флаги, которые вы хотели бы использовать.
const regex = new RegExp([^abc], gim);
  • Оператор Switch: Оператор switch — это оператор потока управления, который в основном объединяет множество условий if без необходимости использовать операторы else if. Оператор switch использует параметр и сравнивает этот параметр с каждым из операторов case. Если этот параметр соответствует регистру, он выполняет логику в этом блоке.
const fizzBuzz = (num) => {
    switch(num) {
     case 1:
       console.log(num);
       break;
     case 2:
       console.log(num);
       break;
     case 3:
       console.log(«fizz»);
       break;
     case 4:
       console.log(num);
       break;
     case 5:
       console.log(«buzz»);
       break;
     case 6:
       console.log(«fizz»);
       break;
     case 7:
       console.log(num);
       break;
     case 8:
       console.log(num);
       break;
     case 9:
       console.log(«fizz»);
       break;
     case 10:
       console.log(num);
       break;
   }
}
console.log(fizzBuzz(3))
  • Обработка try / Catch: обработчик try / catch выдаст ошибку, если блок try по какой-либо причине выйдет из строя. Ниже попытка не удалась, потому что obj никогда не был определен. Выполняется блок catch и генерируется новое исключение объекта Error.
const isValidKey = (val1) => {
try {
  let obj;
  return obj.hasOwnProperty(val1);
} catch (err) {
    throw new Error(«not valid key»);
}
}
console.log(isValidKey(«»))

ES3 был последним крупным обновлением спецификации ECMAScript за почти десять лет, до 2009 года с ES5.

ES4

TC39 — это бесплатная рабочая группа в ECMA International, основной задачей которой является стандартизация ECMAScript. Когда пришло время обновить и выпустить стандарт для ES4, рабочая группа действительно не могла согласовать спецификацию. В результате ES4 как версия была потрепана, но так и не была выпущена в качестве реального стандарта.

Подробно об обновлениях ECMAScript 5

ES5 и ES6 — последние выпущенные спецификации, в которые внесено наибольшее количество изменений.

Через десять лет после выпуска ES3, в 2009 году, была выпущена новая версия ECMAScript. Этот стандарт был самым большим изменением в JavaScript с момента его основания. Некоторые из новых функций включают:

«Использовать строго»

До ES5 разрешалось использовать необъявленные переменные (те переменные, которые не использовали ключевое слово var при первоначальном введении). Когда включена функция «строгого использования», выдается справочная ошибка.

"use strict"
 
= 5; // ReferenceError: x is not defined

Новые методы массива

В ES5 появилось несколько новых методов работы с массивами, которые значительно упростили жизнь при работе с массивами. Новые методы массива показаны здесь в алфавитном порядке:

every()

Метод every()массива проверяет, удовлетворяет ли каждый отдельный элемент массива заданному вами условию.

var arr = [6, 4, 5, 6, 7, 7];
 
arr.every(function(element) {
 return element % 2 === ; //checks to see if even
}); // false

filter()

Думайте о методе фильтрации как о цикле for, в котором есть оператор if. Если элемент проходит проверку, вы помещаете этот элемент в новый массив. Так filter()работает под капотом.

Например map(), другой метод массива, упомянутый в этом разделе, filter()возвращает новый массив со значениями, прошедшими проверку.

var arr = [6, 4, 5, 6, 7, 7];
 
arr.filter(function(element) {
 return element/2 > 3;
})

forEach()

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

var arr = [6, 4, 5, 6, 7, 7];
 
arr.forEach(function(element) {
 console.log(element * 2);
})

indexOf() and lastIndexOf()

Если вам нужно найти определенный элемент в массиве, вы можете сделать это с помощью indexOf()и lastIndexOf(). indexOf()возвращает первый индекс параметра поиска, если он найден, в противном случае возвращает −1.

В lastIndexOf()он дает нам последний индекс элемента поиска в массиве. Опять же, если не найден, вернется −1.

var arr = [6, 4, 5, 6, 7, 7];
 
console.log(arr.indexOf(4)); // 1
console.log(arr.indexOf(2)); // -1
console.log(arr.indexOf(7)); // 4
 
console.log(arr.lastIndexOf(7)); // 5

isArray()

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

var arr = [6, 4, 5, 6, 7, 7];
 
var str = "Hello Educative.io";
 
console.log(Array.isArray(arr));
console.log(Array.isArray(str));

map()

map()Метод очень похож на forEach()метод, за исключением того, что она возвращает новый массив. Это позволяет манипулировать данными без ущерба для исходного массива.

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

var arr = [6, 4, 5, 6, 7, 7];
 
arr.map(function(element) {
 return element * 2;
})

reduce() and reduceRight()

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

reduceRight()Метод так же , как за reduce()исключением того, что он перебирает справа налево , а не слева направо.

var arr = [6, 4, 5, 6, 7, 7];
 
var reduced = arr.reduce(function(curr, next) {
 return curr + next;
}, );
 
var reducedRight = arr.reduceRight(function(curr, next) {
 return curr + next;
}, )
 
console.log(reduced);
 
console.log(reducedRight);

some()

some()Метод почти так же , как every()метод, за исключением того, что он проверяет, по меньшей мере , один элемент удовлетворяет условию вы установили для него.

var arr = [6, 4, 5, 6, 7, 7];
 
arr.some(function(element) {
 return element % 2 === ; //checks to see if even
}); //true

JSON

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

Когда мы передаем данные из одного приложения, они должны быть в виде строки. Мы используем JSON.stringify()для преобразования объектов JavaScript в строки.

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

var arr = [6, 4, 5, 6, 7, 7];
var obj = {
 author: «Christina Kopecky»,
 title: «How to parse JSON objects»,
 published: false
}
console.log(«======== ARR EXAMPLE ==========»);
console.log(«orig arr=====>», arr);
console.log(«stringified arr=====>», JSON.stringify(arr));
console.log(«proof of type=====>», typeof JSON.stringify(arr));
console.log(«parsed string=====>», JSON.parse(JSON.stringify(arr)));
console.log(«proof of type=====>», typeof JSON.parse(JSON.stringify(arr)), «\n\n»);
console.log(«======== OBJ EXAMPLE ==========»);
console.log(«orig obj=====>», obj);
console.log(«stringified obj=====>», JSON.stringify(obj));
console.log(«proof of type=====>», typeof JSON.stringify(obj));
console.log(«parsed string=====>», JSON.parse(JSON.stringify(obj)));
console.log(«proof of type=====>», typeof JSON.parse(JSON.stringify(obj)), «\n\n»);

Новые методы даты

В ES5 появилось два новых метода объекта Date, которые функционально эквивалентны. Оба они возвращают текущее время в миллисекундах с 1 января 1970 года. Они Date.now()и есть новые Date().valueOf().

console.log(Date.now());
 
console.log(new Date().valueOf());

Самая большая разница между этими двумя методами заключается в том, что valueOf()это метод экземпляра объекта Date и Date.now()статическая функция объекта Date.

Примечание: Internet Explorer может не поддерживать Date.now(), поэтому, если вас это беспокоит, вам, возможно, придется разобраться с этим в своем коде.

getters and setters

В ES5 мы познакомились с идеей свойств доступа. Это функции, единственная цель которых — получить или установить значение. Когда вы их называете, они выглядят как стандартные свойства:

let character = {
 first_name: «Darth»,
 last_name: «Vader»,
 get fullName() {
   return `${this.first_name} ${this.last_name}`;
 },
 set fullName(str) {
   [this.first_name, this.last_name] = str.split(» «);
 }
};
console.log(character.fullName); // Darth Vader
character.fullName = «Luke Skywalker»
console.log(character.first_name);
console.log(character.last_name);

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

Подробно об обновлениях ES6

От законченной версии ES5 до выпуска ES6 прошло семь лет . Он стал стандартом в июне 2015 года.

Babel

Одно из самых больших изменений по сравнению с ES5 заключается в том, что ES6 JavaScript не может быть скомпилирован непосредственно в браузерах . Нам нужно использовать транспилятор, вызываемый Babel.jsдля создания совместимого JavaScript, который могут читать старые браузеры.

Babel позволяет вам использовать функции и синтаксис ES6 в вашем проекте, а затем переводит его на ES5, чтобы вы могли использовать его в продакшене.

Чтобы использовать Babel при сборке проекта, вам нужно добавить в проект package.json. Здесь будут храниться все ваши зависимости для вашего проекта.

Убедитесь, что у вас установлены Node и npm (или установлены Node и Yarn, если вы предпочитаете использовать Yarn), а затем введите в терминале команду npm initили yarn init. Ответьте на возникающие вопросы, и эти значения package.jsonбудут предварительно заполнены.

Используйте npm / yarn, чтобы добавить babel к вашим зависимостям с помощью команды:

npm install --save-dev babel-cli
// or
yarn add babel-cli --dev

Вы будете использовать поле сценариев в вашем, package.jsonчтобы установить команду сборки с помощью Babel. Фактическая команда будет отличаться в зависимости от папки, из которой вы создаете, и того, где вы хотите создавать.

Наконец, в корневой папке вашего проекта (где он package.jsonнаходится) создайте .babelrcфайл. Это файл конфигурации Babel, который скажет Babel преобразовать ваш код в ES5. Установите предустановку с помощью:

npm install --save-dev babel-preset-env
// or
yarn add babel-preset-env --dev

А затем определите его в своем .babelrcфайле:

{
    “presets”: [“env”]
}

Теперь вы можете запустить Babel, выполнив команду сборки. Ваша целевая папка теперь должна выглядеть точно так же, как ваша исходная папка, за исключением того, что содержимое целевой папки представляет собой код ES5, а не ES6.

Если вы используете библиотеку или фреймворк JavaScript create-react-app, скорее всего, Babel уже настроен для вас, и вам не нужно об этом беспокоиться. Это для проектов, которые создаются с нуля.

Функции Big Arrow

До этого нового стандарта JavaScript использовал ключевое слово function для создания функций. Теперь мы можем использовать большую стрелку =>, чтобы писать функции. Это может сделать код более элегантным, поскольку мы можем создавать однострочные жирные стрелочные функции.

//pre ES-6
 
function add(num1, num2) {
 return num1 + num2;
}
 
//ES6 (implicit return)
const addImplicit = (num1, num2) => num1 + num2;
 
console.log(add(3, 4));
console.log(addImplicit(3, 4));

Однострочник ES6 имеет неявный возврат. Нам не нужно ключевое слово return, если функция — только одна строка. Это также означает, что фигурные скобки не нужны. Если функция состоит из более чем одной строки, нам потребуются фигурные скобки и оператор возврата:

//ES6 (explicit return)
 
const addExplicitReturn = (num1, num2) => {
 let sum = num1 + num2;
 return sum;
};
 
 
console.log(addExplicitReturn(3, 4));

Также важно отметить, что когда вы используете классы, стрелочная функция привязана к ключевому слову «this», поэтому нет необходимости использовать bind()метод для привязки функции к классу.

При использовании ключевого слова function метод должен быть привязан к классу с bind() методом.

Классы

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

class StarWarsCharacter{
 constructor(attributes) {
   this.name = attributes.name;
   this.age = attributes.age;
   this.homePlanet = attributes.homePlanet;
 }
 getCharacter = () => `${this.name} is ${this.age} years old and is from ${this.homePlanet}.`;
}
const luke = new StarWarsCharacter({ name: «Luke Skywalker», age: 23, homePlanet: «Tatooine»});
luke.getCharacter();
class Heroes extends StarWarsCharacter {
 constructor(attributes) {
   super(attributes);
   this.favoriteVehicle = attributes.favoriteVehicle;
 }
  getFavoriteVehicle = () => `${this.name} is ${this.age} and their favorite vehicle is the ${this.favoriteVehicle}`;
}
const hans = new Heroes({ name: «Hans Solo», age: 35, favoriteVehicle: «Millennium Falcon»});
console.log(hans.getFavoriteVehicle());

Деструктуризация

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

const state = {
 name: «Luke Skywalker»,
 age: 22,
 dark_side: false
}
console.log(«before destructuring»);
console.log(state.name); // notice the prefixed object name prior to each property
console.log(state.age);  // destructuring gets rid of this
console.log(state.dark_side);
const { name, age, dark_side } = state;
console.log(«after destructuring»);
console.log(name); // we can access using just the property name now!
console.log(age);
console.log(dark_side);

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

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

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

const arr_state = [ «Luke Skywalker», 22, false];
console.log(«before destructuring»);
console.log(arr_state[0]); // notice the index number in bracket notation
console.log(arr_state[1]);  // destructuring gets rid of this
console.log(arr_state[2]);
console.log(«\n\n\n»)
const [ name, age, dark_side ] = arr_state; // assign a variable to each of the indexes in the array
console.log(«after destructuring»);
console.log(name); // we can access using just the variable name we created now!
console.log(age);
console.log(dark_side);

let and const

В ES6 у нас есть несколько новых ключевых слов переменных, которые по сути заменили ключевое слово var. До ES6 JavaScript имел только функциональную и глобальную область видимости. С добавлением letи constу нас теперь есть область видимости блока.

let x = 5;
function blockExample() {
 let x = 2 //this is function scope;
 if(x >= 3) {
   let x = 10; // this is block scope
   console.log(x, «inside if block»);»
 } else {
   let x = 1;
   console.log(x, «inside else block»)
 }
 console.log(x, «inside function»);
}
blockExample();
console.log(x, «global example»);

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

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

// pre-ES6: 
var x = 5;
var x = 120; //produces no errors


// ES6: 
let x = 5;
let x = 120; // produces a syntax error

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

Обещания

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

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

console.log(«before promise»)
let promise = new Promise((resolve, reject) => {
  let resolvedFlag = false;
//this is just a flag so we can intentionally throw the response to test logic
  console.log(«this is eventually going to be an API call»);
  resolvedFlag = true; //flip resolved to true once all console logs are done
  if(resolvedFlag) { //if resolved is true invoke the resolve function
resolve(«Promise resolved THIS IS THE RESPONSE»);
  } else { // else invoke the reject function with a new Error object with message
    reject(new Error(«Promise failed»));
    console.log(«after promise»);
  }
});
promise.then(resp => {
  console.log(resp); //promise response
})

Операторы rest and spread

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

function restExample(a, ...b) {
 console.log(a); // 1
 console.log(b); // [2, 3, 4, 5, 6]
}
 
restExample(1, 2, 3, 4, 5, 6);

Оператор распространения использует тот же синтаксис, но вместо этого используется массивами. По сути, он берет содержимое массива, копирует его, чтобы его можно было распространить в новую структуру. Мы можем использовать оператор распространения как способ добавить что-то в массив без использования push()или unshift().

function spreadExample(arr) {
 let newArr = [2, 4, 6, 8];
 console.log(«arr», arr);
 let combinedArr = […newArr, …arr]; //this pushes the contents of newArr and the contens of arr into a one-dimensional combined array.
 let arrWithOtherContents = [«a», …newArr, {b: «c», d: «e»}, true, …arr];
 console.log(arrWithOtherContents);
 console.log(«combined», combinedArr);
}
console.log(spreadExample([1, 3, 5, 7, 9]))

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

Template Literals

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

let name = «Jane»;
let holiday = «Christmas»;
//pre-ES6:
console.log(name + «‘s favorite holiday is » + holiday);
//ES6+:
console.log(`${name}’s favorite holiday is ${holiday}`);
Читайте также:  Как научиться программировать на Swift: полное руководство
Оцените статью
bestprogrammer.ru
Добавить комментарий