JavaScript с нуля для начинающих

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

Вступление

JavaScript – это язык программирования или, другими словами, средство, с помощью которого компьютер получает инструкции.

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

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

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

Как и кошки, компьютерные программисты постоянно совершают ошибки: неправильно пишут, забывают кавычки или скобки и забывают о том, как работают основные функции. Программисты больше заботятся о том, чтобы заставить всё работать в конечном итоге, а не пытаются заставить всё работать с первого раза.

Лучший способ учиться – это учиться на ошибках!

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

Основы

На этой странице сейчас работает JavaScript. Давайте немного поиграемся с этим. Для простоты я предполагаю, что вы используете Google Chrome для чтения этой страницы (если вы этого не сделаете, возможно, нам обоим будет легче, если вы будете всё делать именно в Chrome).

Сначала щелкните правой кнопкой мыши в любом месте экрана и нажмите «Просмотреть код» (Inspect Element), затем перейдите на вкладку «Консоль» (Console). Вы должны увидеть что-то похожее на это:

Это консоль, иначе называемая «командной строкой» (command line) или «терминалом» (terminal). По сути, это способ ввести одну команду за раз в компьютер и немедленно получить ответ. Она очень полезна в качестве инструмента обучения (я все еще использую консоль почти каждый день, когда пишу код).

Консоль делает довольно интересные вещи. Здесь я начал что-то печатать, и консоль помогает мне, давая мне список всех возможных подсказок, которые я мог бы продолжать печатать! Еще одна вещь, которую вы можете сделать, это набрать 1 + 1 в консоли, а затем нажать клавишу Enter и посмотреть, что произойдет.

Использование консоли является очень важной частью изучения JavaScript. Если вы не знаете, работает ли что-то или для чего предназначена команда, перейдите к консоли и выясните это! Ниже будет много примеров.

Строки

Поскольку я кошка, я хочу заменить каждое встречающееся слово dog в Интернете на those blasted dogs (эти проклятые собаки).

Сначала зайдите в консоль и введите несколько предложений, которые содержат слово dog хотя бы один раз. В JavaScript связка букв, цифр, слов или чего-либо еще называется строкой (string). Строки должны начинаться и заканчиваться кавычкой. Неважно, будет это одиночная кавычка ' или двойная ", просто убедитесь, что вы используете одинаковые кавычки в начале и в конце.

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

Хорошо, чтобы исправить одно из этих предложений (заменив dog нашей улучшенной версией), мы должны сначала сохранить оригинальное предложение, чтобы мы могли вызвать его позже, когда будем использовать магию замены. Заметьте, как строка повторяется красным, когда мы вводим ее в консоль? Это потому, что мы не сказали, чтобы она сохранило предложение где-либо, поэтому оно просто возвращает его обратно (или возвращает ошибку, если мы что-то испортили).

Значения и переменные

Значения являются простейшими компонентами в JavaScript. 1 – это значение, true – это значение, hello – это значение, function() {} – это значение, список можно продолжить! В JavaScript есть несколько разных типов значений, но нам не нужно сразу все их пересматривать – вы узнаете их, естественно, чем больше вы будете кодить!

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

var – это сокращение от английского слова variable – переменная, а = означает, что штука с правой стороны хранится в штуке с левой стороны. Также, как вы можете увидеть теперь что, когда мы сохраняем наше предложение в переменной, консоль не просто сразу возвращает наше предложение, но вместо этого дает нам undefined, что означает, что возвращать нечего.

Если вы просто введете имя переменной в консоль, она выведет значение, хранящееся в этой переменной. Отдельно стоит заметить про переменные, что по умолчанию они исчезают при переходе на другую страницу. Например, если бы я нажал кнопку «Обновить» (Refresh) в Chrome, моя переменная dogSentence была бы стерта и она бы больше никогда не существовала. Но пока не беспокойтесь об этом – вы можете просто нажимать стрелки вверх или вниз на клавиатуре, пока находитесь в консоли, чтобы просмотреть все, что вы недавно вводили.

Функции

Теперь, когда наше предложение хранится в переменной, давайте изменим слово, сохраненное в нем! Мы можем сделать это, выполнив функцию. Функции – это тип значения, который, в общем-то, служит для нас определенной функцией (целью или действием). Называть их «действиями» было бы странно, так что вместо этого использовали слово «функция».

В JavaScript есть функция replace, которая делает именно то, что мы хотим! Функции принимают любое количество значений в круглых скобках (ноль, одно или несколько) и возвращают либо undefined, либо измененную строку. Функция replace доступна для использования с любыми строками и принимает два значения: символы, которые нужно вывести, и символы, которые нужно поменять местами. Звучит немного запутано, поэтому приведем наглядный пример:

