company_banner

Ванильный JavaScript и HTML. Никаких фреймворков. Никаких библиотек. Никаких проблем

Автор оригинала: John Papa
  • Перевод
  • Tutorial

Используете для создания приложений Vue, React, Angular или Svelte? Я использую, и если вы тоже, и уверен, что вам уже давно не приходилось писать приложение, которое выводит информацию без этих прекрасных инструментов.

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

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

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

Давайте рассмотрим разные способы вывода информации. И держите под рукой эту документацию!

Образец приложения


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


Инструменты


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

Мы будем использовать только HTML, TypeScript/JavaScript, CSS и браузерную DOM (объектную модель документа). Давайте посмотрим, как можно отобразить HTML.

Здесь вы можете почитать о том, как писать на TypeScript с помощью VS Code.

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

Подход


Наше приложение отображает индикатор выполнения и список персонажей. Начнём с самого простого — индикатора, — и рассмотрим разные способы его отображения. Затем перейдём к списку и разберёмся, как меняется ситуация, если нужно выводить больше информации.

Отображение индикатора выполнения


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

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

Отображение индикатора с помощью внутреннего HTML


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

const heroPlaceholder = document.querySelector('.hero-list');
heroPlaceholder.innerHTML = `
  <progress
    class="hero-list progress is-medium is-info" max=^_^quotdquot^_^
  ></progress>
`;

Просто. Легко. Почему так нельзя сказать про любой код? Ну посмотрите, как быстро этот фрагмент решает проблему!

Вероятно, вы уже заметили, насколько уязвим этот код. Одна ошибка в HTML — и всё! У нас проблема. И действительно ли он удобочитаем? Да, это так, но что будет, когда HTML станет слишком сложным? Когда у вас 20 строк кода с классами, атрибутами и значениями… ну, вы поняли. Да, решение не идеальное. Но при небольшом количестве HTML вполне работает, и я рекомендую присмотреться к нему любителям всё писать в одну строку.

Также обратите внимание, как встроенный контент может влиять на удобочитаемость и стабильность кода. Например, если вам нужно добавить информацию внутрь индикатора для изменяющегося сообщения о загрузке, то можно сделать это с помощью замены, например, ${message}. Это не хорошо и не плохо, просто добавляет удобство при создании больших шаблонных строк.

И последнее: innerHTML может влиять на скорость отображения. Я не увлекаюсь избыточной оптимизацией, но лучше проверяйте производительность, потому что innerHTML может быть причиной возникновения циклов отрисовки и вывода макетов в браузере. Имейте в виду.

Индикатор выполнения с помощью DOM API


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

const heroPlaceholder = document.querySelector('.hero-list');
const progressEl = document.createElement('progress');
progressEl.classList.add('hero-list', 'progress', 'is-medium', 'is-info');
const maxAttr = document.createAttribute('max');
maxAttr.value = '100';
progressEl.setAttributeNode(maxAttr);
heroPlaceholder.replaceWith(progressEl);

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

А недостаток в том, что требуется шесть строк кода для вывода информации, которая выводится одной строкой с помощью innerHTML.

Код DOM API для отображения индикатора более читабелен, чем код с innerHTML? Я бы поспорил. А не в том ли причина, что HTML-код индикатора очень короткий и простой? Возможно. Если бы было 20 строк, то innerHTML стал бы гораздо сложнее в чтении… впрочем, и код DOM API тоже.

Отрисовка индикатора с помощью шаблонов


Ещё один способ заключается в создании тега <tеmplate> и использовании его для упрощения вывода информации.

Создадим <tеmplate> и присвоим ему ID. Шаблон не будет отображён на HTML-странице, но позднее вы можете ссылаться на его содержимое и использовать его. Это очень полезно, вы можете писать HTML везде, где это целесообразно: на HTML-странице, с применением всех полезных возможностей HTML-редактора.

<template id="progress-template">
  <progress class="hero-list progress is-medium is-info" max=^_^quotdquot^_^></progress>
</template>

Затем код может взять шаблон с помощью метода document.importNode() из DOM API. По мере необходимости код может манипулировать содержимым шаблона. Теперь добавьте это содержимое в DOM, чтобы отобразить индикатор.

const heroPlaceholder = document.querySelector('.hero-list');
const template = document.getElementById('progress-template') as HTMLTemplateElement;
const fetchingNode = document.importNode(template.content, true);
heroPlaceholder.replaceWith(fetchingNode);

