Юмор на ютубе, появление шоу TALK

У меня в принципе нет в квартире телевизора. Я, в общем-то, не считаю телезрителей быдлом, а телевизор — устройством для промывки мозгов. Просто в какой-то момент лет десять назад телевизор сломался, заниматься покупкой нового мне было лень, а потом я как-то привык жить без него. Сейчас телевизор мне заменяет ютуб.

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

Скажу только, что шоу «ИГРА» на телеканале «ТНТ», которое сейчас повсюду пиарят, не стало для меня чем-то особенным.

Зато я неожиданно подсел на выпуски шоу «TALK». Я и не знал про существование этого шоу, пока случайно не наткнулся на отрывки из него в ютубе.

Первый сезон этого шоу вышел в сентябре прошлого года на телеканале «ТНТ». В шоу участвуют четыре человека: Тимур Каргинов, Нурлан Сабуров, Руслан Белый и Азамат Мусагалиев. Первых двух я вообще ранее ни разу до этого не видел, хотя, говорят, они популярны в качестве стендаперов. Выступления Руслана Белого слушал неоднократно, но не являюсь его фанатом. Азамата Мусагалиева я уже знал хорошо по его выступлениям в составе команды КВН «Камызяки».


Постер первого сезона шоу «TALK», 2020 год

Для меня это шоу в юморе стало открытием. Просто сидят четыре человека на стульях и разговаривают с друг другом на разные темы. Так сказать, «о чём говорят мужчины».

Ощущение после просмотра примерно такое же, как будто посмотрел пару серий сериала «Друзья».

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

Телеканал «ТНТ» щедро выложил на ютуб для бесплатного просмотра первый и последний выпуски первого сезона. Рекомендую к просмотру:

https://www.youtube.com/watch?v=G35drMjAhYQ (премьерный выпуск первого сезона)
https://www.youtube.com/watch?v=JUNwI3ai32U (12-й выпуск первого сезона)

Где посмотреть онлайн за деньги:

https://talk.tnt-online.ru/
https://premier.one/show/talk

JavaScript: Blob

Прочел подраздел 2.3 «Blob» третьей части учебника по JavaScript.

Для меня эта статья оказалась настолько объемной в плане нового, что ее разбор вылился в целый ряд постов:

1. Браузер: настройка загрузки файлов
2. Капля: от фантастического ужаса до программирования
3. HTML: новые и старые особенности работы со ссылкой
4. Как работает кодирование Base64
5. Как работает кодирование Base64, окончание
6. Сказка про двоичные данные, кодировку Windows-1251 и Юникод

Blob — это встроенный объект в языке JavaScript. Он представляет кусок двоичных данных с типом. С его помощью мы имеем доступ к двоичному содержимому любого файла. Можем в скрипте формировать файлы, а затем заставлять браузер запускать загрузку этого файла на компьютер пользователя.

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

То, что URL — это строка, определяющая адрес ресурса (файла) в интернете, я уже знаю довольно давно. Для работы с этой строкой в скриптах на языке JavaScript был создан веб-API (объект, содержащий набор функций, переменных и констант для определенной работы), который тоже назвали «URL». В обсуждаемом подразделе учебника используется этот веб-API «URL».

https://developer.mozilla.org/en-US/docs/Web/API/URL

В подразделе рассматриваются два способа работы с куском двоичных данных, используя веб-API «URL» и встроенный объект FileReader. При работе первым способом созданный кусок двоичных данных с типом сохраняется в памяти компьютера пользователя, как отдельный файл. Для доступа к нему используется адрес URL, начинающийся на «blob:». Для идентификации куска двоичных данных в памяти компьютера пользователя используется так называемый UUID. UUID — это стандарт идентификации, а также, собственно, сам уникальный идентификатор. Этот стандарт позволяет создавать идентификаторы, которые будут гарантированно уникальны, даже если их будут создавать разные программы, действующие абсолютно независимо друг от друга (конечно, если эти программы используют данный стандарт).

https://ru.wikipedia.org/wiki/UUID

При работе вторым способом созданный кусок двоичных данных с типом помещается не в память компьютера пользователя, а в сам текст (строку) адреса URL. Такой адрес URL начинается с «data:», еще такие адреса называют «Data URL». При этом кусок двоичных данных трансформируется в текст (так как он должен храниться в тексте адреса URL) с помощью кодирования Base64 (я его разбирал в двух постах, см. ссылки выше).

Пример текста (строки) адреса URL при работе первым способом:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273

Здесь 1e67e00e-860d-40a5-89ae-6ab0cbee6273 — это универсальный идентификатор стандарта UUID. Сам он не содержит данных, а является лишь идентификатором, связанным с данными, хранящимися в памяти компьютера пользователя. Один и тот же скрипт при разных запусках HTML-страницы, содержащей его, формирует разные UUID. То есть при каждом обновлении такой HTML-страницы идентификатор UUID тоже будет меняться. Это можно увидеть на практике, взяв код примера с этим способом из обсуждаемого подраздела учебника.

Пример текста (строки) адреса URL при работе вторым способом:
data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7

В этом случае данные содержатся именно в этом URL. Вот эти данные:
R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7

При этом способе браузеру придется для использования этих данных (загрузки на компьютер пользователя) сначала раскодировать их, так как они закодированы способом Base64.

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

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

Сказка про двоичные данные, кодировку Windows-1251 и Юникод

Вопрос из комментариев к подразделу 2.3 «Blob» третьей части учебника по JavaScript, цитата:

Не могу найти способ выдавать файл с кодировкой windows-1251. В любом случае получается utf-8

link.href = URL.createObjectURL(blob);
link.click();


Тут непонимание одной из двух вещей. Либо спрашивающий не понимает, что в данном подразделе учебника (и предыдущих подразделах) нам объясняют и дают в руки инструмент для обращения с двоичными данными, а, следовательно, с любыми данными, потому что в компьютере любые данные в конечном счете хранятся в двоичном виде.

Либо спрашивающий не понимает (плохо понимает), что такое кодировка символов.

* * *

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

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

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

