Файл js map что это

Обновлено: 07.07.2024

Сейчас мы знаем о следующих сложных структурах данных:

  • Объекты для хранения именованных коллекций.
  • Массивы для хранения упорядоченных коллекций.

Но этого не всегда достаточно для решения повседневных задач. Поэтому также существуют Map и Set .

Map – это коллекция ключ/значение, как и Object . Но основное отличие в том, что Map позволяет использовать ключи любого типа.

Методы и свойства:

  • new Map() – создаёт коллекцию.
  • map.set(key, value) – записывает по ключу key значение value .
  • map.get(key) – возвращает значение по ключу или undefined , если ключ key отсутствует.
  • map.has(key) – возвращает true , если ключ key присутствует в коллекции, иначе false .
  • map.delete(key) – удаляет элемент по ключу key .
  • map.clear() – очищает коллекцию от всех элементов.
  • map.size – возвращает текущее количество элементов.

Как мы видим, в отличие от объектов, ключи не были приведены к строкам. Можно использовать любые типы данных для ключей.

Map может использовать объекты в качестве ключей.

Использование объектов в качестве ключей – это одна из известных и часто применяемых возможностей объекта Map . При строковых ключах обычный объект Object может подойти, но для ключей-объектов – уже нет.

Попробуем заменить Map на Object в примере выше:

Так как visitsCountObj – это объект, то все ключи он автоматически преобразует к строке, в итоге получился строковой ключ "[object Object]" . Это не то, чего мы хотим.

Чтобы сравнивать ключи, объект Map использует алгоритм SameValueZero. Это почти такое же сравнение, что и === , с той лишь разницей, что NaN считается равным NaN . Так что NaN также может использоваться в качестве ключа.

Этот алгоритм не может быть заменён или модифицирован.

Каждый вызов map.set возвращает объект map, так что мы можем объединить вызовы в цепочку:

Перебор Map

Для перебора коллекции Map есть 3 метода:

  • map.keys() – возвращает итерируемый объект по ключам,
  • map.values() – возвращает итерируемый объект по значениям,
  • map.entries() – возвращает итерируемый объект по парам вида [ключ, значение] , этот вариант используется по умолчанию в for..of .

В отличие от обычных объектов Object , в Map перебор происходит в том же порядке, в каком происходило добавление элементов.

Кроме этого, Map имеет встроенный метод forEach , схожий со встроенным методом массивов Array :

Object.entries: Map из Object

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

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

Так что мы можем создать Map из обычного объекта следующим образом:

Здесь Object.entries возвращает массив пар ключ-значение: [ ["name","John"], ["age", 30] ] . Это именно то, что нужно для создания Map .

Object.fromEntries: Object из Map

Мы только что видели, как создать Map из обычного объекта при помощи Object.entries(obj) .

Есть метод Object.fromEntries , который делает противоположное: получив массив пар вида [ключ, значение] , он создаёт из них объект:

Мы можем использовать Object.fromEntries , чтобы получить обычный объект из Map .

К примеру, у нас данные в Map , но их нужно передать в сторонний код, который ожидает обычный объект.

Вот как это сделать:

Вызов map.entries() возвращает итерируемый объект пар ключ/значение, как раз в нужном формате для Object.fromEntries .

Мы могли бы написать строку (*) ещё короче:

Это то же самое, так как Object.fromEntries ожидает перебираемый объект в качестве аргумента, не обязательно массив. А перебор map как раз возвращает пары ключ/значение, так же, как и map.entries() . Так что в итоге у нас будет обычный объект с теми же ключами/значениями, что и в map .

Объект Set – это особый вид коллекции: «множество» значений (без ключей), где каждое значение может появляться только один раз.

Его основные методы это:

  • new Set(iterable) – создаёт Set , и если в качестве аргумента был предоставлен итерируемый объект (обычно это массив), то копирует его значения в новый Set .
  • set.add(value) – добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект set .
  • set.delete(value) – удаляет значение, возвращает true , если value было в множестве на момент вызова, иначе false .
  • set.has(value) – возвращает true , если значение присутствует в множестве, иначе false .
  • set.clear() – удаляет все имеющиеся значения.
  • set.size – возвращает количество элементов в множестве.

Основная «изюминка» – это то, что при повторных вызовах set.add() с одним и тем же значением ничего не происходит, за счёт этого как раз и получается, что каждое значение появляется один раз.

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

