Чи знаєте ви, що елементи DOM з ідентифікаторами доступні в JavaScript як глобальні змінні? Це одна з тих речей, які були навколо, начебто, вічно, але я справді копаюся в цьому вперше.
Якщо ви вперше про це чуєте, будьте готові! Ми можемо побачити це в дії, просто додавши ідентифікатор до елемента в HTML:
Зазвичай ми визначаємо нову змінну за допомогою querySelector("#cool")
or getElementById("cool")
щоб вибрати цей елемент:
var el = querySelector("#cool");
Але ми фактично вже маємо доступ #cool
без цієї моралі:
Отже, будь-який id
- або name
якщо на те пішло — у HTML можна отримати доступ у JavaScript за допомогою window[ELEMENT_ID]
. Знову ж таки, це не зовсім «нове», але це справді рідко можна побачити.
Як ви можете здогадатися, доступ до глобальної області за допомогою іменованих посилань — не найкраща ідея. Деякі люди називають це «забруднювачем глобального масштабу». Ми розберемося, чому це так, але спочатку…
Якийсь контекст
Такий підхід є викладено в специфікації HTML, де це описано як «іменований доступ до Window
об'єкт».
Internet Explorer був першим, хто реалізував цю функцію. Усі інші браузери також додали його. Gecko був єдиним браузером на той час, який не підтримував його безпосередньо в стандартному режимі, замість цього вирішив зробити це експериментальною функцією. Були вагання щодо його реалізації взагалі, але воно просунувся вперед у сумісності браузерів (Гекко навіть намагався переконати WebKit щоб вивести його зі стандартного режиму) і зрештою перейшов у стандартний режим у Firefox 14.
Одна річ, яка може бути не дуже відома, полягає в тому, що браузерам довелося застосувати кілька запобіжних заходів — із різним ступенем успіху — щоб гарантувати, що згенеровані глобали не зламатимуть веб-сторінку. Одним із таких заходів є…
Змінна тінь
Ймовірно, найцікавіша частина цієї функції полягає в тому, що посилання на іменовані елементи цього не роблять затіняти існуючі глобальні змінні. Отже, якщо елемент DOM має id
який уже визначено як глобальний, він не замінить існуючий. Наприклад:
window.foo = "bar";
I won't override window.foo
console.log(window.foo); // Prints "bar"
І навпаки:
I will be overridden :(
window.foo = "bar";
console.log(window.foo); // Prints "bar"
Така поведінка є важливою, оскільки вона скасовує небезпечні перевизначення, такі як
, що інакше призвело б до конфлікту через анулювання alert
API. Ця техніка захисту цілком може бути причиною того, чому ви — якщо ви схожі на мене — дізнаєтеся про це вперше.
Справа проти названих глобалів
Раніше я казав, що використання глобальних іменованих елементів як посилань може бути не найкращою ідеєю. На це є багато причин, які TJ VanToll добре висвітлив у своєму блозі і я резюмую тут:
- Якщо змінюється DOM, змінюється і посилання. Це робить деякі справді «крихкими» (термін спец для нього) код, де поділ проблем між HTML і JavaScript може бути занадто великим.
- Випадкові посилання надто легкі. Проста друкарська помилка цілком може призвести до посилання на названий глобал і дати вам несподівані результати.
- У браузерах це реалізовано інакше. Наприклад, ми повинні мати доступ до прив’язки за допомогою
id
— напр— але деякі браузери (а саме Safari та Firefox) повертають a
ReferenceError
в консолі. - Це може не повернути те, що ви думаєте. Відповідно до специфікації, якщо в DOM є кілька екземплярів одного і того ж іменованого елемента, скажімо, два екземпляри
— браузер має повернути an
HTMLCollection
з масивом екземплярів. Однак Firefox повертає лише перший екземпляр. Потім знову, специфікація каже ми повинні використовувати один екземпляр anid
у будь-якому випадку в дереві елементів. Але це не зупинить роботу сторінки чи щось подібне. - Можливо, є вартість продуктивності? Я маю на увазі, що браузер повинен створити цей список посилань і підтримувати його. Кілька людей провели тести у цьому потоці StackOverflow, де насправді були названі глобали більш ефективний в одному тесті та менш ефективний у нещодавньому тесті.
Додаткові міркування
Скажімо, ми відкинемо критику щодо використання іменованих глобалів і все одно використаємо їх. Це все добре. Але є деякі речі, які ви можете враховувати під час цього.
Поліфіли
Як би нестандартно це звучало, ці типи глобальних перевірок є типовою вимогою до налаштування полізаповнення. Перегляньте наведений нижче приклад, де ми встановлюємо файл cookie за допомогою new CookieStore
API, заповнюючи його в браузерах, які ще не підтримують його:
// Polyfill the CookieStore API if not yet implemented.
// https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
if (!window.cookieStore) {
window.cookieStore = myCookieStorePolyfill;
}
cookieStore.set("foo", "bar");
Цей код чудово працює в Chrome, але видає таку помилку в Safari:
TypeError: cookieStore.set is not a function
Safari не підтримує CookieStore
API на момент написання цієї статті. В результаті поліфіл не застосовується, тому що img
Ідентифікатор елемента створює глобальну змінну, яка конфліктує з cookieStore
глобальний
Оновлення JavaScript API
Ми можемо перевернути ситуацію та знайти ще одну проблему, коли оновлення двигуна JavaScript браузера можуть порушити глобальні посилання іменованого елемента.
Наприклад:
window.BarcodeDetector.focus();
Цей сценарій захоплює посилання на вхідний елемент і викликає focus()
на ньому. Він працює правильно. Все одно ми не знаємо як довго він продовжуватиме працювати.
Розумієте, глобальна змінна, яку ми використовуємо для посилання на елемент введення, перестане працювати, щойно браузери почнуть підтримувати BarcodeDetector
API. У той момент, window.BarcodeDetector
global більше не буде посиланням на елемент input і .focus()
кине "window.BarcodeDetector.focus
не є функцією».
Бонус: не всі іменовані елементи створюють глобальні посилання
Хочете почути щось смішне? Щоб додати образи до травми, іменовані елементи доступні як глобальні змінні, лише якщо імена містять лише букви. Браузери не створюватимуть глобальне посилання для елемента з ідентифікатором, який містить спеціальні символи та цифри, наприклад hello-world
та item1
.
Висновок
Давайте підсумуємо, як ми сюди потрапили:
- Усі основні браузери автоматично створюють глобальні посилання на кожен елемент DOM за допомогою
id
(або, в деяких випадках, aname
атрибут). - Доступ до цих елементів через їхні глобальні посилання є ненадійним і потенційно небезпечним. використання
querySelector
orgetElementById
замість цього. - Оскільки глобальні посилання генеруються автоматично, вони можуть мати деякі побічні ефекти на ваш код. Це вагомий привід уникати використання
id
атрибут, якщо він вам дійсно не потрібен.
Зрештою, ймовірно, доцільно уникати використання іменованих глобалів у JavaScript. Раніше я процитував специфікацію про те, як це призводить до «крихкого» коду, але ось повний текст, щоб підкреслити цю думку:
Як правило, покладаючись на це, код буде крихким. Ідентифікатори, які в кінцевому підсумку зіставляються з цим API, можуть змінюватися з часом, наприклад, коли до веб-платформи додаються нові функції. Замість цього використовуйте
document.getElementById()
ordocument.querySelector()
.
Я думаю, той факт, що сама специфікація HTML рекомендує триматися подалі від цієї функції, говорить сам за себе.