Итого 62 штуки символов. Для представления 62 разных символов в двоичной системе требуются числа не менее, чем с 6 разрядами (битами). Следовательно, решили люди, каждый символ можно представлять группой (двоичным числом) из 6 бит. Так появились байты. По-английски «byte». Созвучно с другим английским словом «bite» (по-русски «откусывать», «укус»). Можно сказать, люди откусывали по 6-битному кусочку от общего куска двоичных данных, чтобы хранить символы в двоичном виде. Второе, что любому начинающему программисту нужно понимать, что изначально слова «байт» и «символ» были одним и тем же. Например, в дедушке языков программирования «Си» мы говорим «char» — подразумеваем «байт», мы говорим «байт», подразумеваем «char» (напомню, в данном случае «char» — это сокращение от английского слова «character», которое в данном случае по-русски означает «символ»).

Кто-то может удивиться, но когда-то байты могли быть длиной в 6 бит.

Вскоре, однако, люди вспомнили, что в текстах есть не только буквы и цифры, но еще знаки препинания, пробелы и вообще. Тогда байт решили увеличить до 7 бит. С помощью 7 бит можно представить 128 символов. Так появилась знаменитая таблица ASCII (если что, данная аббревиатура читается как «аски» с ударением на первый слог). Шёл 1963 год.

https://ru.wikipedia.org/wiki/ASCII

Через некоторое время появились персональные компьютеры (ПК), которые пошли в народ. Компьютерами стали пользоваться не только англоязычные пользователи, но и люди, говорящие на других языках. А в таблице ASCII есть только буквы латинского алфавита. Чтобы как-то выкрутиться, байт увеличили в очередной раз до 8 бит. С таким байтом можно представить 256 символов. Сейчас уже все привыкли, что один байт — это 8 бит. Устройства с байтами в 6-7 бит либо канули в Лету, либо остались в каких-то очень узких нишах. Специалисты придумали называть байт с 8 битами «октетом», чтобы отличать его от байтов с 6 или 7 битами. Но кто в наше время слушает специалистов? Любому прогрессивному человеку понятно, что байт состоит из 8 бит, поэтому и нечего выдумывать разные «октеты».

Как программисты решали проблему разных языков с помощью таблицы из 256 символов? Первая половина таблицы (128 символов) совпадала с таблицей ASCII. Во второй половине таблицы (тоже 128 символов) можно было расположить символы еще одного какого-нибудь алфавита и еще какие-нибудь нужные символы. В разные страны стали продавать ПК с разными таблицами из 256 символов. Во всех случаях в первой половине такой таблицы содержалась таблица ASCII, а во второй половине располагали алфавит той страны, в которую продавался ПК. В России такая таблица называлась «расширенной таблицей ASCII» и во второй половине содержала буквы русского алфавита.

Однако, производители программного обеспечения (ПО) были недовольны: при выходе на международный рынок приходилось писать свою версию ПО для каждой страны. Каждый решал эту проблему по-своему. Компания «Microsoft» стала выпускать свою операционную систему «Windows» сразу с целым набором таблиц символов (кодовых страниц). В частности, был набор таблиц символов, который в совокупности называли «кодировки ANSI». Вообще, ANSI — это «Американский национальный институт стандартов», довольно известная организация. Не знаю, какая здесь связь. Полагаю, организация ANSI помогала компании «Microsoft» разрабатывать данные кодировки, за что и удостоилась чести быть упомянутой. Каждая из таблиц символов из набора «ANSI» представляет собой таблицу из 256 символов, в первой части которой содержится таблица ASCII, а во второй части — таблица какого-либо национального алфавита. Для России такая таблица в операционной системе «Windows» называется «Windows-1251».

https://ru.wikipedia.org/wiki/Windows-1251

На сегодня все таблицы из 256 символов устарели в связи с появлением таблицы символов Юникода и его представлений (UTF-8, UTF-16 и так далее). При этом дальнейшее увеличение бит в байте сочли нецелесообразным. Для увеличения таблицы решили, что можно один символ представить несколькими байтами. Это позволило в одной таблице Юникода представить несколько миллионов символов. Такое количество позволило вставить в одну таблицу символов алфавиты всех языков Земли. Таким образом, проблема множества языков при представлении символов в компьютере оказалась решена. Можно догадаться, почему такую таблицу назвали «Юникодом»: потому, что эта таблица содержит универсальную (начало английского слова «universal» стало началом названия «Юникод») кодировку, то есть кодировку, которую могут использвать все народы Земли.

Однако, можно понять тех, кто ищет способы работы с устаревшей кодировкой «Windows-1251». Дело в том, что существует так называемый «legacy-код» (от английского слова «legacy», что по-русски означает «наследие»). Под этим термином в данном случае имеется в виду программное обеспечение, написанное во времена, когда Юникод еще не завоевал рынок (причем завоевывал он его довольно долго: появился в 1991 году, а побеждать начал примерно с 2008 года). Вообще, «legacy-код» — это старое программное обеспечение, которое, однако же, всё еще находится в эксплуатации и всё еще требует обслуживания программистами (исправления, переделки под новые реалии и так далее).

* * *

Вернемся к вопросу из начала поста. Автор вопроса приводит только часть кода, причем не самую важную. Полностью код примера из подраздела выглядит так, цитата:
let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Привет, мир!'], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);

link.click();

URL.revokeObjectURL(link.href);

Я только заменил здесь фразу «Hello, world!» на «Привет, мир!», чтобы было нагляднее, ведь для латинских букв разницы между кодировкой «Windows-1251» и кодировкой «UTF-8» (одно из представлений Юникода) нет, потому что начало обеих этих таблиц символов совпадает между собой и является таблицей ASCII (которая, как мы помним, содержит латинский алфавит).

Запустив HTML-страницу с этим скриптом, мы заставим браузер загрузить (или выдать пользователю запрос на загрузку, в зависимости от настроек, после чего пользователь может дать или не дать согласие на загрузку) файл «hello.txt» на наш компьютер. В файле будет содержаться фраза «Привет, мир!» в кодировке «UTF-8». Размер файла — 21 байт.

Как мы помним, для компьютера символы — это числа. Что в файле содержится на самом деле? Это следующие числа (про двоичный редактор я писал в предыдущем посте):



или то же самое, только не картинкой:

D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82 2C 20 D0 BC D0 B8 D1 80 21