Множество Set – как раз то, что нужно для этого:

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

Перебор объекта Set

Мы можем перебрать содержимое объекта set как с помощью метода for..of , так и используя forEach :

Заметим забавную вещь. Функция в forEach у Set имеет 3 аргумента: значение value , потом снова то же самое значение valueAgain , и только потом целевой объект. Это действительно так, значение появляется в списке аргументов дважды.

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

Set имеет те же встроенные методы, что и Map :

  • set.values() – возвращает перебираемый объект для значений,
  • set.keys() – то же самое, что и set.values() , присутствует для обратной совместимости с Map ,
  • set.entries() – возвращает перебираемый объект для пар вида [значение, значение] , присутствует для обратной совместимости с Map .

Итого

Map – коллекция пар ключ-значение.

Методы и свойства:

  • new Map([iterable]) – создаёт коллекцию, можно указать перебираемый объект (обычно массив) из пар [ключ,значение] для инициализации.
  • map.set(key, value) – записывает по ключу key значение value .
  • map.get(key) – возвращает значение по ключу или undefined , если ключ key отсутствует.
  • map.has(key) – возвращает true , если ключ key присутствует в коллекции, иначе false .
  • map.delete(key) – удаляет элемент по ключу key .
  • map.clear() – очищает коллекцию от всех элементов.
  • map.size – возвращает текущее количество элементов.

Отличия от обычного объекта Object :

  • Что угодно может быть ключом, в том числе и объекты.
  • Есть дополнительные методы, свойство size .

Set – коллекция уникальных значений, так называемое «множество».

Методы и свойства:

  • new Set([iterable]) – создаёт Set , можно указать перебираемый объект со значениями для инициализации.
  • set.add(value) – добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект set .
  • set.delete(value) – удаляет значение, возвращает true если value было в множестве на момент вызова, иначе false .
  • set.has(value) – возвращает true , если значение присутствует в множестве, иначе false .
  • set.clear() – удаляет все имеющиеся значения.
  • set.size – возвращает количество элементов в множестве.

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

Задачи

Фильтрация уникальных элементов массива

Допустим, у нас есть массив arr .

Создайте функцию unique(arr) , которая вернёт массив уникальных, не повторяющихся значений массива arr .

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

Здесь вступают в игру source maps, указывая точное сопоставление в нашем production коде на исходный авторский код. В этом вводном учебнике мы рассмотрим простой проект и пропустим его через различные компиляторы JavaScript, чтобы поиграться с source maps в браузере.

Что такое Source Maps?

Source Maps предлагают не зависимый от языка способ сопоставления production кода с исходным кодом.

Исходные карты предлагают не зависимый от языка способ сопоставления производственного кода с исходным кодом, который был создан в вашей среде разработки. Когда мы в конечном счете смотрим на базу кода, сгенерированную и подготовленную к production, становится очень сложно найти то место, где происходит сопоставление строк с нашим исходным авторским кодом. Однако во время компиляции source map хранит эту информацию, поэтому, когда мы запрашиваем строку, она вернет нам точное местоположение в исходном файле! Это дает огромное преимущество разработчику, поскольку код становится читаемым и даже отлаживается!

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

Браузеры

Обратите внимание, что при написании этой статьи Chrome (версия 23) поддерживает карты исходников JavaScript и даже карты SASS. Firefox также должен получить поддержку в ближайшем будущем, так как в настоящее время он находится в активной стадии разработки. Итак, давайте посмотрим, как мы можем использовать карты источников в браузере!

Source maps в Chrome

Во-первых, мы должны включить поддержку в Chrome, выполнив следующие простые шаги:




Настройка

Если вы хотите продвигаться вместе с этой статьей, загрузите демоверсию и откройте каталог «start». Файлы и структура каталогов довольно простые, с некоторым простым JavaScript внутри scripts/script.js . Вы должны иметь возможность открывать index.html и даже добавлять некоторые названия цветов CSS или шестнадцатеричные значения, чтобы изменить цвет фона.




Просмотрите простые файлы сценариев в простых JavaScript, TypeScript или CoffeeScript. Используя различные компиляторы JavaScript, мы создадим готовую для production версию, а также сгенерируем соответствующие карты.

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

Вариант A : компилятор Closure

