Почему использование onClick() в HTML - плохая практика?
Я много раз слышал, что с помощью событий JavaScript, таких как onClick()
в HTML это плохая практика, потому что это не хорошо для семантики. Я хотел бы знать, каковы недостатки и как исправить следующий код?
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
8 ответов
Вы, вероятно, говорите о ненавязчивом Javascript, который будет выглядеть так:
<a href="#" id="someLink">link</a>
с логикой в центральном файле JavaScript, выглядит примерно так:
$('#someLink').click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
Преимущества
- поведение (Javascript) отделено от представления (HTML)
- нет смешения языков
- Вы используете Javascript Framework, такой как JQuery, который может решить большинство кросс-браузерных проблем для вас
- Вы можете добавить поведение ко многим элементам HTML одновременно без дублирования кода.
Если вы используете jQuery, то:
HTML:
<a id="openMap" href="/map/">link</a>
JS:
$(document).ready(function() {
$("#openMap").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
});
Преимущество в том, что вы все еще работаете без JS, или если пользователь щелкает среднюю ссылку.
Это также означает, что я мог бы обрабатывать всплывающие окна, переписав снова:
HTML:
<a class="popup" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), 300, 300, 'map');
return false;
});
});
Это позволит вам добавить всплывающее окно к любой ссылке, просто назначив ей класс всплывающего окна.
Эта идея может быть расширена еще дальше, вот так:
HTML:
<a class="popup" data-width="300" data-height="300" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
return false;
});
});
Теперь я могу использовать один и тот же фрагмент кода для большого количества всплывающих окон на всем моем сайте без необходимости писать кучу материалов по клику! Yay для повторного использования!
Это также означает, что если позже я решу, что всплывающие окна являются плохой практикой (что они и есть!) И что я хочу заменить их модальным окном в стиле лайтбокса, я могу изменить:
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
в
myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
и все мои всплывающие окна на моем сайте теперь работают совершенно по-другому. Я мог бы даже определить функции, чтобы решить, что делать во всплывающем окне, или сохранить предпочтения пользователей, разрешать им или нет. С помощью встроенного onclick это требует огромных усилий по копированию и вставке.
С очень большими приложениями JavaScript программисты используют больше инкапсуляции кода, чтобы избежать загрязнения глобальной области видимости. И чтобы сделать функцию доступной для действия onClick в элементе HTML, она должна находиться в глобальной области видимости.
Возможно, вы видели файлы JS, которые выглядят так...
(function(){
...[some code]
}());
Это выражения для немедленного вызова функций (IIFE), и любая функция, объявленная в них, будет существовать только в пределах их внутренней области видимости.
Если вы объявите function doSomething(){}
в рамках IIFE, а затем сделать doSomething()
действие элемента onClick на вашей HTML-странице, вы получите ошибку.
Если, с другой стороны, вы создаете eventListener для этого элемента в этом IIFE и вызываете doSomething()
когда слушатель обнаруживает событие щелчка, вы хорошо, потому что слушатель и doSomething()
поделиться сферой деятельности IIFE.
Для небольших веб-приложений с минимальным количеством кода это не имеет значения. Но если вы стремитесь писать большие, поддерживаемые кодовые базы, onclick=""
это привычка, которую вы должны избегать.
Это не хорошо по нескольким причинам:
- он смешивает код и разметку
- код, написанный таким образом, проходит
eval
- и работает в глобальном масштабе
Самое простое было бы добавить name
приписать к вашему <a>
элемент, то вы могли бы сделать:
document.myelement.onclick = function() {
window.popup('/map/', 300, 300, 'map');
return false;
};
хотя современная лучшая практика будет использовать id
вместо имени и использовать addEventListener()
Вместо того, чтобы использовать onclick
так как это позволяет вам связать несколько функций с одним событием.
пересмотр
Ненавязчивый подход JavaScript был хорош в PAST - особенно связывание обработчиков событий в HTML считалось плохой практикой (главным образом потому, что onclick events run in the global scope and may cause unexpected error
что упомянул Минхуан)
Тем не мение...
В настоящее время кажется, что этот подход немного устарел и нуждается в некотором обновлении. Если кто-то хочет стать профессиональным разработчиком внешнего интерфейса и писать большие и сложные приложения, ему нужно использовать такие фреймворки, как Angular, Vue.js и т. Д. Однако эти фреймворки обычно используют (или позволяют использовать) HTML-шаблоны, в которых связываются обработчики событий. непосредственно в коде html-шаблона, и это очень удобно, понятно и эффективно - например, в угловых шаблонах обычно пишут:
<button (click)="someAction()">Click Me</button>
В сыром js / html эквивалент этого будет
<button onclick="someAction()">Click Me</button>
Разница в том, что в сыром JS onclick
Событие запускается в глобальной области видимости, но фреймворки обеспечивают инкапсуляцию.
Так в чем же проблема?
Проблема в том, когда начинающий программист, который всегда слышал, что html-onclick это плохо, и кто всегда использует btn.addEventListener("onclick", function(){...
, хочет использовать некоторые рамки с шаблонами. Тогда он столкнется с чем-то вроде вредных привычек или неправильного подхода к использованию фреймворка - и он будет очень плохо использовать фреймворк - потому что он сосредоточится в основном на js-части, а не на шаблонной части (и будет производить неясные и сложные в обслуживании код). Чтобы изменить эти привычки, он потеряет много времени (и, возможно, ему понадобится удача и учитель).
Так что, на мой взгляд, исходя из опыта моих учеников, было бы лучше для них, если бы они использовали html-handlers-bind в начале. Как я уже сказал, это правда, что обработчики вызываются в глобальной области, но на этом этапе студенты обычно создают небольшие приложения, которыми легко управлять. Для написания больших приложений они выбирают несколько фреймворков.
Так что делать?
Мы можем ОБНОВИТЬ ненавязчивый подход JavaScript и разрешить обработчики событий связывания (в конечном итоге с простыми параметрами) в html (но только обработчик связывания - не помещать логику в onclick, как в задании OP). Так что, по моему мнению, в raw js / html это должно быть разрешено:
<button onclick="someAction(3)">Click Me</button>
или же
function popup(num,str,event) {
let re=new RegExp(str);
// ...
event.preventDefault();
console.log("link was clicked");
}
<a href="https://example.com" onclick="popup(300,'map',event)">link</a>
но нижеприведенные примеры не должны быть разрешены
<button onclick="console.log('xx'); someAction(); return true">Click Me</button>
или это
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Реальность меняется, наша точка зрения тоже должна
Есть несколько причин:
Я считаю, что это помогает поддерживать разметку, то есть HTML и клиентские скрипты. Например, jQuery позволяет легко добавлять обработчики событий программно.
Приведенный вами пример будет разбит на любом пользовательском агенте, который не поддерживает javascript или у него отключен javascript. Концепция прогрессивного улучшения будет поощрять простую гиперссылку на
/map/
для пользовательских агентов без javascript, затем добавление обработчика щелчков программно для пользовательских агентов, которые поддерживают javascript.
Например:
Разметка:
<a id="example" href="/map/">link</a>
Javascript:
$(document).ready(function(){
$("#example").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
})
Это новая парадигма под названием " Ненавязчивый JavaScript". Нынешний "веб-стандарт" говорит об отделении функциональности и презентации.
На самом деле это не "плохая практика", просто большинство новых стандартов требуют, чтобы вы использовали прослушиватели событий вместо встроенного JavaScript.
Кроме того, это может быть просто личная вещь, но я думаю, что гораздо проще читать, когда вы используете прослушиватели событий, особенно если у вас более 1 оператора JavaScript, который вы хотите выполнить.
Ваш вопрос вызовет обсуждение, я полагаю. Общая идея заключается в том, что хорошо разделять поведение и структуру. Кроме того, afaik, встроенный обработчик кликов должен быть eval
привело к "стать" настоящей функцией JavaScript. И это довольно старомодно, хотя это довольно шаткий аргумент. Ах, ну, почитайте об этом @quirksmode.org
- События onclick запускаются в глобальной области видимости и могут вызвать непредвиденную ошибку.
- Добавление событий onclick ко многим элементам DOM замедлит
производительность и эффективность.
Еще две причины не использовать встроенные обработчики:
Они могут потребовать утомительных проблем с выходом цитаты
Учитывая произвольную строку, если вы хотите иметь возможность создать встроенный обработчик, который вызывает функцию с этой строкой, для общего решения вам придется избегать разделителей атрибутов (со связанным объектом HTML), и вы должны экранировать разделитель, используемый для строки внутри атрибута, как показано ниже:
const str = prompt('What string to display on click?', 'foo\'"bar');
const escapedStr = str
// since the attribute value is going to be using " delimiters,
// replace "s with their corresponding HTML entity:
.replace(/"/g, '"')
// since the string literal inside the attribute is going to delimited with 's,
// escape 's:
.replace(/'/g, "\\'");
document.body.insertAdjacentHTML(
'beforeend',
'<button onclick="alert(\'' + escapedStr + '\')">click</button>'
);
Это невероятно уродливо. Из приведенного выше примера, если вы не заменили'
s, это приведет к ошибке SyntaxError, потому что alert('foo'"bar')
неверный синтаксис. Если вы не заменили"
s, то браузер интерпретирует это как конец onclick
атрибут (разделен "
s выше), что также было бы неверно.
Если кто-то обычно использует встроенные обработчики, нужно обязательно помнить, что нужно делать что-то подобное вышеизложенному (и делать это правильно) каждый раз, что утомительно и трудно понять с первого взгляда. Лучше полностью избегать встроенных обработчиков, чтобы произвольную строку можно было использовать в простом закрытии:
const str = prompt('What string to display on click?', 'foo\'"bar');
const button = document.body.appendChild(document.createElement('button'));
button.textContent = 'click';
button.onclick = () => alert(str);
Разве это не намного приятнее?
Цепочка областей видимости встроенного обработчика чрезвычайно своеобразна.
Как вы думаете, что будет записывать следующий код?
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
Попробуйте, запустите сниппет. Вероятно, это не то, чего вы ожидали. Почему он производит то, что делает? Потому что встроенные обработчики работают внутриwith
блоки. Приведенный выше код находится внутри трех with
блоки: один для document
, один для <form>
, и один для <button>
:
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
поскольку disabled
это свойство кнопки, ссылающееся на disabled
внутри встроенного обработчика относится к свойству кнопки, а не к внешнему disabled
переменная. Это довольно нелогично.with
имеет много проблем: он может быть источником сбивающих с толку ошибок и значительно замедляет код. Это вообще не разрешено даже в строгом режиме. Но со встроенными обработчиками вы вынуждены прогонять код черезwith
s - и не только через один with
, но через несколько вложенных with
с. Это безумие.
with
никогда не следует использовать в коде. Поскольку встроенные обработчики неявно требуютwith
Наряду со всем его запутанным поведением следует также избегать встроенных обработчиков.