В кодировке «UTF-8» каждая русская буква представляется двумя байтами (у нас таких букв 9, значит, они занимают 18 байт). Оставшиеся три символа (запятая, пробел и восклицательный знак) занимают каждый по одному байту. Всего получается 21 байт, это размер нашего файла, как уже было указано выше. Берем таблицу Юникода (например, отсюда: https://unicode-table.com/ru/) и расшифровываем эти числа:

D0 9FD1 80D0 B8D0 B2D0 B5D1 822C20D0 BCD0 B8D1 8021
Привет, мир!

«Превращение» строки «Привет, мир!» в числа происходит в следующей строке скрипта:
let blob = new Blob(['Привет, мир!'], {type: 'text/plain'});

После чего в переменной blob оказывается соответствующий кусок двоичных данных. Отметим, что в этой строке скрипта нигде не указана кодировка текста, хотя преобразование текста в числа происходит, а для этого необходимо использовать какую-то кодировку (таблицу кодирования символов). Дело в том, что по спецификации, в которой регламентируется работа конструктора Blob, кодировка «UTF-8» должна использоваться по умолчанию.

Что же делать, если нам нужно выгрузить файл, содержащий текст не в кодировке «UTF-8»? (В данном случае нам нужно выгрузить файл, содержащий текст в кодировке «Windows-1251».) Для этого в скрипте нужно работать не с текстом, а напрямую с числами. Ведь нам только что в учебнике даны пояснения по работе с куском двоичных данных напрямую (даны инструменты для этого).

Возьмем наш текст: «Привет, мир!» и превратим его в числа по таблице кодировки «Windows-1251». В этой кодировке любой символ представляется одним байтом. Значит, у нас должен получиться файл размером в 12 байт (в строке «Привет, мир!» содержится 12 символов). Используем таблицу кодировки «Windows-1251» (например, отсюда: https://ru.wikipedia.org/wiki/Windows-1251):

Привет, мир!
CFF0E8E2E5F22C20ECE8F021

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

CF F0 E8 E2 E5 F2 2C 20 EC E8 F0 21

Или то же самое в десятичной системе:

207 240 232 226 229 242 44 32 236 232 240 33

Перепишем это на язык JavaScript:
let arr = new Uint8Array([0xCF, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x2C, 0x20, 0xEC, 0xE8, 0xF0, 0x21]);

Или то же самое в десятичной системе:
let arr = new Uint8Array([207, 240, 232, 226, 229, 242, 44, 32, 236, 232, 240, 33]);

Полный код итогового скрипта (я пометил изменения красным цветом):
let link = document.createElement('a');
link.download = 'hello.txt';

let arr = new Uint8Array([0xCF, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x2C, 0x20, 0xEC, 0xE8, 0xF0, 0x21]);
let blob = new Blob([arr], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);

link.click();

URL.revokeObjectURL(link.href);

Запустив HTML-страницу с этим скриптом, мы заставим браузер загрузить файл «hello.txt» на наш компьютер. Размер этого файла составит 12 байт. Этот файл будет содержать текст «Привет, мир!» в кодировке «Windows-1251».

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

Конечно, по-хорошему нужно еще иметь функцию, которая будет преобразовывать любой заданный текст в числа по таблице кодировки «Windows-1251». Не скажу, что написание такой функции является суперлегкой задачей, но это и не слишком сложная задача. Подобные задачи в языках программирования решаются, когда ученик проходит тему массивов и других коллекций вроде объектов, map, set и так далее и манипуляций с этими коллекциями.

Как работает кодирование Base64, окончание

Начало: «Как работает кодирование Base64».

Пример второй. «Картинка-смайлик»

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

В подразделе 2.3 «Blob» третьей части обсуждаемого учебника по JavaScript такой пример с изображением есть, но он приведен как-то слишком вскользь, практически в двух словах. Что там есть?

Код на языке HTML:
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">

И пример вставки этого кода на HTML-страницу указанного подраздела учебника. (Я попробовал вставить этот код в данный пост, но ЖЖ не отображает этот HTML-элемент. Видимо, «Data URL» в ЖЖ запрещены.) Вот фрагмент (кусочек снимка экрана) HTML-страницы указанного подраздела учебника с вставленным описанным выше образом изображением (картинка-смайлик):


В учебнике не объяснено подробно, как был получен такой «Data URL». Давайте разбираться.

Я скачал эту картинку-смайлик к себе на компьютер (нажал на картинку-смайлик в браузере правой кнопкой мыши и выбрал пункт контекстного меню «Save image as») под предложенным браузером названием «download.png» (тип файла «png», как видно из кода выше, указан в «Data URL», а название «download», как я понимаю, мой браузер даёт файлам, названия которых не знает, так как оно в HTML-элементе не указано).

Загруженный файл «download.png» имеет размер 96 байт. Я попробовал открыть его в своих браузерах. В современном «Microsoft Edge» (на движке «Chromium») этот файл открылся, а на стареньком «Internet Explorer 11» — нет. Почему?

Как оказалось, авторы учебника ошиблись: это файл формата GIF, а не PNG. В этом можно убедиться, открыв данный файл в любом текстовом редакторе. Конечно, так как данный файл является куском двоичных данных, содержащим информацию об изображении, то в текстовом редакторе по большей части будут показаны кракозябры. Однако, самое начало данного файла будет содержать вполне читаемые 6 символов: «GIF89a». Это так называемая «сигнатура», которая служит для идентификации ресурса. Вообще формат GIF имеет две версии: «GIF87a» и «GIF89a». Здесь цифры обозначают год разработки версии формата: 1987 год и 1989 год.

Современные браузеры, как я понимаю, определяют формат картинок в первую очередь по сигнатуре, а старенький «Internet Explorer 11» — по расширению файла. Поэтому современный браузер «Microsoft Edge» (на движке «Chromium») правильно отобразил данный файл, несмотря на неправильно указанное расширение файла, а «Internet Explorer 11» эту картинку вообще не отобразил.

Как только я поменял название файла на «download.gif», он стал отображаться в обоих указанных выше браузерах. Так что «Internet Explorer 11» еще бывает полезен. Вообще, я использую его для просмотра картинок. У него есть несколько интересных способностей, которых нет у современных браузеров.

* * *

Чтобы рассмотреть содержимое куска двоичных данных, нужен специальный редактор, который называют «двоичным редактором». В прошлом году я писал пост о таком редакторе в среде «Visual Studio Community 2017». Конечно, у меня еще есть текстовые редакторы «Notepad++» и «Visual Studio Code», а в них, как пишут в интернетах (я не проверял), вроде бы есть возможность установить специальные плагины, работающие в качестве двоичного редактора. Однако, я решил пока не разбираться с этими плагинами, а воспользоваться указанным выше двоичным редактором, которым уже пользовался. С тех пор я установил «Visual Studio Community 2019» и там этот редактор тоже есть.

Откроем файл «download.gif» в двоичном редакторе (фрагмент снимка экрана):



Содержимое файла «download.gif» здесь находится в центральной части. Слева указаны номера строк (это числа в шестнадцатиричной системе). Справа — редактор пытается интерпретировать байты содержимого файла как символы в таблице ASCII. Как я понимаю, точками отображаются коды символов, которые не имеют графического представления в таблице ASCII. В принципе, на правую часть редактора можно не обращать внимания.

Каждый байт содержимого файла «download.gif» изображен двумя шестнадцатиричными цифрами. Для удобства просмотра и редактирования байты отделены друг от друга пробелами. В самом файле, естественно, этих пробелов нет. Эти пробелы нужны только человеку, компьютер разберется и без них. В каждой строке редактора отображается по 16 байт. Всего строк в данном случае шесть, то есть размер файла составляет 6 × 16 = 96 байт. Этот размер уже упоминался ранее.

Номер каждой строки слева равен количеству байт в предыдущих строках. Например, номер шестой строки — «00000050» или просто «50». Это число в шестнадцатиричной системе. В десятичной системе оно равно 80. Таким образом, получается, что в строках до последней (в первых пяти строках) содержится 80 байт. В последней строке содержится 16 байт, всего в файле 80 + 16 = 96 байт.

Когда я впервые познакомился с двоичным редактором, у меня сразу же возник вопрос: если редактор называется «двоичным», почему в нем используются шестнадцатиричные цифры и числа? Дело в том, что если в редакторе содержимое будет отображено в двоичном виде (нулями и единицами), то такую информацию будет воспринять тяжелее. А шестнадцатиричные цифры, отображающие байты, дают пользователю редактора полную власть над любым байтом, вплоть до каждого бита. Как это получается?

Возьмем для примера первый байт данного файла:

4716 = 0100 01112

Я специально отделил четыре старших бита от четырех младших бит в байте пробелом (в самом файле, естественно, такого пробела нет). Можно заметить, что шестнадцатиричная цифра «4» отвечает за старшие четыре бита, а шестнадцатиричная цифра «7» отвечает за младшие четыре бита. Предположим, нам нужно поменять один бит в этом двоичном числе. К примеру, мы хотим, чтобы вместо 0100 01112 получилось 0110 01112. Двоичное число 01102 — это шестнадцатиричная цифра 616, значит, нам нужно поменять «4» на «6»:

6716 = 0110 01112

Таким образом, редактируя шестнадцатиричные цифры, мы можем поменять любой отдельный бит двоичного числа. Из-за того, что в двоичном редакторе для удобства используются шестнадцатиричные цифры и числа, его еще часто называют «шестнадцатиричным редактором» или «hex-редактором» (здесь «hex» — сокращение от английского слова «hexadecimal», что на русский переводится как «шестнадцатиричный»).

* * *

Представим содержимое файла «download.gif» (кусок двоичных данных) в скрипте на языке JavaScript:
let arr = new Uint8Array(
    [0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0C, 0x00, 0x0C, 0x00, 0xA2, 0x05, 0x00, 0x5E, 0x4B, 0x00,
     0xFF, 0xF3, 0xC4, 0x00, 0x00, 0x00, 0xDC, 0xAE, 0x00, 0xFF, 0xE0, 0x68, 0xFF, 0xFF, 0xFF, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x05, 0x00, 0x2C, 0x00, 0x00,
     0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x03, 0x25, 0x58, 0xB3, 0xDC, 0x1A, 0x30, 0xCA, 0x15,
     0x88, 0xBD, 0x97, 0x12, 0x81, 0x39, 0xA1, 0x9C, 0x17, 0x7E, 0x43, 0x85, 0x61, 0xDA, 0x99, 0x95,
     0x16, 0xD0, 0xAE, 0x26, 0x01, 0xB8, 0xB0, 0x5A, 0xDB, 0x9F, 0xD2, 0xEC, 0x45, 0x02, 0x00, 0x3B]);
let blob = new Blob([arr], {type: "image/gif"});

Сравните это с картинкой из двоичного редактора, представленной ранее. Я перенес шестнадцатиричные числа, представляющие байты содержимого файла «download.gif», в том же виде, только добавил к каждому шестнадцатиричному числу, представляющему байт, слева обозначение «0x». Так в языке JavaScript обозначаются шестнадцатиричные числа, чтобы их можно было отличить от десятичных (вообще, есть еще обозначения для восьмиричных и двоичных чисел).

А далее используем скрипт, который был показан в первом примере в предыдущем посте, изменив его под HTML-элемент img:

Интересная нам часть кода на HTML-странице:
Браузер декодирует строку и показывает смайлик: <img src="" id="image">

Скрипт на языке JavaScript:
let arr = new Uint8Array(
    [0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0C, 0x00, 0x0C, 0x00, 0xA2, 0x05, 0x00, 0x5E, 0x4B, 0x00,
     0xFF, 0xF3, 0xC4, 0x00, 0x00, 0x00, 0xDC, 0xAE, 0x00, 0xFF, 0xE0, 0x68, 0xFF, 0xFF, 0xFF, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x05, 0x00, 0x2C, 0x00, 0x00,
     0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x03, 0x25, 0x58, 0xB3, 0xDC, 0x1A, 0x30, 0xCA, 0x15,
     0x88, 0xBD, 0x97, 0x12, 0x81, 0x39, 0xA1, 0x9C, 0x17, 0x7E, 0x43, 0x85, 0x61, 0xDA, 0x99, 0x95,
     0x16, 0xD0, 0xAE, 0x26, 0x01, 0xB8, 0xB0, 0x5A, 0xDB, 0x9F, 0xD2, 0xEC, 0x45, 0x02, 0x00, 0x3B]);
let blob = new Blob([arr], {type: "image/gif"});

let reader = new FileReader();
reader.readAsDataURL(blob);    // конвертирует blob в base64 и вызывает onload

reader.onload = function() {
    image.src = reader.result; // текст (строка) адреса URL с вставленным в нее
                               // куском двоичных данных blob
};

Если запустить HTML-страницу с этим скриптом, он заменит содержимое атрибута src нашей картинки и получится следующее:
<img src="data:image/gif;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7" id="image">

Это как раз то же самое, что мы видели в начале поста.

Дальнейший подробный разбор того, как в этом случае двоичные данные были преобразованы в этот «Data URL», думаю, будет лишним, так как он будет аналогичным тому, который я делал для первого примера. Чтобы совсем закрыть вопрос, для проверки сделаем кодирование Base64 для первой группы из трех байт двоичного содержимого файла «download.gif».

Итак, у нас есть следующие первые три байта:

47 49 46

Переведем в двоичный вид:

01000111 01001001 01000110

Сгруппируем в кусочки по 6 бит:

010001 110100 100101 000110

По таблице кодирования Base64 (например, отсюда: https://ru.wikipedia.org/wiki/Base64) переведем 6-битные кусочки в символы:

R 0 l G

Эти четыре символа совпадают с началом куска двоичных данных в полученном выше «Data URL» для нашей картинки (я отметил их красным цветом):
<img src="data:image/gif;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7" id="image">

Если проделать подобные преобразования для всего содержимого файла «download.gif», то получим именно указанный «Data URL».

Как работает кодирование Base64

Я заинтересовался кодированием Base64 в связи с подразделом 2.3 «Blob» третьей части учебника по JavaScript. Но вообще это кодирование не является только одним из инструментов языка JavaScript и было придумано не конкретно для JavaScript, оно используется во многих языках программирования и не только.

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

Дополнительное чтение:
https://en.wikipedia.org/wiki/Binary-to-text_encoding
https://ru.wikipedia.org/wiki/Транспортное_кодирование

Для представления двоичных данных должны быть выбраны «безопасные» символы. Что тут имеется в виду под «безопасностью»? «Безопасные» символы — это такие символы, которые не изменятся при каких-то перекодировках в процессе передачи текста по каналам связи. Кроме того, это должны быть так называемые «печатные символы» (символы, имеющие графическое представление).

Кодирование Base64 как раз и выполняет эту задачу: переводит куски двоичных данных в текстовые символы. При этом в качестве «безопасных» символов взяты символы из таблицы ASCII, так как эта таблица включена в самые популярные на сегодня кодировки. Самое главное, что она содержится во всех представлениях Юникода. Конечно, она не полностью «безопасна», но это самый подходящий кандидат на сегодня.

В таблице ASCII всего 128 символов, из которых 95 являются «печатными символами». Для кодирования Base64 необходимо из этих 95 символов выбрать 64 символа для кодирования куска двоичных данных и 1 символ в качестве служебного, подробнее о нём будет рассказано ниже. Думаю, теперь должно стать понятно, почему рассматриваемый способ кодирования называется «Base64» (в его основе лежит использование 64 символов текста).

Изначально из 95 «печатных символов» таблицы ASCII выбрали буквы латинского алфавита, цифры и два дополнительных символа «+» и «/». Посчитаем: 26 прописных букв латинского алфавита в диапазоне «A..Z», 26 строчных букв латинского алфавита в диапазоне «a..z», 10 цифр в диапазоне «0..9» и два дополнительных символа «+» и «/» — итого получается 64 символа. В качестве 1 служебного символа выбрали символ «=» (вообще получается, что это 65-й символ, использующийся в кодировании Base64, но нужно понимать, что он используется для служебных целей, а не для представления куска двоичных данных).

* * *

На данный момент мне нужно будет (в связи с упомянутым в начале статьи подразделом учебника по JavaScript) представлять кусок двоичных данных в тексте (строке) адреса URL. И тут возникают сложности из-за выбранных выше для кодирования Base64 символов. Точнее, сложности возникают только из-за символов «+» и «/».

Почему из-за этих символов в тексте (строке) адреса URL возникают сложности? В тексте (строке) адреса URL по стандарту разрешается использовать лишь латинские буквы, цифры и символы «-» (дефис), «_» (подчеркивание), «.» (точка) и «~» (тильда). Остальные символы перекодируются с помощью так называемой «процентной кодировки». Кроме того, в стандарте адреса URL определены так называемые «зарезервированные символы» (их 18), в состав которых входят символы «+» и «/». Стандарт адреса URL конфликтует с кодированием Base64.

Как избежать этого конфликта? Умные люди предложили, что если кодирование Base64 используется для вставки куска двоичных данных в текст (строку) адреса URL, то именно в этом случае вместо символов «+» и «/» следует использовать «безопасные» в данном случае символы «-» (дефис) и «_» (подчеркивание), упомянутые выше. Таким образом, для этого случая при кодировании Base64 будем использовать буквы латинского алфавита (всего 52 символа, в том числе 26 прописных и 26 строчных), цифры (всего 10) и два дополнительных символа: «-» (дефис) и «_» (подчеркивание). Для служебных целей будет использоваться, как было сказано выше, символ «=».

Дополнительное чтение:
https://ru.wikipedia.org/wiki/URL#Кодирование_URL
https://datatracker.ietf.org/doc/html/rfc3986 (стандарт адреса URL)
https://datatracker.ietf.org/doc/html/rfc4648#section-4 (кодирование Base64)
https://datatracker.ietf.org/doc/html/rfc4648#section-5 (кодирование Base64 для URL)

* * *

Чтобы лучше понять, как работает кодирование Base64, я решил разобрать два примера из подраздела 2.3 «Blob» третьей части обсуждаемого учебника. Там само кодирование Base64 не разбирается детально. Видимо, предполагается, что читатель уже знаком с ним.

Пример первый. «Hello, world!»

Я слегка переделал пример из учебника:

Интересная нам часть кода на HTML-странице:
<a download="hello.txt" href="#" id="link">Загрузить</a>

Скрипт на языке JavaScript:
let blob = new Blob(["Hello, world!"], {type: "text/plain"});

let reader = new FileReader();
reader.readAsDataURL(blob);    // кодирует blob в base64 и вызывает onload

reader.onload = function() {
    link.href = reader.result; // текст (строка) адреса URL с вставленным в нее
                               // куском двоичных данных blob
};

Если запустить HTML-страницу с этим скриптом, то он заменит содержимое атрибута href нашей ссылки следующим образом. Было до запуска скрипта:
href="#"
Стало после запуска скрипта:
href="data:text/plain;base64,SGVsbG8sIHdvcmxkIQ=="

Здесь используются возможности так называемого «Data URL», про который можно почитать тут:
https://developer.mozilla.org/ru/docs/Web/HTTP/Basics_of_HTTP/Data_URIs

По этой ссылке, кстати, в качестве одного из примеров приведена аналогичная строка:
data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D

Внимательный читатель отметит, что в окончании двух сравниваемых строк есть разница. Однако, для браузера эти адреса URL аналогичны, так как (как было рассказано выше) символ «=» не входит в список некодируемых в адресе URL символов и поэтому подвергается «процентной кодировке». В результате «процентной кодировки» строка «==» превращается в «%3D%3D», то есть один символ «=» в «процентной кодировке» превращается в «%3D».

Итак, посмотрим, как кусок двоичных данных, содержащий строку «Hello, world!», превращается в результате кодирования Base64 в строку «SGVsbG8sIHdvcmxkIQ==».

1) В первой строке скрипта конструктор встроенного объекта Blob получает строку «Hello, world!» и должен преобразовать ее в кусок двоичных данных. Как он ее преобразовывает, ведь для преобразования нужно использовать какую-то конкретную кодировку?

В спецификации, в которой определяется поведение конструктора встроенного объекта Blob, сказано, что в данном случае используется строка типа USVString. То есть имеется в виду Юникод (аббревиатура «USV» расшифровывается как «Unicode Scalar Values»), а, точнее, представление Юникода UTF-8 (об этом сказано в спецификации: https://w3c.github.io/FileAPI/#constructorBlob).

Таким образом, используя таблицу Юникода (например, эту https://unicode-table.com/ru/) для UTF-8, получим из строки «Hello, world!» кусок двоичных данных. Вот содержимое переменной blob (кусок двоичных данных):
01001000 01100101 01101100 01101100 01101111 00101100 00100000 01110111 01101111 01110010 01101100 01100100 00100001

В строке «Hello, world!» содержится 13 символов, каждый из которых в UTF-8 представляется одним байтом (каждый байт содержит 8 бит). Можно было бы, конечно, взять для этого примера строку по-русски «Привет, мир!», но тогда пришлось бы иметь дело с 21 байтом (русские буквы в UTF-8 занимают каждая по 2 байта, в строке «Привет, мир!» содержится 9 русских букв [18 байт] и символы запятой, пробела и восклицательного знака, каждый из которых в UTF-8 занимает по одному байту) и понадобилось бы увеличить пост.

2) Кодирование Base64 запускается в строке reader.readAsDataURL(blob); скрипта. Кодирование происходит группами по 3 байта. Разделим наш кусок двоичных данных на такие группы:

01001000 01100101 01101100 01101100 01101111 00101100 00100000 01110111 01101111 01110010 01101100 01100100 00100001

Далее каждая группа из 3 байт (всего 24 бита) делится на 4 кусочка по 6 бит (всего 24 бита). Этот принцип является сердцем кодирования Base64. Нашей задачей является перевод (преобразование) куска двоичных данных, который разбит на байты по 8 бит, в код из 64 символов, а для представления 64 символов требуются, как известно из математики, кусочки по 6 бит. То есть по сути мы переводим 8-битные байты в 6-битные байты:

010010 000110 010101 101100 011011 000110 111100 101100 001000 000111 011101 101111 011100 100110 110001 100100 001000 01

В последней группе из 24 бит содержится всего лишь 8 бит. Как обрабатываются такие ситуации? Всё равно производим разбиение по 6 бит. Теперь в последней группе у нас есть 6 бит и 2 бита. Оставшийся кусочек из двух бит дополним нулями до 6 бит:

010010 000110 010101 101100 011011 000110 111100 101100 001000 000111 011101 101111 011100 100110 110001 100100 001000 010000

После этого в последней группе теперь 12 бит. Но в кодировании Base64 такая ситуация не допускается, в каждой группе в любом случае должно оказаться по 4 шестибитных кусочка. Недостающие 2 шестибитных кусочка в результатной строке дополняются служебным символом «=» (который при кодировании Base64 называется по-английски «pad», что на русский можно перевести как «подкладка», он подкладывается, чтобы заполнить собой ненужное пространство 24-битной группы), о котором упоминалось в начале поста. В данном случае у нас недостает 2 шестибитных кусочка, поэтому служебных символов «=» требуется тоже два. Вообще, рассуждая логически, можно понять, что после окончания кодирования Base64 для любой начальной строки итоговая (результатная) строка будет содержать в конце либо один служебный символ «=», либо два таких служебных символа, либо ни одного. Итак, теперь у нас есть следующее:

010010 000110 010101 101100 011011 000110 111100 101100 001000 000111 011101 101111 011100 100110 110001 100100 001000 010000==

Берем таблицу кодирования Base64, например из википедии:
https://ru.wikipedia.org/wiki/Base64
и переписываем каждый шестибитный кусочек в текстовый символ по таблице.

Вот что получается:
S G V s b G 8 s I H d v c m x k I Q==

Это и есть та самая строка «SGVsbG8sIHdvcmxkIQ==», которую наш скрипт помещает в текст адреса URL ссылки на HTML-странице.

Продолжение следует.

HTML: новые и старые особенности работы со ссылкой

Хоть разработка стандарта языка HTML версии 5 была завершена и стандарт был рекомендован к использованию организацией «W3C» в относительно далёком на сегодня 2014 году, для меня он до сих пор является новым и знаю я его довольно слабо. (Сейчас, кстати, разработчики стандарта языка HTML, насколько я знаю, отказались от четкого деления на версии в дальнейшей разработке стандарта. Изменения в стандарт вносятся постоянно, поэтому теперь его называют «Живым стандартом» [по-английски «Living Standard»]. «Живой» — в смысле, что он находится в состоянии постоянного изменения.)

В этом посте я хочу разобрать некоторые (не все) новые и старые особенности работы со ссылкой (HTML-элементом a).

* * *

Оказывается, HTML версии 5 добавил в HTML-элемент a атрибут download. Если у данного HTML-элемента есть этот атрибут, то при нажатии на ссылку пользователь запустит не переход по ссылке, а загрузку файла, адрес которого указан в атрибуте href данного HTML-элемента. Пример кода на языке HTML:
<a href="file.jpg">Открыть файл в браузере</a>
<a href="file.jpg" download>Скачать файл</a>

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

При использовании указанного выше кода при запуске загрузки файла предполагается, что файл будет сохранен на компьютере пользователя под именем file.jpg. Однако, в атрибуте download можно указать имя, под которым файл будет сохранен на компьютере пользователя. Пример кода:
<a href="file.jpg" download="image.jpg">Скачать файл</a>
В этом примере браузер (если пользователь нажмет на эту ссылку) должен скачать файл file.jpg и сохранить его на компьютере пользователя под именем image.jpg.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/A
http://htmlbook.ru/html/a/download

* * *

Еще одно новое для меня изменение в стандарте HTML версии 5 заключается в том, что несколько изменился способ создания якоря (закладки) на HTML-странице. Ранее это можно было делать следующим образом:
<a href="#anchor">Ссылка на якорь</a>
<!-- ... -->
<a name="anchor">Текст, на который поставлен якорь</a>

Этот способ работает в браузере и сегодня (как я понимаю, он оставлен для обратной совместимости). Однако, стандарт HTML версии 5 не одобряет (осуждает, по-английски такие атрибуты называются «deprecated») использование атрибута name для HTML-элемента a. Вместо него в данном случае предлагается использовать глобальный атрибут id. Пример кода:
<a href="#anchor">Ссылка на якорь</a>
<!-- ... -->
<a id="anchor">Текст, на который поставлен якорь</a>


* * *

Рассмотрим такой пример кода на языке HTML:
<a href="">Ссылка на текущую HTML-страницу</a>
<a href="#">Ссылка на текущую HTML-страницу</a>

Обе эти ссылки ссылаются на текущую HTML-страницу. Но между ними есть важное отличие: при нажатии на первую ссылку браузер перезагрузит (обновит) текущую HTML-страницу, а при нажатии на вторую ссылку браузер не станет перезагружать (обновлять) текущую HTML-страницу. Поэтому именно вторую ссылку некоторые программисты используют для создания кнопок на HTML-странице (вообще, такой способ создания кнопок не рекомендуется использовать, для кнопок следует использовать HTML-элемент button).

А что произойдет, если к этим ссылкам добавить вышеописанный атрибут download?
<a href="" download>Ссылка на текущую HTML-страницу</a>
<a href="#" download>Ссылка на текущую HTML-страницу</a>

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

Капля: от фантастического ужаса до программирования

Помню, в детстве посмотрел американский фильм «Капля» («The Blob») 1988 года в жанре фантастических ужасов. Он произвел на меня большое впечатление. [вики, кинопоиск]



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

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

А еще позднее я прочел фантастический роман «Клон» («The Clone») 1965 года американских авторов Теодора Томаса (Theodore L. Thomas) и Кейт Вильгельм (Kate Wilhelm):
http://www.lib.ru/INOFANT/TOMAS/clon.txt

В 1966 году этот роман был номинирован на известную ежегодную премию «Небьюла» (это был первый год награждения этой премией, она была учреждена в 1965 году) в категории «за лучший роман» (всего номинантов было 12), но уступил знаменитой «Дюне» Фрэнка Герберта.

Роман «Клон» родился из одноименного фантастического рассказа Теодора Томаса, опубликованного в декабре 1959 года в журнале «Fantastic», который издавался в США в период с 1952 года по 1980 год. Вероятно, Теодор Томас вдохновлялся фильмом «The Blob» 1958 года.

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

Слово «blob» переводится на русский с английского как «капля», «сгусток», «комочек».

* * *

Использовать слово «blob» для обозначения больших аморфных кусков двоичных данных придумал разработчик реляционных систем управления базами данных Джим Старки (Jim Starkey) во время своей работы на американскую компьютерную компанию «DEC» во второй половине 1970-х.

Джим Старки вдохновлялся в том числе вышеописанным фильмом «The Blob» 1958 года. Подробнее об этом можно почитать в википедии и в «Архиве Интернета» (там есть тексты электронных писем самого Джима Старки и его супруги Энн Харрисон [Ann Harrison]):

https://ru.wikipedia.org/wiki/BLOB
https://en.wikipedia.org/wiki/Binary_large_object
https://en.wikipedia.org/wiki/Jim_Starkey
https://web.archive.org/web/20110723065224/http://www.cvalde.net/misc/blob_true_history.htm

Изначально слово «blob» использовалось в программировании на уровне сленга. Но позже для него придумали бэкроним (обратная аббревиатура), то есть придумали аббревиатуру под имеющийся термин (обычно всё происходит наоборот: для часто использующегося словосочетания придумывают аббревиатуру). Слово «blob» превратилось в аббревиатуру «BLOB» и вошло в программирование в качестве термина. Сейчас аббревиатура «BLOB» расшифровывается как «Binary Large Object» (по-русски «двоичный большой объект»).

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

Браузер: настройка загрузки файлов

Читаю сейчас подраздел 2.3 «Blob» третьей части учебника по JavaScript. Нахожу в нем очень много нового для себя. В частности, возник вопрос: как в браузере можно настраивать загрузку файлов?

Я пользуюсь браузером «Microsoft Edge» на движке «Chromium», поэтому далее буду излагать этот вопрос на примере именно этого браузера. (Для других браузеров, построенных на этом же движке [например, для того же «Google Chrome»], думаю, настройки должны быть примерно такими же.) Кроме этого, наверное, стоит отметить, что у меня на компьютере установлена операционная система «Windows 10 Pro».

Настройки браузера я могу открыть в открытом окне браузера с помощью комбинации клавиш «Alt+F» или с помощью кнопки с многоточием в правом верхнем углу окна браузера. В меню этих настроек можно найти и нажать пункт «Downloads» (по-русски «Загрузки»), а еще этот пункт можно вызывать с помощью комбинации клавиш «Ctrl+J». Еще этот пункт можно вызвать с помощью кнопки со стрелкой вниз на панели инструментов браузера, но ее там может и не быть (наличие или отсутствие этой кнопки на панели инструментов браузера тоже может быть настроено).

После открытия пункта «Downloads» появится небольшое окошко, основную часть которого занимает список загруженных (и загружаемых) файлов. Это окошко может открываться само при попытке загрузить файл (но может и не открываться, это тоже настраивается).

В верхней части этого окошка есть заголовок «Downloads», нажав на который можно открыть отдельную вкладку в браузере с тем же списком загруженных (и загружаемых) файлов, но в отдельной вкладке браузера с этим списком работать удобнее. Также эту отдельную вкладку можно открыть в браузере, написав в адресной строке следующее:
edge://downloads

Из окошка (или вкладки браузера) «Downloads» можно установить, какие файлы были загружены (загружаются в данный момент) и куда они были сохранены (сохраняются в данный момент).

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

Список загруженных (загружаемых) файлов в любой момент можно очистить (это не удалит сами загруженные файлы). Это называется «Clear all download history» (по-русски «Стереть историю загрузок»).

Настройки загрузки файлов можно открыть из окошка «Downloads», нажав на кнопку с многоточием в этом окошке (эта кнопка называется «More options» или по-русски «Дополнительные опции»). Также эту кнопку можно нажать в описанной ранее вкладке «Downloads» браузера. В открывшемся меню следует выбрать пункт «Downloads settings» (по-русски «Настройки загрузок»). При выборе этого пункта меню откроется отдельная вкладка браузера с настройками. Еще эту вкладку браузера можно открыть в браузере, написав в адресной строке следующее:
edge://settings/downloads

Четвертый способ открыть вкладку браузера с этими настройками — открыть, собственно, настройки браузера (это тоже можно сделать несколькими способами). Откроется отдельная вкладка браузера с адресом:
edge://settings
И уже на этой вкладке выбрать в меню слева пункт «Downloads». Откроется та же самая вкладка браузера с настройками загрузок, описанная выше.

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

1) «Location» (по-русски «Папка по умолчанию, в которую загружаются файлы»). Тут можно изменить папку, в которую по умолчанию загружаются файлы;

2) «Ask me what to do with each download» (по-русски «Спрашивать меня, что делать с каждой загрузкой»). Если эта опция включена, при начале загрузки каждого файла браузер откроет окошко загрузок, а в нем выведет вопрос, что делать с данным файлом.

Тут можно отказаться от загрузки файла, нажав крестик в правом верхнем углу окна вопроса. Либо можно выбрать пункт «Open» (по-русски «Открыть»), в этом случае браузер загрузит файл, сохранит его в своей служебной папке и сразу же откроет этот файл соответствующей программой. Либо можно выбрать пункт «Save as» (по-русски «Сохранить как»), тогда браузер выведет окно, в котором можно будет выбрать папку для загрузки и хранения данного файла. Либо можно выбрать пункт «Save» (по-русски «Сохранить»), тогда браузер загрузит данный файл и сохранит его в папку по умолчанию из вышеописанного пункта «Location».

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

3) «Open Office files in the browser» (по-русски «Автоматически открывать офисные файлы в браузере, а не скачивать их в папку»). Под офисными файлами, как я понимаю, подразумеваются файлы, созданные для офисного пакета приложений «Microsoft Office» (таблицы, презентации, документы и так далее).