Closure Compiler от Google - это инструмент для оптимизации JavaScript. Он делает это, анализируя ваш код, удаляя ненужные биты, а затем минимизируя остальные. Кроме того, он также может генерировать карты.

Для создания оптимизированной версии script.js , используя компилятор Closure, сделаем следующие шаги:




Когда мы открываем index.html в браузере и перейдем к Source панели в средствах разработчика, то будет ссылка только на оптимизированную версию script.closure.js ; у нас нет способа вернуть соотношение к нашему оригинальному, исходному файлу. Давайте создадим файл карты, выполнив следующую команду в каталоге scripts :

Обратите внимание, что Closure Compiler использует два варианта: --create_source_map и --source_map_format для создания файла карты script.closure.js.map с версией 3. Затем добавьте URL-адрес исходного URL-адреса в конец скомпилированного файла сценария, script.closure.js , так что оптимизированный файл содержит информацию о местоположении карты:

Теперь, когда мы просматриваем проект в браузере, в директории «scripts» под панелью Source инструментов разработчика будет показан как исходный файл, так и оптимизированная версия script.closure.js . Хотя браузер, конечно же, использует оптимизированный файл, на который мы первоначально ссылались в index.html , карты позволяют нам создать связь с исходным файлом.

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




Вариант B : задача GruntJS для JSMin

Если вы уже используете Grunt.js для процессов сборки, вам понадобится плагин Grunt для карт JSMin. Он не только оптимизирует ваш код, но также создаст карту!

Следующие шаги продемонстрируют, как создать оптимизированную версию script.js с плагином Grunt JSMin:

  1. установите Grunt.js и инициируйте gruntfile, grunt.js , в корне каталога:
  2. Установите Grunt-плагин grunt-jsmin-sourcemap; когда вы это сделаете, будет создан каталог с именем node_modules/grunt-jsmin-sourcemap :
  3. Отредактируйте вновь созданный файл grunt.js , чтобы он содержал только задачу jsmin-sourcemap - чтобы все было как можно проще.
  4. Вернитесь в командную строку и запустите grunt ; это выполнит задачу jsmin-sourcemap, поскольку задание по умолчанию указано как таковое в файле grunt.js:
  5. В файле созданной карты, script.grunt-jsmin.js.map , убедитесь, что источником является "sources":["script.js"] .
  6. Раскомментируйте вариант B для ссылки на вновь созданный файл, script.grunt-jsmin.js , в index.htm l, и откройте его в браузере.

С Grunt и плагином, jsmin-sourcemap, процесс сборки создал два файла: оптимизированный файл сценария с URL-адресом исходного источника, а также карту. Для просмотра всех их в браузере вам потребуются они оба.




Вариант C : UglifyJSё

UglifyJS2 - это еще один парсер JavaScript, minfier и компрессор. Подобно двум альтернативам выше, UglifyJS2 создаст оптимизированный файл сценария, добавленный с URL-адресом исходного источника, а также файл карты, который будет содержать сопоставление с исходным файлом. Чтобы использовать UglifyJS, выполните следующую команду в командной строке каталога «start»:

  1. Установите модуль NPM, uglify-js , локально; будет создан каталог node_module/uglify-js .
  2. Внутри каталога «scripts» мы выполним команду для создания оптимизированной версии, а также исходного файла с параметрами --source-map и --output , чтобы указать имя выходного файла.
  3. Наконец, убедитесь, что index.html правильно связан с скриптом script.uglify.js




Вариант D : CoffeeScript Redux

Для предыдущих трех вариантов нам потребовалась только одношаговая оптимизация - от исходного кода до оптимизированного JavaScript. Однако для таких языков, как CoffeeScript, нам нужен двухэтапный процесс: CoffeeScript > JavaScript > оптимизированный JavaScript. В этом разделе мы рассмотрим, как создавать многоуровневые карты с помощью CoffeeScript и компилятора CoffeeScript Redux.

Шаг 1: CoffeeScript для простого JavaScript

Перейдите в каталог «start» в командной строке. В следующих шагах мы сопоставим оптимизированный файл сценария с CoffeeScript:

  1. Установите CoffeeScript в качестве глобального пакета npm
  2. Скомпилируйте файл CoffeeScript, script.coffee.coffee , чтобы создать простую версию JavaScript, используя следующую команду:
  3. Установите CoffeeScript Redux:
  4. Затем мы создадим файл карты, script.coffee.js.map, который будет хранить информацию о преобразовании из сгенерированного JavaScript обратно в файл CoffeeScript:
  5. Убедитесь, что сгенерированный файл JavaScript, script.coffee.js , имеет URL-адрес исходного кода в конце со следующей строкой:
  6. Убедитесь, что файл карты, script.coffee.js.map , имеет правильный файл ссылки как "file":"script.coffee.coffee" и исходный файл как "sources":["script.coffee.coffee"]

