Ответы на вопросы на собеседование Основы JavaScript (часть 2).

  • Eсть ли разница между window и document?

Да. У JavaScript есть глобальный объект и всё происходит через него. window - тот самый объект, который хранит глобальные переменные, функции, местоположение, историю. Всё находится внутри него, setTimeout, XMLHttpRequest, console и localStorage также являются частью window.
Аналогично дело обстоит и с document, который является свойством объекта window и представляет DOM. Все ноды - это часть document, следовательно, вы можете использовать getElementById или addEventListener для document. Но обратите внимание, что этих методов нет в объекте window.

  • Вызываются ли document.onload и window.onload одновременно?

window.onload вызывается, когда DOM готов и весь контент, включая картинки, стили, фреймы и т.д. загружен. document.onload вызывается когда дерево DOM выстроено, но до момента, как подгружаются картинки, стили и пр.
document.readyState возвращает "loading" пока документ грузится, "interactive" - когда завершился парсинг, но продолжается загрузка дополнительных ресурсов, и "complete" когда всё загружено. Событие readystatechange вызывается для объекта document когда это значение изменяется.

  • Как остановить дальнейшее распространение события?

Вызвать event.stopPropagation();

  • Назовите различные пути для получения элемента из DOM дерева?

Вы можете использовать следующие методы document:
  • getElementById для получения одного элемента, которому соответствует указанный ID.
  • getElementsByClassName для получения nodeList (nodeList это не массив, это скорее массиво-подобный объект) по названию класса.
  • getElementsByTagName для получения nodeList по имени тэга.
  • querySelector вы можете указывать селекторы в виде css стилей (аля jquery) и данный метод вернёт первый элемент из DOM соответствующий запросу.
  • querySelectorAll вернёт список не "живых" nodeList. Не "живые" значит, что любые изменения (добавление, удаления в DOM) после выборки элементов не будут отражены в результатах поиска.
  • getElementsByName возвращает список элементов returns the list of elements by the provided name of the html tag
  • getElementsByTagNameNS возвращает элементы с определённым названием тэга в пространстве имён.

  • Какой наибыстрейший метод для получения элемента через css селектор?