4) «Show downloads menu when a download starts» (по-русски «Открывать окошко «Downloads» при начале загрузки файла»). У меня эта опция включена, так как я хочу сразу знать, если браузер начнет загружать какой-либо файл на мой компьютер.

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

При этом браузер может либо вообще никак не показать пользователю, что началась загрузка некоего файла на его компьютер, либо браузер может сообщить об этом пользователю: всё зависит от настройки браузера.

JavaScript: TextDecoder и TextEncoder, UTF-8

Прочел подраздел 2.2 «TextDecoder и TextEncoder» третьей части учебника по JavaScript.

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

let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);

alert( new TextDecoder().decode(uint8Array) ); // Hello

let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);

alert( new TextDecoder().decode(uint8Array) ); // 你好

Во-первых, следовало бы отметить, что «你好» — это знаменитое «ни хао», то есть «привет» по-китайски или «hello» по-английски. Без этой ремарки лично мне было не очень понятно, зачем в этом примере эти два китайских иероглифа. Ну и следовало бы отметить, что этих иероглифов тут именно два, это важно. С первого взгляда для нашего, русского, глаза непонятно, сколько в этой надписи символов.

Во-вторых, следовало бы немного пояснить за кодировку. Я, конечно, уже немного ориентируюсь в Юникоде и знаю про существование одного из его форматов UTF-8 (для встроенных объектов TextDecoder и TextEncoder этот формат является форматом по умолчанию). Однако, подробно я разбирался с Юникодом только на примере формата UTF-16LE (тут), для него в операционной системе «Windows» в большинстве случаев всё достаточно просто: все символы представляются двумя байтами.