Шаг 2: Обычный JavaScript для минификации-JavaScript

  1. Наконец, мы снова будем использовать UglifyJS, чтобы минимизировать сгенерированный JavaScript, а также создать карту. На этот раз это займет карту, чтобы мы могли вернуться к исходному файлу CoffeeScript. Выполните следующую команду в каталоге «scripts»:
  2. Наконец, убедитесь, что файл карты, script.coffee.min.js.map , имеет правильный ссылочный файл как "file":"script.coffee.min.js" и правильные источники как "sources":["script.coffee.coffee"] .




Вариант E : TypeScript

TypeScript, как и CoffeeScript, также требует двухэтапный процесс: TypeScript > Обычный JavaScript > Мини-код JavaScript. Поскольку скрипт использует плагин jQuery, нам нужны два файла TypeScript, которые уже предоставлены: script.typescript.ts и jquery.d.ts .

Шаг 1: TypeScript для простого JavaScript

Перейдите в каталог «scripts» из командной строки и выполните следующую команду:

Вышеприведенная команда создаст новый файл JavaScript, с именем script.typescript.js , с url внизу: //@sourceMappingURL=script.typescript.js.map . С помощью этой единственной команды он также создаст файл карты, script.typescript.js.map .

Шаг 2: Обычный JavaScript для минифицированного JavaScript

Как и в примере с CoffeeScript, следующим шагом будет использование UglifyJS.

Наконец, убедитесь, что index.htm l ссылается на правильный файл сценария, scripts/script.typescript.min.js и откройте его в браузере!




Карты для SASS

Помимо JavaScript, в настоящее время Chrome также поддерживает карты SASS или SCSS. Для сопоставления исходника SASS исправьте несколько настроек в Chrome, а затем скомпилируем SASS в CSS с параметрами отладки:

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













Помимо просто просмотра файла SASS, если вы используете LiveReload в фоновом режиме и вносите изменения в файл SASS, страница также будет обновляться, чтобы отразить изменения. Например, давайте откроем проект в Firefox и проверим страницу, используя расширение Firebug.




Информация на карте

Если мы просмотрим любой из файлов *.map , он будет содержать информацию о сопоставлении из исходного файла в оптимизированный файл. Структура карты обычно находится в формате JSON, используя спецификации версии 3. Обычно он содержит следующие пять свойств:

  1. version: номер версии карты - обычно «3.»
  2. file: имя оптимизированного файла.
  3. sources: Имена исходных файлов.
  4. names: Символы, используемые для сопоставления.
  5. mappings: отображение данных.




Дополнительные ресурсы

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

  • Введение в карты JavaScript Райана Седдона, HTML5 Rocks
  • Эпизод 3 точки останова: карты JavaScript от команды разработчиков Google
  • Эпизод 2 точки останова: карты SASS от команды разработчиков Google
  • Source Maps wiki о языках, инструментах и статьях об картах.
  • Многоуровневые карты с использованием CoffeeScript и TypeScript Райана Седдона

Заключение

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

недавно я видел файлы с .js.map расширение поставляется с некоторыми библиотеками JavaScript (например,Угловое), и это просто вызвало несколько вопросов в моей голове:

  • что это? Почему ребята в Angular care доставляют ?
  • как я могу (как разработчик JavaScript) использовать ?
  • должен ли я заботиться о создании .js.map файлы для моих приложений JavaScript?
  • как это сделать создан? Я взглянул на angular.min.js.map и он был заполнен строками странного формата, поэтому я предполагаю, что он не создан вручную.

на .map файлы для js и css (теперь ts too) файлы, которые были уменьшены. Они называются SourceMaps. Когда вы уменьшаете файл, например, угловой.JS-файл, он занимает тысячи строк красивого кода и превращает его всего в несколько строк уродливого кода. Надеюсь, когда вы отправляете свой код в производство, используется код сокращен вместо полного, unminified версии. Когда ваше приложение находится в производстве и имеет ошибку, sourcemap поможет вам уродливый файл, и позволит вам увидеть исходную версию кода. Если у вас нет исходной карты, любая ошибка будет казаться в лучшем случае загадочной.

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