Шаблоны — хороший способ сборки HTML; мне он нравится, потому что позволяет писать HTML там, где это имеет смысл, и можно меньше делать с помощью кода на TypeScript/JavaScript.

Можно ли импортировать шаблоны из других файлов? Да, но с помощью других библиотек. Однако сейчас мы от них сознательно отказались. Импорт HTML обсуждается годами, но, как видно из материалов сайта «Can I Use», даже не все современные браузеры поддерживают эту возможность.

Отображение списка персонажей


Теперь поговорим о том, как можно отображать список персонажей с помощью тех же трёх методик. Отличие от отрисовки одиночного HTML-элемента <prоgress> и списка персонажей заключается в том, что теперь мы:

  • отображаем несколько HTML-элементов;
  • добавляем несколько классов;
  • добавляем определённую последовательность из дочерних элементов;
  • отображаем для каждого персонажа много одинаковой информации;
  • динамически отображаем текст внутри элементов.

Отображение персонажей с помощью внутреннего HTML


При использовании innerHTML целесообразно начать с массива персонажей и итерировать его. Таким образом можно создавать по строке за раз. Строки будут отличаться только именами персонажей и описанием, которые можно вставлять с помощью шаблонных строк. Каждый персонаж в массиве создаёт тег li, который связывается со строчным массивом. Наконец, строчный массив преобразуется в чистый HTML, обёрнутый в ul и назначенный для innerHTML.