Для формата UTF-8, однако же, всё не так просто. Легко читать только первые 128 кодов Юникода, которые совпадают с ASCII и содержат все символы латинского алфавита. В UTF-8 они кодируются одним байтом. Поэтому в вышеприведенном примере фраза «Hello» кодируется и декодируется так легко: [72, 101, 108, 108, 111] это и есть «H», «e», «l», «l», «o» (каждый из этих кодов меньше 128).

Но для остальных кодов Юникода в формате UTF-8 используется переменное число байтов: от двух до четырех. Для вышеприведенных китайских иероглифов в формате UTF-8 используется по три байта:

228, 189, 160 — это «你»
229, 165, 189 — это «好»

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

https://ru.wikipedia.org/wiki/UTF-8

Как при декодировании строки в формате UTF-8 понять, сколько байтов представляет очередной символ? Для этого считывается первый байт из буфера и анализируются его старшие биты (нам это делать не нужно, это делает встроенный в JavaScript объект, но важно понимание процесса). В нашем примере берем первый байт из буфера:

22810 = 111001002

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

0xxxxxxx — байт однобайтового представления символа;
10xxxxxx — байт многобайтового представления символа (не первый);
110xxxxx — первый байт двухбайтового представления символа;
1110xxxx — первый байт трехбайтового представления символа;
11110xxx — первый байт четырехбайтового представления символа.

В нашем случае подходит шаблон 1110xxxx, следовательно, это первый байт трехбайтового представления символа. Далее движок считывает три байта: 228, 189, 160 и определяет по таблице Юникода, что это символ «你». И так далее.

Вот моя любимая таблица Юникода в интернете:
https://unicode-table.com/ru/

Вот для примера страница символа-иероглифа «你» на этом сайте (там приведены десятичные, шестнадцатиричные и двоичные коды этого символа в разных форматах Юникода):
https://unicode-table.com/ru/4F60/

Этот символ содержится в разделе таблицы Юникода, который называется «Унифицированные идеограммы ККЯ — расширение F» (по-английски «CJK Unified Ideographs Extension F»). «Идеограммами» названы иероглифы. Аббревиатура «ККЯ» расшифровывается как «Китайский, Корейский, Японский» (на самом деле, там еще есть и вьетнамские иероглифы, так что должно быть «ККЯВ»). Тут подробнее:
https://ru.wikipedia.org/wiki/Унифицированные_идеограммы_ККЯ_—_расширение_F