Заметьте, что значение dogSentence одинаковое даже после того, как мы запустим команду replace? Это происходит потому, что функция replace (и большинство функций JavaScript в этом отношении) принимает значение, которое мы ей передаем, и возвращает новое значение без изменения значения, которое мы передали. Поскольку мы не сохранили результат (нет =) она просто возвращает значение в нашей консоли.

«Стандартная библиотека»

Вам может быть интересно, какие другие функции доступны в JavaScript. Ответ: очень много.

Есть много встроенных стандартных библиотек, о которых вы можете узнать в MDN (сайт, управляемый Mozilla, который имеет много полезной информации о веб-технологиях). Например, вот страница MDN об объекте JavaScript Math.

Сторонний JavaScript

Существует также много JavaScript-кода, который не является встроенным. JavaScript от третьих лиц обычно называют «библиотекой» или «плагином». Один из моих любимых называется Underscore.js. Давайте возьмем его и загрузим на нашу страницу!

Сначала перейдите на сайт Underscore (http://underscorejs.org), нажмите на ссылку для загрузки (я обычно использую версии для разработки, потому что они легче читаются, но и другие предоставляют вам одинаковую базовую функциональность). Затем скопируйте весь код в буфер обмена (вы можете использовать Select All из меню Edit, чтобы выбрать все). Затем вставьте всё в консоль и нажмите Enter. Теперь в вашем браузере есть новая переменная: _.

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

Создание новых функций

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

function makeMoreExciting(string) {
  return string + '!!!!'
}

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

Выражение string + '!!!!' возвращает новую строку, и наша переменная с именем string остается такой же, как и раньше (так как мы не обновляли с помощью =.

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

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

Строка makeMoreExciting(выражение) эквивалентна выражение + '!!!!'. Что, если мы хотим изменить на месте (или обновить) значение строки? Просто сохраните возвращаемое значение функции обратно в нашу переменную выражения:

var sentence = "time for a nap"
sentence = makeMoreExciting(sentence)

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

Что произойдет, если мы уберем оператор return в нашей функции?

Почему выражение пустое? Потому что функции возвращает undefined по умолчанию! Вы можете вернуть значение, написав return. Функции должны принимать значение и, если они изменяют значение или создают новое значение, которое предполагается использовать позже, возвращают return значение (забавный факт: причудливый термин для этого стиля – функциональное программирование). Вот еще одна функция, которая ничего не возвращает, а использует другой метод, чтобы показать нам результат:

function yellIt(string) {
  string = string.toUpperCase()
  string = makeMoreExciting(string)
  console.log(string)
}

Эта функция yellIt использует нашу предыдущую функцию makeMoreExciting, а также встроенный метод для строк (String) toUpperCase. Методы – это просто имя функции, когда она принадлежит чему-то – в этом случае toUpperCase – это функция, которая принадлежит string, поэтому мы можем ссылаться на нее как на метод или функцию. С другой стороны, makeMoreExciting не принадлежит никому, поэтому было бы технически некорректно ссылаться на него как на метод (я знаю, это сбивает с толку).

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

Может быть что-то не так с вышеуказанной функцией yellIt? Вот два основных типа функций:

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

console.log является примером функции второго типа: она выводит данные в консоль – действие, которое вы можете увидеть своими глазами, но которое нельзя представить в виде значения JavaScript. Мое эмпирическое правило состоит в том, чтобы попытаться отделить два типа функций друг от друга, поэтому вот как я бы переписал функцию yellIt:

function yellIt(string) {
  string = string.toUpperCase()
  return makeMoreExciting(string)
}

console.log(yellIt("i fear no human"))

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

Циклы

Теперь, когда у нас есть некоторые базовые навыки мы можем начать лениться. Что?! Да, верно. Программирование – это лень. Ларри Уолл, изобретатель языка программирования Perl, назвал лень самым важным достоинством хорошего программиста.

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

Циклы являются одним из наиболее важных способов использования мощности компьютера. Помните underscore.js? Убедитесь, что он загружен на странице (помните: вы можете просто нажать стрелку вверх на клавиатуре несколько раз, а затем нажать Enter, чтобы загрузить его снова, если вам нужно), и попробуйте скопировать/вставить это в консоль:

function logANumber(someNumber) {
  console.log(someNumber)
}
_.times(10, logANumber)

В этом коде используется один из методов underscore под названием times, который принимает 1 число и 1 функцию, а затем начиная с 0 и до 10 прибавляет 1, вызывая функцию на каждом шаге.

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

logANumber(0)
logANumber(1)
logANumber(2)
logANumber(3)
logANumber(4)
logANumber(5)
logANumber(6)
logANumber(7)
logANumber(8)
logANumber(9)

Но кошки отказываются делать ненужную ручную работу как эта, поэтому мы всегда должны спрашивать себя: «Я делаю это самым ленивым способом?».

Так почему это называется зацикливанием? Подумайте об этом так: если бы мы записали список из 10 чисел (от 0 до 9) с использованием массива JavaScript, это выглядело бы так:

var zeroThroughTen = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Что на самом деле делает times – переходит к каждому числу и повторяет задачу. В приведенном выше примере задача состояла в том, чтобы вызвать функцию logANumber с текущим числом. Повторение задачи таким способом называется циклом с массивом.

Массивы

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

Вот как вы создаете массив:

var myCatFriends = ["bill", "tabby", "ceiling"]

Классно! Теперь у вас есть список ваших друзей.

Элементы (это то, что вы называете отдельным элементом в массиве), которые хранятся в массивах, начинаются и отсчитываются с 0. Таким образом, myCatFriends[0] возвращает bill, а myCatFriends[1] возвращает tabby т.д.

Чтобы вывести друзей в консоль из вашего нового массива, вы можете просто получить доступ к элементу, например, так:

console.log(myCatFriends[0])

Если у вас появился друг и вы хотите добавить его в свой список, то это очень просто: myCatFriends.push("super hip cat").

Чтобы проверить, что новый друг попал в ваш массив, вы можете использовать .length:

Обратите внимание, как push вернул длину? Удобно! Также обратите внимание, что массивы всегда сохраняют порядок, что означает, что они будут помнить порядок, в котором вы добавили или определили вещи. Не все в JavaScript сохраняет порядок, так что запомните это специальное свойство массивов!

Объекты

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

var myCatFriends = ["bill", "tabby", "ceiling"]
var lastNames = ["the cat", "cat", "cat"]
var addresses = ["The Alley", "Grandmas House", "Attic"]

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

Всё это очень ненадежно, потому что если наши массивы изменятся и мы добавим в начало нового друга, нам также придется обновить нашу переменную billsPosition, чтобы указать информацию о новом местоположении Билла в массивах! Вот более простой способ хранения информации с использованием объектов:

var firstCat = { name: "bill", lastName: "the cat", address: "The Alley" }
var secondCat = { name: "tabby", lastName: "cat", address: "Grandmas House" }
var thirdCat = { name: "ceiling", lastName: "cat", address: "Attic" }

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

Вы можете думать об объектах как о ключах на связке ключей. Каждый из них предназначен для конкретной двери, и если у вас есть красивые наклейки на ваших ключах, вы можете открыть двери очень быстро. Фактически, слева от : – ключи (или свойства), а справа – значения. В английском – это keys, properties и values.

// an object with a single key 'name' and single value 'bill'
{ name: 'bill' }

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

{ date: "10/20/2012", diary: "slept a bit today", name: "Charles" }

Но компьютер может вернуть вам так:

{ diary: "slept a bit today", name: "Charles", date: "10/20/2012" }

Или так:

{ name: "Charles", diary: "slept a bit today", date: "10/20/2012" }

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

var moodLog = [
  {
    date: "10/20/2012",
    mood: "catnipped"
  }, 
  {
    date: "10/21/2012",
    mood: "nonplussed"
  },
  {
    date: "10/22/2012",
    mood: "purring"
  }
]

// ordered from least to most favorite
var favorites = {
  treats: ["bird sighting", "belly rub", "catnip"],
  napSpots: ["couch", "planter box", "human face"]
}

Когда вы объединяете разные вещи таким образом, то вы создаете структуры данных, как лего!

Callbacks

Callbacks (или обратные функции) – это на самом деле не опция JavaScript, как Object (объект) или Array (массив), а просто определенный способ использования функций. Чтобы понять, почему обратные вызовы или функции полезны, вы сначала должны узнать об асинхронном программировании. Асинхронный код по определению – это код, написанный не синхронно. Синхронный код легко понять и написать. Вот пример для иллюстрации:

var photo = download('http://foo-chan.com/images/sp.jpg')
uploadPhotoTweet(photo, '@maxogden')

Этот синхронный псевдокод скачивает очаровательную фотографию кота, а затем загружает фотографию в твиттер и отправляет ее на @maxogden. Довольно просто!

Этот код является синхронным, потому что для загрузки фотографии в твиттер, скачивание фотографии должно быть завершено. Это означает, что строка 2 не может выполняться, пока задача в строке 1 не будет полностью завершена. Если бы мы на самом деле реализовали этот псевдокод, мы бы хотели убедиться, что загрузка «заблокирована», пока скачивание не будет завершено, что предотвратит выполнение любого другого JavaScript-кода до его завершения. А затем, когда скачивание завершится, будет разблокировано выполнение JavaScript, и строка 2 будет выполнена.

Синхронный код хорош для вещей, которые происходят быстро, но ужасен для вещей, которые требуют сохранения, загрузки, скачивания или выгрузки. Что если сервер, с которого вы загружаете фотографию, работает медленно, или интернет-соединение, которым вы пользуетесь, работает медленно, или на компьютере, на котором вы запускаете код, слишком много открытых вкладок с youtube и он работает медленно? Это означает, что потенциально может занять несколько минут ожидания, прежде чем строка 2 приступит к работе. Между тем, поскольку весь JavaScript на странице блокируется от запуска во время загрузки, веб-страница полностью зависает и перестает отвечать до завершения скачивания.

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

function measureLoopSpeed() {
  var count = 0
  function addOne() { count = count + 1 }

  // Date.now() returns a big number representing the number of
  // milliseconds that have elapsed since Jan 01 1970
  var now = Date.now()

  // Loop until Date.now() is 1000 milliseconds (1 second) or more into
  // the future from when we started looping. On each loop, call addOne
  while (Date.now() - now < 1000) addOne() // Finally it has been >= 1000ms, so let's print out our total count
  console.log(count)
}

measureLoopSpeed()

Скопируйте и вставьте приведенный выше код в консоль JavaScript, и через секунду он должен вывести число. На моем компьютере я получил 8527360, примерно 8,5 миллионов. За одну секунду JavaScript может вызвать функцию addOne 8,5 миллионов раз! Таким образом, если у вас есть синхронный код для скачивания фотографии, а загрузка фотографии занимает одну секунду, это означает, что вы потенциально предотвращаете выполнение 8,5 миллионов операций, когда выполнение JavaScript заблокировано.

В некоторых языках есть функция sleep, которая блокирует выполнение на некоторое количество секунд. Например, вот некоторый код bash, работающий в Terminal.app в Mac OS, который использует sleep. Когда вы запускаете команду sleep 3 && echo 'done sleep now', она блокируется на 3 секунды, прежде чем выведет 'done sleep now'.

У JavaScript нет функции sleep (сна). Вероятно, вы спрашиваете себя: «Почему я изучаю язык программирования, который не подразумевает сон?». Вместо того, чтобы полагаться на сон, чтобы подождать, когда что-то произойдет, JavaScript поощряет использование функций. Если вам нужно дождаться завершения задачи A, прежде чем выполнять задачу B, вы помещаете весь код задачи B в функцию и вызываете эту функцию только после завершения A.

Например, это код в стиле блокировки:

a()
b()

А этом в неблокирующем стиле:

a(b)

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

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

Вот псевдокод реализации того, как a может выглядеть:

function a(done) {
  download('https://pbs.twimg.com/media/B4DDWBrCEAA8u4O.jpg:large', function doneDownloading(error, png) {
    // handle error if there was one
    if (err) console.log('uh-oh!', error)

    // call done when you are all done
    done()
  })
}

Вспомните наш неблокирующий пример a(b), где мы вызываем a и передаем b в качестве первого аргумента. В определении функции для a над done наша функция b, которую мы передаем. Такое поведение сначала сложно понять.

Когда вы вызываете функцию a, передаваемые аргументы не будут иметь одинаковые имена переменных, когда они находятся в функции. В этом случае то, что мы называем b, называется done внутри функции. Но b и done – это просто имена переменных, которые указывают на одну и ту же базовую функцию. Обычно функции обратного вызова помечаются чем-то вроде done или callback, чтобы прояснить, что они являются функциями, которые должны вызываться при выполнении текущей функции.

Таким образом, до тех пор, пока a выполняет свою работу и вызывает b по завершении, a и b будут вызываться как в неблокирующей, так и в блокирующей версиях. Разница в том, что в неблокирующей версии нам не нужно останавливать выполнение JavaScript. В общем, неблокирующий стиль – это то, где вы пишете каждую функцию, чтобы она могла вернуть значение (return) как можно скорее, без каких-либо блокировок.

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

Помните: программирование – это про лень, и вы должны спать, а не сидеть за компьютером.

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

Зе енд

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

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

Перевод материала “JavaScript for cats”

Наверх ↑

Сергей Ермилов

Создаю веб-проекты, придумываю идеи, занимаюсь разработкой, оптимизирую и продвигаю сайты. Первые шаги в этом направлении были сделаны в 2008 году. Если у вас есть какие-то идеи или вы хотите что-то предложить, то пишите мне и моей команде.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вы можете использовать HTML теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>