function createListWithInnerHTML(heroes: Hero[]) {
  const rows = heroes.map(hero => {
    return `<li>
        <div class="card">
          <div class="card-content">
            <div class="content">
              <div class="name">${hero.name}</div>
              <div class="description">${hero.description}</div>
            </div>
          </div>
        </div>
      </li>`;
  });
  const html = `<ul>${rows.join('')}</ul>`;
  heroPlaceholder.innerHTML = html;

Это работает. И возможно, это более удобочитаемо. Что насчёт производительности? Трудно ли выявить (или вообще возможно?) опечатки при наборе кода? Судить вам. Но давайте не будем делать выводов, пока не разберёмся с другими методиками.

Отображение персонажей с помощью DOM API


function createListWithDOMAPI(heroes: Hero[]) {
  const ul = document.createElement('ul');
  ul.classList.add('list', 'hero-list');
  heroes.forEach(hero => {
    const li = document.createElement('li');
    const card = document.createElement('div');
    card.classList.add('card');
    li.appendChild(card);
    const cardContent = document.createElement('div');
    cardContent.classList.add('card-content');
    card.appendChild(cardContent);
    const content = document.createElement('div');
    content.classList.add('content');
    cardContent.appendChild(content);
    const name = document.createElement('div');
    name.classList.add('name');
    name.textContent = hero.name;
    cardContent.appendChild(name);
    const description = document.createElement('div');
    description.classList.add('description');
    description.textContent = hero.description;
    cardContent.appendChild(description);
    ul.appendChild(li);
  });
  heroPlaceholder.replaceWith(ul);
}

Отображение персонажей с помощью шаблонов


Список персонажей можно отобразить с помощью шаблонов. Для этого применяется та же методика, что и для отображения элемента <prоgress>. Сначала на HTML-странице создаём шаблон. Этот HTML-код будет чуть сложнее, чем в случае с <prоgress>. Но это не проблема. Это просто HTML внутри HTML-страницы, так что мы можем легко исправлять ошибки и форматирование с помощью прекрасного редактора VS Code.

<template id="hero-template">
  <li>
    <div class="card">
      <div class="card-content">
        <div class="content">
          <div class="name"></div>
          <div class="description"></div>
        </div>
      </div>
    </div>
  </li>
</template>

Теперь можете написать код для создания списка персонажей. Сначала создаём ul для обёртывания строк с персонажами li. Теперь можно проходить по массиву и с помощью того же метода document.importNode() применять один и тот же шаблон для каждого персонажа. Обратите внимание, что шаблон можно многократно использовать для создания каждой строки. Он превращается в чертёж-исходник, по которому создаётся необходимое количество строк.

Список персонажей должен содержать имена и описания. То есть нужно будет заменять конкретные значения внутри шаблона. Для этого целесообразно каким-то образом определять и ссылаться на места, куда вы будете вставлять эти значения. В нашем примере для получения ссылки на каждого персонажа используется метод querySelector('your-selector'), после чего задаётся имя и описание.

function createListWithTemplate(heroes: Hero[]) {
  const ul = document.createElement('ul');
  ul.classList.add('list', 'hero-list');
  const template = document.getElementById('hero-template') as HTMLTemplateElement;
  heroes.forEach((hero: Hero) => {
    const heroCard = document.importNode(template.content, true);
    heroCard.querySelector('.description').textContent = hero.description;
    heroCard.querySelector('.name').textContent = hero.name;
    ul.appendChild(heroCard);
  });
  heroPlaceholder.replaceWith(ul);
}

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

Итоги


Я не упомянул о том, как отображают информацию популярные фреймворки и библиотеки. Vue, React, Angular и Svelte существенно облегчают задачу и требуют писать меньше кода. Также у них есть и другие преимущества. В этой статье мы рассмотрели лишь относительно простые методики вывода информации с помощью DOM и чистого HTML, а также TypeScript/JavaScript.

К чему мы пришли?

Надеюсь, вы теперь представляете, как выводить информацию без использования библиотек. Есть ли другие способы? Конечно. Можно написать функции, которые упростят код и позволят использовать его многократно? Конечно. Но однажды вам захочется поэкспериментировать с одним из таких прекрасных инструментов, как Vue, React, Angular или Svelte.

Эти фреймворки делают за нас много работы. Вы можете помнить, как выводить информацию с помощью чистого DOM-кода с JavaScript или jQuery. Или как делать это с помощью инструментов вроде handlebars или mustache. А может быть, вы никогда не выводили информацию в DOM без помощи фреймворка. Можете сами представить, какие процессы протекают под капотом современных библиотек и фреймворков, когда они выводят для вас информацию на экран.

Полезно знать, что можно сделать с помощью чистого HTML, TypeScript/JavaScript и CSS, даже если вы используете фреймворк, который избавляет вас от всего этого.

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

Похожие публикации

Комментарии 42

    +2
    Код примера ниже выглядят дико, неужели действительно кто-то добавляет HTML элементы таким способом?

        const li = document.createElement('li');
        const card = document.createElement('div');
        card.classList.add('card');
        li.appendChild(card);
        const cardContent = document.createElement('div');
        cardContent.classList.add('card-content');
        card.appendChild(cardContent);
        const content = document.createElement('div');
        content.classList.add('content');
        cardContent.appendChild(content);
        const name = document.createElement('div');
        name.classList.add('name');
        name.textContent = hero.name;
        cardContent.appendChild(name);
        const description = document.createElement('div');
        description.classList.add('description');
        description.textContent = hero.description;
        cardContent.appendChild(description);
        ul.appendChild(li);


    innerHTML ведь намного эстетичнее и нагляднее
      0
      В Мейл.ру — так
        0
        innerHTML полностью затирает существующее содержимое — но что если нужно добавить элементы, особенно если не в конец, а середину?
          +5

          insertBefore — если хотите на объектах, insertAdjacentHTML — если хотите на стрингах

            –6
            Это очевидно. Так же как и то, что вопрос был риторический.
          +4
          innerHTML хоть и эстетичней и наглядней, но имеет много недостатков, и один из них это скорость.
          При каждой записи, браузер парсит заново все содержимое данного элемента, особенно мне нравиться записи такого рода:
          ul.innerHTML += '<li> ... </li>';

          Также огромным минусом является потеря всех ссылок и ивентов на элементах:
          div.innerHTML = '<div class="content"></div>';
          var content = div.querySelector('.content');
          div.innerHTML += '<div class="content2"></div>';
          console.log(content.parentNode); // null

          Ну и к тому же пример автора хороший тем, что уже сразу есть все ссылки, хотя прочесть и понять данный код, очень трудно…
            +1
            ul.innerHTML += '… ';

            Ну, простите, это проблема криворукого программиста, а не innerHTML. Понятно же, что для innerHTML нужно сперва подготовить строку, а потом присвоить её всю целиком.

              –1
              Интересно было бы услышал мнения человека, который не поленился, и поставил минус в карму, из-за данного комментария…
                +2

                Я как был мелким, делал юзерскрипт для ВК (еще старого), долго не мог плнять почему сайдбар переставал работать когда я добавлял свою ссылку)
                Через innrHTML +=

                  –2

                  присвоение innerHTML — самый быстрый (производительный) способ вставить большой фрагмент в документ. Уж движок знает, как быстро распарсить HTML. Портянка с дясятком DOM-операций не может быть производительней ОДНОЙ DOM-операции.

                    +1
                    В том то и дело, что надо распарсить, и для начала лучше проверить, чем говорить о том, о чем не знаете, как пример:
                    var el = document.getElementById('el');
                    
                    console.time('time');
                    
                    var html = '';
                    for (var i = 0; i != 1000000; i++) {
                      html += '<div></div>';
                    }
                    el.innerHTML = html;
                    
                    console.timeEnd('time'); // time: 934.566162109375ms
                    
                    el.innerHTML = '';
                    
                    console.time('time');
                    
                    for (var i = 0; i != 1000000; i++) {
                      var div = document.createElement('div');
                      el.appendChild(div);
                    }
                    
                    console.timeEnd('time'); // time: 564.466796875ms

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

                    P.S. заметьте, в первом случае, с innerHTML, выполняется лишь одна DOM операция, а во втором, аж целых миллион раз, так почему же она быстрее?
                      0
                      А если вынести генерацию html для первого примера за пределы учета времени?
                        0
                        Сильно цифры не поменялись, но тогда надо и для второго примера написать, вместо цикла, что то такое:
                        el.appendChild(document.createElement('div'));
                        el.appendChild(document.createElement('div'));
                        el.appendChild(document.createElement('div'));
                        el.appendChild(document.createElement('div'));
                        ...
                  0

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

                    +1
                    Часто приходится работать с чистым JS, я обычно так делаю:

                    ul.insertAdjacentHTML('beforeEnd', `
                        <li>
                            <div class="card">
                                <div class="card-content">
                                    <div class="content">
                                        <div class="name">
                                            ${ hero.name }
                                        </div>
                                        <div class="description">
                                            ${ hero.description }
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </li>`
                    );
                    
                      +3

                      Тут надо быть осторожным с hero.name и hero.description, т.к. они могут вызвать XSS проблемы, если hero.description например <script src="evil.com/do_bad_stuff.js"></script>.

                        0
                        Хорошее замечание, спасибо.
                          0

                          Кстати, плюсом шаблонных строк являются "теговые шаблоны", которые позволяют эскейпить строку, но не всю, а только вставленные переменные


                          Вот: https://hacks.mozilla.org/2015/05/es6-in-depth-template-strings-2/

                        +3

                        Почему нет? Работает достаточно быстро. Правда таким голым совсем кодом только если прям совсем одну кнопку инлайн нужно добавить. Если чуть сложнее — можно обернуть из серии


                        create_element('li', { className: 'card' }, [ createElement('div', {}, [create_text_node('text')])]);


                        Вынести create_element в отдельную библиотеку, позволить ее расширять. Разве что синтаксис получился многословный, но раз мы используем typescript — мы уже используем препроцессинг, поэтому можно сделать DSL чтобы оно выглядело как обычный html и компилировалось в такой код. Oh wait…

                          +2

                          При использовании innerHTML надо быть осторожным с динамическими данными типа hero.name и hero.description, т.к. если они не закодированы, то могут вызвать XSS. В этом плане textContent безопаснее.

                            0
                            В innerHTML, если вставлять в специально для этого предназначенный пустой узел, остаётся экранировать в пользовательских данных только открывающую угловую скобку, насколько я помню. Зато можно добавлять какое-нибудь несложное форматирование на стороне сервера — например, вставлять форматированный текст с полужирным, курсивом, etc.
                          0

                          Ванильный js это круто, но на просторах github можно найти много чего интересного.
                          Очень много микро пародий react вроде choo.js или есть просто jsx -> dom https://github.com/proteriax/jsx-dom

                            +2
                            Особенности его набора прекрасно помогают избегать багов, и я пользуюсь его возможностью транспилирования в любой формат JavaScript.

                            На этой фразе я сломался.

                              +4
                              Фреймворки на самом деле нужны для того чтобы программисты были легко заменяемы.
                              В этом суть.

                              Индустрии необходимо чтобы все работники были стандартными. Чтобы можно было легко уволить работника и взять нового.

                              Поэтому в вакансиях требование — умение читать чужой код, чтобы доделывать работу вместо уволенного программиста.
                                –3
                                чтобы доделывать работу вместо уволенного программиста
                                А нафига его брали тогда? :) Что бы уволить? А когда брали, вот интересно — куда смотрели?

                                Или вы и правда хотите сказать, а оно получается так, что бы так называемому руководству всегда было «комфортно» и «приятно», то для этого и появились фреймворки? :)))))
                                И сами же программисты их и написали ога? Ну что бы у руководства появились «комфорт» и «приятность» ага?

                                Слушайте, вы точно не сумасшедший? :))))
                                  +4
                                  чтобы доделывать работу вместо уволенного программиста.
                                  Или уволившегося программиста
                                  +4
                                  Ванильный JS ради ванильного JS.

                                  Когда это приложение хоть чуть-чуть разрастется, поддержка таких рендеров превратится в ад. Аналогично, когда сам шаблон чуть подрастет в функционале и для изменений одной циферки будет перерисовываться все страница. Затем вы решите этого избежать и ванильный JS превратиться в ванильный-самопальный шаблонизатор, который будет заимствовать идеи из взрослых библиотек и ванильность куда-то улетучится.

                                  Но если ради примера — почему бы и нет.
                                    +3
                                    Хорошая статья получилась, отлично показывает, почему писать в 2020 году без инструментов шаблонизации — уже мягко говоря не стоит.
                                    При том, что простые инструменты шаблонизации — это в общем-то тривиальная надстройка над document.createElement/appendChild, которую вы в любом случае (криво) переизобретете в очередной миллионный раз, если вздумаете написать хоть сколько-нибудь большой код.
                                      +4
                                      Если уж и использовать ванилу — то почему без кастомных елементов?
                                        +1
                                        Там еще наследование классов появляется, практически полноценное ООП + стейт-менеджмент (свойства и методы кастом элементов доступны как DOM properties), красота.
                                        –5
                                        и нафига тут тупоскрипт и реклама этого вот редактора? Без них не взлетит?
                                          0
                                          Импорт HTML обсуждается годами, но, как видно из материалов сайта «Can I Use», даже не все современные браузеры поддерживают эту возможность

                                          Это предложение содержит искаженную информацию. Стандарт уже завернули обратно, и никем он не обсуждается. Современные браузеры и не собираются его поддерживать, вот позиция Mozilla, например: https://hacks.mozilla.org/2014/12/mozilla-and-web-components/


                                          В качестве замены нам обещают html modules но прогресс с ними пока не особый.

                                            +2
                                            Поправьте меня, но правильно ли я понял? Мы отказались от svelte, а потом руками написали тот код который бы нам сгенерировал sveltejs, только код стал более запутанным и плохо поддерживаемым. И ладно бы мотивировал это простотой и отсутствием доп инструментов, но нет же, мы возьмем TS и добавим этап сборки.
                                            Вопрос. Зачем?
                                              0
                                              Кроме того, это забавно, полезно и помогает понять ценность современных библиотек и фреймворков, которые делают за нас так много работы.
                                              +1

                                              Недавно пришлось переписать одно приложение на ваниле (ES5), т.к. оно должно было работать на странном клиентском смарт-тв с допотопным браузерным движком и целым рядом прочих ограничений. Сам удивился насколько это оказалось просто и быстро. Ясно, что для масштабируемости подобных решений все равно понадобятся дополнительные телодвижения, но писать свои микрофреймворки для таких случаев это интересно, как минимум. А примеры в статье — ужасны.

                                                +2

                                                Спасибо за статью. Ловлю некоторый диссонанс после прочтения. В прологе рассказывается о ванильном JS и CSS. В статье используется уже TypeScript. Придерживаясь такой политики, можно взять и SvelteJS, на выходе тоже получаем максимально приближенный ванильный JS код.

                                                  0

                                                  Автор оригинала статьи – евангелист Angular. Видимо, настолько прикипел к тайпскрипту, что даже в статье не смог без него.

                                                  0
                                                  Какие же в итоге селекторы получатся если так называть классы? По 2.5 метра?
                                                    0
                                                    Если уж добавлен typescript, то грех отказываться от jsx. Нужно будет только написать простую createElement функцию, которая и элементы сгенерирует, и обработчики событий привяжет, и ref расставит как душе угодно.
                                                      0

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


                                                      <div className="test"/>
                                                      // превращается в
                                                      React.createElement('div', {className: 'test'})

                                                      Чтобы это все работало, нужен React или любая другая библиотека с совместимой сигнатурой.

                                                        0
                                                        Чтобы это все работало, нужен React или любая другая библиотека с совместимой сигнатурой.

                                                        <irony>непомерно сложная задача</irony> :)

                                                      +1
                                                      Ну есть же классы, веб-компоненты. С ними вполне можно и на «чистом» писать. А так уж скорее статья не в пользу ванильного жса.

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                      Самое читаемое