Это зависит от того, что вам нужно найти. Если у вас есть ID элемента, то getElementById - это самый быстрый путь для получения элемента. Однако, вам не следует содержать много ID в вашем документе, чтобы избежать заучивания стилей. getElementsByClassName - это второй по скорости метод для получения элемента.
Вот список упорядоченный по скорости выборки элементов, начиная с наибыстрейшего:
  • ID (#myID)
  • Класс (.myClass)
  • Тэг (div, p)
  • Элемент, находящийся рядом (sibling) (div+p, div~p)
  • Прямой потомок (div > p)
  • Все потомки (div p)
  • Универсальный (*)
  • Атрибут (input[type="checkbox"])
  • Псевдо-элемент (p:first-child)
Если у вас невероятно длинный селектор для получения элемента, подумайте, быть может, лучше использовать вместо него класс?

  • Могу ли я удалить удалить обработчик события с элемента?

Да. target.removeEventListener('click', handler)

  • Почему querySelectorAll('.my-class') медленнее, чем getElementsByClassName('my-class')?

querySelectorAll является универсальным методом. Он оптимизирован под различные типы селекторов. Если вы просто укажите имя класса с ".", внутри он будет использовать getElementsByClassName (может меняться в зависимости от браузера).
В то же время, если вы будете напрямую использовать getElementsByClassName, то понятно, что этому методу не нужно проходить через все внутренние процессы, в отличии от querySelectorAll. Следовательно, для поиска  элемента с конкретным именем класса, getElementsByClassName будет быстрее, чем querySelectorAll.

  • Почему я не могу использовать forEach или похожий метод массива для NodeList?

Да, и массив и nodeList имеет параметр length и вы можете использовать цикл для прохода по элементам, но не всё так просто.
Оба они унаследованы от Object. Однако, массив имеет иной прототип нежели, чем nodeList. forEach, map, и пр. включены в array.prototype, которого не существуют для NodeList.prototype объекта. Следовательно, вы не можете использовать forEach для nodeList.
Для решения этой проблемы можно пропустить nodeList через цикл и делать всё, что пожелаете внутри цикла. Или вызвать метод для конвертации nodeList в массив. После этого у вас будет доступ ко всем методам из array.prototype.

  • Если вам необходимо реализовать getElementByAttribute, как вы будете это делать?

Во-первых, получить все элементы из DOM. Это можно сделать используя getElementsByTagName с параметром '*' и затем проверить имеют ли они нужные атрибуты. В этом случае, даже если атрибут равен null, он будет захвачен. Если вам нужно проверить значение, вам следует добавить один дополнительный параметр и сравнивать с ним в блоке с IF.

  • Как бы вы добавили класс к элементу через селектор?

Очень просто. Просто получите элемент и добавьте имя класса в classlist.
Кроме того, вы можете реализовать методы removeClass, toggleClass и hasClass:

  • Как я могу запустить обработчик в фазе захвата, а не в фазе всплытия?

В методах addEventListener и removeEventLister есть третий опциональный параметр. Вы можете установить его в true или false в зависимости от того хотите или нет использовать фазу захвата.

  • Как проверить, что один элемент является дочерним другому?

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

  • Какой метод больше всего подходит для создания DOM элемента? Что лучше innerHTML или createElement?

Когда вы устанавливаете свойство innerHTML, браузер удаляет всех "детей" из элемента. Затем парсит строку и вставляет её в элемент как потомка. Например, если вы хотите добавить элемент списка к несортированному списку, вы можете получить элемент и задать ему innerHTML:
innerHTML может быть медленным при парсинге строки. Браузер вынужден иметь дело со строкой даже если вы задали ему невалидный html.
С другой стороны, пока вы используете appendChild, вы создаёте новый элемент. С момента его создания, браузеру не нужно парсить строку и иметь дело с невалидным html. И вы можете указать потомка для родителя, который будет добавлен к элементу родителя.
Все-таки, лучше написать пару дополнительных строк на JavaScript - это упростит жизнь браузеру и сделает вашу страницу быстрее.

  • Каким образом можно предотвратить множественный вызов обработчика для одного события?

Если слушатель события прикреплён к одному и тому же типу (click, keydown, и т.д.) элемента, вы можете вызвать event.stopImmediatePropagation() в первом обработчике и другие не будут выполнены.

  • Что делает createDocumentFragment и для чего можно его использовать?

documentFragment - очень легковесная и маленькая штука. Этот метод помогает в тех случаях, когда вы производите множество манипуляции с DOM. "Дерганье" DOM сотни раз - это дорогое удовольствие, которое может привести к вызову reflow. Избегайте частого reflow. Вы можете избежать этого, используя documentFragment, что уберегает от использования лишней памяти.

  • Что такое reflow?

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

  • Как я могу проверить были событие отменено или нет?

Используйте event.cancelable для получения true или false. Однако, вам обязательно нужно вызвать preventDefault() для предотвращения события.

  • Какие причины reflow? Как можно уменьшить reflow?

Причины reflow:
  • изменение шаблона (геометрия страницы)
  • изменения размера окна
  • изменения высоты\ширины любого элемента
  • изменение шрифта
  • перемещение элемента (анимация)
  • удаление или добавление стиля
  • калькуляции смещения по высоте или по ширине
  • display: none
Как этого избежать:
  • не устанавливайте стили внутри элементов
  • применяйте анимацию к элементам, которые отпозиционированы fixed или absolute
  • избегайте таблиц

  • Что такое repaint и когда оно происходит?

repaint происходит когда вы изменяете вид элемента без изменения размеров. Причины repaint:
  • изменения цвета фона
  • изменения цвета шрифта
  • visibility: hidden
Предпочтительней repaint вместо reflow.

  • Есть ли что-то такое о чём нужно позаботится при использовании node.cloneNode()?

При клонировании убедитесь, что вы не дублируете ID.
Как быть уверенным в том, что DOM подготовлен и можно выполнять JavaScript, как реализовать $(document).ready?
Существует четыре различных метода:
  • вставьте ваш скрипт в конце body элемента. Когда DOM будет готов браузер вызовет ваш script внутри тэга.
  • вставьте ваш код внутрь события DOMContentLoaded. Это событие будет вызываться, когда DOM полностью загружен.
  • Наблюдайте событие в readyState для document. Состояние "complete" будет означать полную загрузку:
  • Найдите исходники jQuery и скопируйте функцию dom.ready. В этом случае вы будете иметь функцию, которая работает во всех браузерах.

  • Что такое всплытие?

Для понимания "всплытия", вам нужно понять что происходит когда вы кликаете где-либо на странице. Предположим, у вас есть таблица с множеством колонок и столбцов и вы кликаете в одну из ячеек.
Вы возможно думаете, что когда вы кликните на ячейку, то браузер будет знать что у вас есть обработчик на нажатии на ячейку и он будет вызван незамедлительно. Это абсолютно не верно. На самом деле, браузер не знает куда вы кликнули. Браузер будет определять местоположение клика следующими путями:
Захват: когда вы кликаете, браузер знает, что событие клика произошло. Он начинает с window (самый низкий уровень), затем идёт в document, затем html тэг, затем body, затем table... Он пытается достичь самого высокого уровня элемента, который только возможен. Это зовётся фазой "захвата" (первая фаза).
Цель: когда браузер достигнет самого элемента на котором был произведен клик, то браузер отметит если ли у этого элемента какие-либо прикрепленные обработчики. Если ничего нет, то браузер выполнил обработчик клика. Это называет фаза цели (вторая фаза).
Всплытие: после вызова обработчика, прикрепленного к "td", браузер начнёт своё путешествие обратно с window. Уровень за уровнем он будет проверять если ли на элементе обработчик на "click" и если обнаружит таковой - выполнит. Это и есть стадия всплытия (третья фаза). Заметьте, когда вы кликните на ячейку, будут исполнены все обработчики событий на click для всех родительских элементов.

  • Как можно уничтожить несколько элементов с одним вызовом click?

Если у вас есть список из сотни элементов, которые имеют различные обработчики, вы можете написать одну сотню обработчиков событий (аля копипаст) с одинаковым кодом в сотне места. Это работает, но если понадобится что-то изменить в обработчике, вы будете вынуждены поменять это везде.
Вторая проблема заключается в том, что вы хотите динамически добавлять новые элементы и, следовательно, вам нужно быть уверенным в том, что к новому элементу был добавлен свой обработчик. Много JavaScript кода!
Ответ: В данном случае, как нельзя кстати, нам подойдет всплытие. Вы можете навесить только один обработчик на родительский элемент. В нашем примере это будет "ul" тэг. После клика по элементу списка (заметьте, элемент не имеет обработчика), событие будет всплывать и достигнет элемента "ul", который имеет обработчик и об будет исполнен.

  • Как предотвратить нажатие по ссылке?

Можно return false, можно и event.preventDefault() внутри обработчика события. Однако, это не остановит дальнейшее распространение.

  • Создайте кнопку, которая удаляется при нажатии на неё, и создаются две новые кнопки в этом же месте.

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

  • Как отлавливать все нажатия на странице?

Вы можете достичь цели при помощи фазы всплытия, т.к. все события click будут всплывать до элемента body.
Однако, если "всплытие" было отменено через stopPropagation() этот код не будет работать.

  • Как получить весь текст на странице?

Самый простой путь получить весь текст - через свойство innerText у body.

  • Что такое defer и async?

обычное состояние: когда вы вставляете стандартный тэг script (без defer и async), парсер приостанавливает парсинг до того момента, как скрипт будет скачан и выполнен.
defer: defer в тэге script отложит выполнение скрипта. Следовательно скрипт будет выполнен когда DOM будет доступен. Важный момент, defer не поддерживается всеми современными браузерами.
async: скачивание и выполнение скрипта асинхронно. Если это возможно, устанавливайте выполнение скрипта в асинхронном режиме, но обратите внимание, что async не имеет эффекта на инлайновые скрипты.

  • Какие существуют типы нод?

ELEMENT_NODE (1), TEXT_NODE (3), COMMENT_NODE(8), DOCUMENT_NODE(9), DOCUMENT_TYPE_NODE(10), DOCUMENT_FRAGMENT_NODE(11), и т.д.

Рассказать друзьям:

2 коментарі :

  1. Отличная статья!
    Небольшой недочет в оформлении - выдеите текст "Как быть уверенным в том, что DOM подготовлен и можно выполнять JavaScript, как реализовать $(document).ready?" как вопрос

    ОтветитьУдалить
  2. Это полный капец!

    ОтветитьУдалить