Объектная модель браузера (Browser Object Model, BOM) – это дополнительные объекты, предоставляемые браузером (окружением), чтобы работать со всем, кроме документа.
Объект window является глобальным.
Все переменные и функции, объявленные глобально с var, становятся свойствами и методами window объекта.
var counter = 1;
var showCounter = () => console.log(counter);
window.counter
window.showCounter();
Размер внешнего окна — это ширина и высота текущего окна браузера, включая адресную строку, панель вкладок и другие панели браузера.
const windowOuterWidth = window.outerWidth
const windowOuterHeight = window.outerHeight
Внутренний размер окна — это ширина и высота области просмотра (вьюпорта).
const windowInnerWidth = window.innerWidth
const windowInnerHeight = window.innerHeight
const windowInnerWidth = document.documentElement.clientWidth
const windowInnerHeight = document.documentElement.clientHeight
https://learn.javascript.ru/popup-windows
window.open(url, name, params):
Всплывающее окно («попап» – от англ. Popup window)
window.open('https://vk.com/feed')
Откроется новое окно с указанным URL. Большинство современных браузеров по умолчанию будут открывать новую вкладку вместо отдельного окна.
Всплывающее окно блокируется в том случае, если вызов window.open произошёл не в результате действия посетителя (например, события onclick).
window.open('https://vk.com/feed');
button.onclick = () => {
window.open('https://vk.com/feed');
};
Она показывает сообщение и ждёт, пока пользователь нажмёт кнопку «ОК».
alert("Hello");
Этот код отобразит модальное окно с текстом, полем для ввода текста и кнопками OK/Отмена.
const result = prompt(title, [default]);
const confirm = confirm("Ты здесь главный?");
Все эти методы являются модальными: останавливают выполнение скриптов и не позволяют пользователю взаимодействовать с остальной частью страницы до тех пор, пока окно не будет закрыто.
location — это объект хранящийся в window, который позволяет получать информацию о текущем адресе страницы и менять его с помощью функций или обновления полей объекта.
С помощью location мы можем получить текущий адрес:
console.log(window.location.href)
Обновление текущей страницы можно произвести с помощью reload(). Этот метод делает то же самое, что и кнопка «Обновить» в браузере:
window.location.reload()
С помощью replace() можно сделать клиентский редирект, это приведёт к мгновенному переходу по адресу, указанному при вызове метода:
window.location.replace('https://vk.com/feed')
search - содержит параметры в формате ключ=значение разделённые &. Если параметров нет, то значением будет пустая строка. ?itemsId=1,2,3&filter=abcd&id=10
hash — якорная ссылка включая символ #. Она находится в самом конце пути и отвечает за навигацию между размеченными на странице элементами с помощью установки атрибута id на тегах. Эта часть URL не передаётся на сервер. Если параметров нет, то значением будет пустая строка. #itemsId=1,2,3&filter=abcd&id=10
pathname – репрезентация текущего пути на сайте. Если текущий урл не содержит путь, то значением будет корневой путь "/".
assign(новый_путь) – метод вызывает переход на страницу, переданную в аргументах. После перехода на страницу пользователь может вернуться на страницу, с которой произошёл переход, с помощью браузерной кнопки назад.
replace(новый_путь) аналогичен методу assign(), но адрес страницы с которой был вызван этот метод не будет сохранён в истории браузера. При попытке вернуться назад пользователь будет отправлен на страницу предшествующую той, с которой произошёл переход.
reload() перезагружает текущую страницу.
toString() приводит адрес страницы к строке. Возвращает то же самое значение, что и location.href.
js redirect
https://stackoverflow.com/questions/503093/how-do-i-redirect-to-another-webpage
Чтобы программно перейти на другую страницу, используйте location.href.
Перейти по адресу на текущем сайте:
location.href = '/another-page'
Чтобы перейти на другой сайт, укажите его URL полностью:
location.href = 'https://google.com'
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
URLSearchParams интерфейс определяет служебные методы для работы со строкой запроса URL.
?itemsId=1,2,3&filter=abcd&id=10
#itemsId=1,2,3&filter=abcd&id=10
Объект navigator содержит информацию о браузере: название, версия, платформа, доступные плагины, доступ к буферу обмена и прочее. Это один из самых больших объектов в окружении.
Свойство window.navigator возвращает объект описания приложения (user agent), которое выполняет скрипт. В подавляющем большинстве случаев это приложение — браузер. Объект содержит свойства, описывающие браузер, и методы для выполнения действий.
navigator.userAgent
if ('bluetooth' in navigator) {
// Есть доступ к Bluetooth API.
}
if ('serviceWorker' in navigator) {
// Есть доступ к Service Worker API.
}
Объект screen содержит информацию об экране браузера.
width и height указывают ширину и высоту экрана в пикселях соответственно.
avail-метрики указывают, сколько ширины и высоты доступно — то есть ширину и высоту с учётом интерфейсных деталей браузера типа полос прокрутки.
pixelDepth указывает количество битов на один пиксель экрана.
// Без учёта полосы:
const screenWidth = screen.width
// С учётом полосы прокрутки:
const withoutScrollBar = screen.availWidth
History API даёт доступ к управлению историей браузера в рамках текущей сессии. Браузер создаёт новую сессию, когда пользователь открывает новую вкладку или новое окно браузера.
С помощью History API можно переходить по истории вперёд, назад и управлять содержимым истории. Доступ к API осуществляется с помощью объекта window.history.
Основные методы:
- back() перемещает пользователя по истории на страницу назад;
- forward() перемещает пользователя по истории на страницу вперёд;
- go() универсальный метод для перемещения по истории вперёд или назад;
- pushState() добавляет новую запись в истории сессии;
- replaceState() изменяет текущую запись в истории сессии.
Метод back() аналогичен вызову window.history.go(-1), а метод forward() — вызову window.history.go(1).
Все методы для перемещения по истории браузера back(), forward() и go() являются асинхронными.
Основным способом в вебе навигации являются ссылки <a>
. С помощью ссылок страницы соединяются друг с другом.
Особенность этого способа навигации в том, что при переходе на новый адрес страница перезагружается. Каждый такой переход сохраняется в истории браузера. История может выглядеть так:
Верхние пункты списка – это недавно посещённые страницы, а последний пункт — страница, с которой началась сессия.
Работают без перезагрузки страницы.
История в браузере будет выглядеть как единственный пункт:
С появлением History API появилась возможность напрямую добавлять записи в историю просмотров. History API так же расширяет возможности для программного перемещения по истории браузера. Это позволяет создавать полноценную навигацию в одностраничных приложениях, менять адрес в браузерной строке и все это будет происходить без перезагрузки страницы.
Для создания новой записи в истории используется метод pushState(), а для модификации текущей записи – replaceState().
Оба метода похожи с точки зрения использования и оба принимают три аргумента:
- объект состояния, в который можно добавить любые данные, необходимые для навигации;
- неиспользуемый параметр, который существует по историческим причинам;
- новый URL-адрес, этот параметр опциональный.
window.history.pushState(
{},
'',
'about.html'
)
Методы для перемещения по истории браузера back(), forward() и go() являются асинхронными, но они не принимают колбэков и не возвращают Promise.
Чтобы узнать, когда переход был завершён необходимо подписаться на событие popstate окна:
window.history.pushState(
{page: 'about', user: 123},
'',
'/about/user/'
)
window.history.back()
window.addEventListener('popstate', (event) => {
console.log(`Данные навигации: ${JSON.stringify(event.state)}`)
});
Интерфейс в глобальной области видимости window.matchMedia, который позволяет получить доступ к медиавыражениям из JavaScript и подписываться на их срабатывание. Медиавыражения активно используются в CSS с помощью директивы @media.
const mobileWidthMediaQuery = window.matchMedia('(max-width: 420px)')
https://developer.mozilla.org/ru/docs/Web/API/Console
console.log() отражает любой объект в консоли в удобном виде. Если это примитив — то его значением, если объект — его древовидной формой. DOM-элемент — его можно также раскрыть и увидеть, что внутри.
Но console.log() показывает содержимое DOM, а не его свойства. Если нужно увидеть свойства DOM-элемента, то лучше использовать console.dir():
const el = document.createElement('div')
const el2 = document.createElement('div')
el2.appendChild(el)
console.log(el)
console.dir(el)
console.log() удобен для исследования объектов и их вложенных элементов, а console.dir() удобен для просмотра свойств объекта.
Если у нас есть массив с каким-то количеством однотипных объектов, то можно воспользоваться console.table():
const data = [
{ section: 'HTML', count: 212 },
{ section: 'CSS', count: 121 },
{ section: 'JavaScript', count: 182 },
{ section: 'Tools', count: 211 },
]
console.table(data);
Данные, хранящиеся в куках, также передаются на сервер в виде HTTP-заголовка и могут быть им изменены. Cookie являются частью спецификации протокола HTTP, и их поддерживают все браузеры.
При разработке сайтов часть информации (например, токен авторизации или данные пользователя) нужно хранить и читать как в браузере, так и на сервере. Для этого используют Cookie (произносится «куки»).
Куки передаются в виде HTTP-заголовка, это накладывает на них ограничения. Например, максимальный размер куки в 4096 байт или отсутствие в содержимом пробелов или запятых. Чтобы обезопасить содержимое, можно закодировать его с помощью функции encodeURIComponent()
document.cookie
document.cookie = 'counter=1'
document.cookie = 'sidebar=false'
console.log(document.cookie)
document.cookie = 'sidebar=true'
console.log(document.cookie)
При установке кук можно указывать не только её название и значение, но и другие параметры. Все они являются необязательными и разделяются точкой с запятой;
path – определяет путь, по которому будет доступна кука. Он должен быть абсолютным, то есть начинаться с /. Если параметр не передан, то кука будет доступна на всех страницах сайта.
domain - определяет домен, для которого указана кука. Если не указано, то будет использоваться текущий домен.
max-age и expires - определяет время жизни куки.max-age указывает, через сколько секунд, а expires указывает точное время, когда кука станет недействительна. Время для expires можно отформатировать с помощью встроенного метода даты Date.toUTCString()
secure - указывает, что данная кука может быть передана только при запросах по защищённому протоколу HTTPS.
samesite - определяет, может ли данная кука быть отправлена при кросс-доменном запросе. Значение параметра strict будет предотвращать отправку на другие домены, а lax разрешит отправлять куки с GET-запросами.
Запись куки с разрешением передавать её только по HTTPS и только для текущего домена, со временем жизни в 1 час будет выглядеть так:
document.cookie = 'sidebar=true;secure;samesite=strict;max-age=3600'
Есть куки, которые нельзя прочитать или записать из JavaScript. Если сервер устанавливает куки с параметром HttpOnly (доступен только для установки сервером), то такие куки будут недоступны в document.cookie. Как правило, такие куки используются для хранения чувствительной информации, как, например, токены для авторизации. Проверка авторизации происходит с помощью запроса с текущим авторизованным пользователем и считается при успешном ответе сервера.
function getCookie() {
return document.cookie.split('; ').reduce((acc, item) => {
const [name, value] = item.split('=')
return { ...acc, [name]: value }
}, {})
}
const cookie = getCookie()
console.log(cookie.counter)
console.log(cookie.sidebar)
https://github.com/js-cookie/js-cookie
Web Storage – это интерфейс взаимодействия с хранилищем. Есть две реализации этого API: Local Storage и Session Storage. Оба способа имеют идентичный API и ограничения, а основным различием является время хранения данных.
Максимальный объем хранимых данных — 5 Мб. При этом любой скрипт, загруженный на странице, может иметь доступ к Web Storage на этой странице. Поэтому не стоит хранить там приватную информацию или токены авторизации, так как таким скриптом может являться вредное браузерное расширение, которое ворует информацию пользователя.
Значения хранятся в виде строк. При попытке сохранения других типов данных, они будут приведены к строке. Например, если записать число, то при чтении нам вернётся число, записанное в строку.
Для сохранение объектов, используем - JSON.stringify()
const user = {
name: 'IVAN',
last: 'IVAN2'
}
localStorage.setItem('user', JSON.stringify(user))
Запись в Web Storage является синхронной. Это значит, что на время записи браузер не выполняет другие действия. Поэтому избегайте частых записей в этот вид хранилища.
Session Storage похож на краткосрочные Cookie, потому что данные в этом хранилище хранятся только во время жизни текущей сессии.
Сессия страницы создаётся при открытии новой вкладки браузера. Сессия остаётся активной до тех пор, пока открыта вкладка, а состояние сессии сохраняется между перезагрузками. Открытие новой вкладки с таким же адресом приведёт к созданию новой сессии.
window.sessionStorage.setItem('name', 'OVANB')
Local Storage, в теории, является бессрочным хранилищем данных. Хотя данные и должны храниться бессрочно, браузеры все равно вводят свои ограничения.
window.localStorage.setItem('name', 'IVAN')
const name = window.localStorage.getItem('name')
window.localStorage.setItem('name', 'PETR')
window.localStorage.removeItem('name')
window.localStorage.clear()
При установке значения в хранилище срабатывает глобальное событие storage, с помощью которого можно отслеживать изменения в хранилище.
Событие происходит только на других открытых страницах текущего сайта.
Событие содержит свойства:
- key - ключ, который был изменён (при вызове метода clear(), ключ будет null);
- oldValue - старое значение, записанное в поле;
- newValue - новое значение, записанное в поле;
- url - адрес страницы, на которой вызвано изменение.
window.addEventListener('storage', function (evt) {
console.log(evt)
})
Это самый новый способ хранения данных. При этом он обладает достаточно сложным API. В отличие от всех остальных способов, это API асинхронное.
setTimeout() позволяет исполнить функцию через указанный промежуток времени. Функция возвращает числовой идентификатор установленного таймера. Этот идентификатор можно передать в функцию clearTimeout(), чтобы остановить таймер.
const timerId = setTimeout(() => {
console.log('Прошла 1 секунда')
}, 1000)
console.log(timerId)
Запланировать одноразовое выполнение функции можно как раз с помощью setTimeout(). Это самый простой способ исполнить функцию асинхронно.
Время таймера не гарантирует, что функция будет выполнена точно в момент, когда таймер закончится.
function timeout(workFn) {
setTimeout(() => {
console.log('setTimeout')
}, 0)
workFn()
}
timeout(() => {
for(let i=0; i<2000000000; i++) {}
})
Таймер ждёт, пока выполнится синхронный код и только потом запускает отложенную функцию, если время истекло. Строго говоря, когда мы устанавливаем таймаут, то нужно ожидать, что функция выполнится в произвольный момент после указанного времени.
Функция переданная в setTimeout() всегда будет вызвана только после выполнения синхронного кода, даже если выставить таймер в 0. Дело в том, что такая функция сразу попадает в асинхронную очередь вне зависимости от значения таймера.
function timeout(workFn) {
setTimeout(() => {
console.log('setTimeout')
}, 0)
setTimeout(() => {
workFn()
}, 0)
}
timeout(() => {
for(let i=0; i<2000000000; i++) {}
console.log('complete - for')
})
clearTimeout() очищает таймаут установленный с помощью setTimeout().
const timerId = setTimeout(() => {
console.log('Прошло 10 секунд')
}, 10000)
clearTimeout(timerId)
setInterval() позволяет регулярно исполнять функцию через указанный промежуток времени.
const intervalId = setInterval(function() {
console.log('Я выполняюсь каждую секунду')
}, 1000)
setInterval() не гарантирует точный запуск по таймеру, но гарантирует, что предыдущая функция завершила свою работу. Если функция работает дольше указанного времени, то вызовы станут идти один за другим без пауз.
const intervalId = setInterval(function() {
for(let i=0; i<2000000000; i++) {}
console.log('complete')
}, 1000)
Если необходимо выжидать время не между запусками функций, как в setInterval(), а между завершениями, то этого можно достичь цепочкой вызовов setTimeout():
let timerId;
timerId = setTimeout(function work() {
for(let i=0; i<2000000000; i++) {}
console.log('complete')
timerId = setTimeout(work, 1000)
}, 1000)
Отменяет регулярное выполнение функции, установленное вызовом setInterval().
const intervalId = setInterval(function() {
console.log('Я выполняюсь каждую секунду')
}, 1000)
clearInterval(intervalId)
Выполнение JS-кода — однопоточное. Это значит, что в конкретный момент времени движок может выполнять не более одной строки кода. То есть вторая строка не будет выполнена, пока не выполнится первая.
Такое выполнение кода (строка за строкой) называется синхронным. То есть вторая строка не будет выполнена, пока не выполнится первая.
console.log('A')
console.log('B')
console.log('C')
Операции, которые не дают выполнять ничего кроме них самих, пока они не завершатся, называются блокирующими выполнение.
console.log('A')
for(let i=0; i<3000000000; i++) {}
console.log('B')
console.log('C')
console.log('A')
setTimeout(function () {
for(let i=0; i<3000000000; i++) {}
console.log('complete')
}, 5000)
console.log('B')
console.log('C')
При вызове какой-то функции она попадает в так называемый стек вызовов.
Стек — это структура данных, в которой элементы упорядочены так, что последний элемент, который попадает в стек, выходит из него первым (LIFO: last in, first out)
В стеке вызовов хранятся функции, до которых дошёл интерпретатор, и которые надо выполнить.
function outer() {
function inner() {
// Функция 3
console.log('Hello!')
}
// Функция 2
inner()
}
// Функция 1
outer()
Вызываем функцию 1 — outer(), она попадает в стек:
outer();
Вызываем функцию 2 — inner(), она попадает в стек:
inner(); outer();
Вызываем console.log(), теперь в стеке 3 функции:
console.log(); inner(); outer();
Как только console.log() выполнится, она уйдёт из стека, там останется 2 функции:
Выполнившись, функция inner() тоже уйдёт из стека, в нём останется лишь одна:
После выполнения всего блока стек станет пустым.
В синхронном коде в стеке хранится вся цепочка вызовов.
Теперь посмотрим, как ведёт себя стек вызовов при работе с асинхронным кодом:
function main() {
setTimeout(function greet() {
console.log('Hello!')
}, 2000)
console.log('Bye!')
}
main()
Вызов main()
:
Стек | Web API | Очередь задач |
---|---|---|
main() | ||
Вызов setTimeout()
:
Стек | Web API | Очередь задач |
---|---|---|
setTimeout() | ||
main() | ||
Когда setTimeout()
исчезает из стека, он попадает в видимость Web API, где интерпретатор понимает, что внутри него есть функция greet()
, которую надо выполнить через 2 секунды:
Стек | Web API | Очередь задач |
---|---|---|
main() | setTimeout(greet) | |
После этого выполняется вызов консоли console.log('Bye!')
. В Web API находится функция setTimeout(greet)
.
Стек | Web API | Очередь задач |
---|---|---|
console.log('Bye!') | setTimeout(greet) | |
main() | ||
Отработал console.log()
, заканчивается работа main()
:
Стек | Web API | Очередь задач |
---|---|---|
main() | setTimeout(greet) | |
main()
отработал, стек пуст.
Стек | Web API | Очередь задач |
---|---|---|
setTimeout(greet) | ||
Наконец, 2 секунды прошли - функция greet()
перемещается в очередь задач:
Стек | Web API | Очередь задач |
---|---|---|
greet() | ||
Теперь цикл событий перемещает функцию greet()
из списка задач в вызов:
Стек | Web API | Очередь задач |
---|---|---|
greet() | ||
Затем вызов console.log('Hello!')
:
Стек | Web API | Очередь задач |
---|---|---|
console.log('Hello!') | ||
greet() | ||
Стек | Web API | Очередь задач |
---|---|---|
greet() | ||
Стек | Web API | Очередь задач |
---|---|---|
Наблюдает за стеком и за очередью задач, если стек пуст, цикл берет первый элемент из очереди и помещает его в стек.
Функция setTimeout() не является частью JavaScript-движка, это по сути Web API, включённое в среду браузера как дополнительная функциональность.
Эта дополнительная функциональность (Web API) берёт на себя работу с таймерами, интервалами, обработчиками событий.
То есть когда мы регистрируем обработчик клика на кнопку — он попадает в окружение Web API. Именно оно знает, когда обработчик нужно вызвать.
Управление тем, как должны вызываться функции Web API, берёт на себя цикл событий (Event loop).
Цикл событий отвечает за выполнение кода, сбор и обработку событий и выполнение подзадач из очереди.
Очередь — структура данных, в которой элементы упорядочены так, что первый попавший в очередь элемент покидает её первым. FIFO: first in, first out
Web API использует очередь для хранения того, что нужно выполнить.
Web API не может изменить наш код, он не может поместить какие то команды на стек
https://developer.mozilla.org/en-US/docs/Web/API
Callback (колбэк, функция обратного вызова) — функция, которая вызывается в ответ на совершение некоторого события.
это функция, переданная в другую функцию в качестве аргумента, которая затем вызывается по завершению какого-либо действия.
setTimeout(function greet() {
console.log('Hello!')
}, 5000)
Таким образом колбэк — это первый способ обработать какое-либо асинхронное действие.
Промис — это объект-обёртка для асинхронного кода. Он содержит в себе состояние: вначале pending («ожидание»), затем — одно из: fulfilled («выполнено успешно») или rejected («выполнено с ошибкой»).
В понятиях цикла событий промис работает так же, как колбэк: функция, которая должна выполниться (resolve или reject), находится в окружении Web API, а при наступлении события — попадает в очередь задач, откуда потом — в стек вызова.
В асинхронных задачах есть разделение между макрозадачами и микрозадачами. Колбэки в промисах попадают в очередь микрозадач, тогда как колбэк в setTimeout() — в очередь макрозадач.
Промисы придумали, чтобы организовывать асинхронный код последовательно.
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done"), 10000);
});
promise.then(() => console.log('promise complete'))
let promise = new Promise(function initPromise(resolve, reject) {
setTimeout(function initTimer(){
resolve("done")
}, 10000);
});
promise.then(function promiseThen(){
console.log('promise complete')
})
https://developer.mozilla.org/ru/docs/Web/JavaScript/EventLoop
https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
макротаски - загрузка скрипта, реакция на пользовательские действие, колбеки от setTimeout, setInterval
микротаски - создаются в основном из результатов промисов, то что происходит в then, catch, finally
Очередь микрозадач — контейнер для хранения асинхронных операций, имеющих высокий приоритет;
Очередь макрозадач — контейнер для хранения асинхронных операций с низким приоритетом.
setTimeout(function timeout(){
Promise.resolve().then(function promise1(){
console.log(1)
});
Promise.resolve().then(function promise2(){
console.log(2)
});
Promise.resolve().then(function promise3(){
console.log(3)
});
}, 10000);
setTimeout(function timeout2(){
Promise.resolve().then(function promise4(){
console.log(4)
});
Promise.resolve().then(function promise5(){
console.log(5)
});
Promise.resolve().then(function promise6(){
console.log(6)
});
}, 5000);
Выполняются все микротаски из промисов, после чего переходим к следующей макро таски
Когда стек почистился, сначало идут микротаски, а потом макротаски
Promise.resolve().then(() => console.log(1));
setTimeout(() => console.log(2),0)
console.log(3);
https://developer.mozilla.org/ru/docs/Web/API/window/requestAnimationFrame
requestAnimationFrame указывает браузеру на то, что вы хотите произвести анимацию, и просит его запланировать перерисовку на следующем кадре анимации. В качестве параметра метод получает функцию, которая будет вызвана перед перерисовкой.
Главное преимущество requestAnimationFrame над setTimeout(interval) для анимаций что он синхронизируется с частотой обновления монитора (пока в браузере включен VSync)