Итак, чтобы ответить на ваши вопросы по порядку:

Я надеюсь, что это имеет смысл.

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

  1. грунт: используя плагин grunt-contrib-uglify
  2. залпом: используя плагин gulp-uglify
  3. закрытие Google: используя параметр --create_source_map

Я не знаю, есть ли другие инструменты, которые могут создавать исходные карты.

как разработчик может использовать его? Я не нашел ответа на это в комментариях, вот как можно использовать:

когда минута.js и js.файлы карт готовы.

  1. Chrome: откройте dev-tools, перейдите на вкладку "Sources", вы увидите папку "sources", где хранятся файлы приложений unminifide

файл карте карты файл unminified в файл минифицированный. Если вы внесете изменения в файл unminified, то изменения будут автоматически отражены в уменьшенной версии файла.

для угловых файлов, с которыми я работал сегодня, я нажимаю

Ctrl-P и в маленьком окошке появляется список оригинальных файлов.

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

Давайте посмотрим на объяснение этих 7 режимов в документации:

Режим объяснение
eval Каждый модуль будет инкапсулирован в eval, упакован и выполнен, и в конце будет добавлен комментарий //@ sourceURL .
source-map Генерация одногоSourceMapфайл.
hidden-source-map То же, что и карта-источник, но комментарии не будут добавлены в конец пакета.
inline-source-map СоздатьDataUrlФайл SourceMap в форме.
eval-source-map Каждый модуль будет выполнен eval () и сгенерирует SourceMap в форме DataUrl.
cheap-source-map Создать файл SourceMaps без информации о столбцах (сопоставления столбцов), исключая исходную карту загрузчика (например, исходную карту babel)
cheap-module-source-map Создайте файл SourceMaps без информации о столбцах (сопоставления столбцов), а также упростите исходную карту загрузчика, чтобы она содержала только соответствующие строки.

Примечание 1:

Webpack не только поддерживает эти 7 типов, но также может комбинировать перечисленные выше встроенные скрытые ключевые слова eval, как сказано в документации, вы можете установить опцию souremap на cheap-module-inline-source-map.

Заметка 2:

Если ваши модули уже содержат SourceMaps, вам нужно использоватьsource-map-loaderПриходите и объединяйтесь для создания новых SourceMaps.

Чем отличаются результаты

Ниже мы перечислим результаты этих 7 режимов после упаковки и компиляции и увидим их сходства и различия:

В режиме eval каждый модуль оборачивается в eval и переносится для выполнения, а в конце добавляются комментарии.

Each module is executed with eval and //@ sourceURL .

В то же время в выходном каталоге вы найдете дополнительный файл index.js.map.

Мы можем отформатировать этот index.js.map для облегчения нашего наблюдения и сравнения ниже:

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

Заинтересованные студенты могут обратиться к научно-популярной статье Руан Ифэн:Подробная исходная карта JavaScript

По сравнению с исходной картой меньше конечных комментариев,

Но в выходном каталоге не меньше index.js.map

Как вы можете видеть, комментарий в конце, sourceMap, внедряется в пакет в виде DataURL. Поскольку вся информация sourceMap добавляется в пакет, весь файл пакета становится очень большим.

Аналогично eval, но все исходные карты в комментариях преобразуются в DataURL.

Аналогично результатам, генерируемым картой источника. Содержимое index.js в выходном каталоге одинаково.

Но содержимое файла index.js.map, созданного с помощью cheap-source-map, намного меньше, чем index.js.map, сгенерированного source-map. Давайте сравним результаты index.js.map, сгенерированного source-map выше. Обнаружил, что в исходном атрибуте меньшеКолонка информации, Остался только один "webpack:///js/index.js" 。

В дешевом модуле-источнике-карте sourceMap имеет меньше контента,Информация столбца SourceMapСокращенный, вы можете увидеть, что SourceContent ушел.

Какой из них лучше для стольких режимов?

развитиеЭкологическая рекомендация:

cheap-module-eval-source-map

производитьЭкологическая рекомендация:

cheap-module-source-map

Это также опция по умолчанию для следующей версии веб-пакета при использовании команды -d для запуска режима отладки.

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

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

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

Читайте также: