Почему использование onClick () в HTML - плохая практика?

133

Я много раз слышал, что использование событий JavaScript, например onClick(), в HTML, является плохой практикой, потому что это плохо для семантики. Я хотел бы знать, каковы недостатки и как исправить следующий код?

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Нилл
источник
4
В onlickc нет ничего плохого, но, сделав href #, любой, кто решит отключить javascript, застрянет и не сможет ничего делать. Для некоторых сайтов это хорошо, но для простого открытия окна просто глупо не предоставлять «настоящую» ссылку.
Marc B
2
Обязательно прочтите эту ссылку. Это не имеет ничего общего с семантикой и больше связано с ... всем, что есть на этой странице :-)
morewry
Попробуйте открыть свою ссылку в новой вкладке, и вы увидите пример того, почему это неправильно ...
Себастьен С.
2
Это должно быть <button>, потому что ссылки должны указывать на реальный ресурс. Так он более семантичен и понятен пользователям программ чтения с экрана.
programmer5000

Ответы:

171

Вы, наверное, говорите о ненавязчивом Javascript , который выглядел бы так:

<a href="#" id="someLink">link</a>

с логикой в ​​центральном файле javascript, выглядящей примерно так:

$('#someLink').click(function(){
    popup('/map/', 300, 300, 'map'); 
    return false;
});

Преимущества

  • поведение (Javascript) отделено от представления (HTML)
  • без смешения языков
  • вы используете фреймворк javascript, такой как jQuery, который может решить большинство кросс-браузерных проблем за вас
  • Вы можете добавить поведение сразу ко множеству HTML-элементов без дублирования кода.
Майкл Боргвардт
источник
30
Вы перечислили немало преимуществ, а как насчет недостатков? Отладка синего дыма?
abelito
2
@abelito: Не уверен, что отладка - это недостаток. Во всяком случае, это преимущество для отладки, поскольку вы можете пошагово выполнять свой JavaScript в любом инструменте отладки браузера. Не уверен, что вы сможете сделать это так же просто, если код встроен в файл onClick. Вы также можете писать модульные тесты, если хотите противостоять своим скриптам, что также очень сложно, я бы предположил, что код находится внутри onClickи никакая логика не разделена. Лично у меня нет проблем с отладкой ненавязчивого JavaScript, и преимущества в управлении и тестировании кода JavaScript слишком велики, чтобы не использовать его.
Нет,
45
@ François Wahl: главный недостаток - возможность обнаружения: просмотр HTML не говорит вам, какие обратные вызовы к нему привязаны, даже если они есть.
Майкл Боргвардт
1
@MichaelBorgwardt: Я полностью согласен с вами, что это недостаток. Если код хорошо структурирован и организован, я не вижу в этом большой проблемы при работе над проектом или отладке чужой базы кода. В целом, хотя я согласен с вами, я не считаю, что возможность обнаружения является хорошей причиной для написания встроенного скрипта, жертвуя такими преимуществами, как тестируемость кода и возможность отделить код вашей функции / поведения от DOM. Я не говорю, что встроенный код плох и не будет работать. Это работает и абсолютно нормально, в зависимости от того, интересуетесь ли вы такими вещами, как тестируемость или нет.
Нет,
4
Еще один момент в этом обсуждении с новичками - они onclickмогут более четко отобразить причинно-следственные связи между взаимодействием элемента и эффектом (вызовом функции), а также снизить когнитивную нагрузку. Многие уроки бросают учащихся в то, addEventListenerчто объединяет гораздо больше идей в более сложный синтаксис. Более того, последние компонентные фреймворки, такие как Riot и React, используют этот onclickатрибут и меняют представление о том, каким должно быть разделение. Переход от HTML onclickк компоненту, который поддерживает это, может быть более эффективным «пошаговым» процессом обучения.
jmk2142
41

Если вы используете 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;
    });
});

Теперь я могу использовать один и тот же фрагмент кода для множества всплывающих окон на всем моем сайте, без необходимости писать множество файлов onclick! Ура за возможность повторного использования!

Это также означает, что если позже я решу, что всплывающие окна - плохая практика (а это так!) И что я хочу заменить их модальным окном в стиле лайтбокса, я могу изменить:

popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');

в

myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');

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

Рич Брэдшоу
источник
4
Работает без JavaScript ?? Это jQuery. Для работы нужен Javascript в браузере, верно?
Томас Шилдс
2
Да, но в этом случае ссылка может перенаправлять на страницу, выполняющую действие без JavaScript.
ThiefMaster
12
Ссылка работает без JavaScript. Посмотрите, как у ссылки есть обычный атрибут href. Если у пользователя нет JavaScript, ссылка все равно будет ссылкой, и пользователь все равно сможет получить доступ к контенту.
morewry
3
@Thomas Shields: Нет, он имеет в виду, что если у вас нет Javascript, ссылка все равно приведет вас к / map / ...
Роберт
2
Ах, Рич! Мы все любим вас за это отличное решение!
Нирмал
21

С очень большими приложениями JavaScript программисты используют больше инкапсуляции кода, чтобы избежать загрязнения глобальной области. И чтобы сделать функцию доступной для действия onClick в элементе HTML, она должна быть в глобальной области.

Возможно, вы видели файлы JS, которые выглядят так ...

(function(){
    ...[some code]
}());

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

Если вы объявляете function doSomething(){}в IIFE, а затем doSomething()выполняете действие onClick элемента на своей HTML-странице, вы получите сообщение об ошибке.

Если, с другой стороны, вы создаете eventListener для этого элемента внутри этого IIFE и вызываете его, doSomething()когда слушатель обнаруживает событие щелчка, вы в порядке, потому что слушатель и doSomething()разделяют область действия IIFE.

Для небольших веб-приложений с минимальным объемом кода это не имеет значения. Но если вы стремитесь писать большие, поддерживаемые кодовые базы, onclick=""это привычка, которой вам следует избегать.

LetMyPeopleCode
источник
1
если вы стремитесь писать большие, поддерживаемые кодовые базы, используйте JS-фреймворки, такие как Angular, Vue, React ... которые рекомендуют связывать обработчики событий внутри своих HTML-шаблонов,
Камил Келчевски
20

Это нехорошо по нескольким причинам:

  • он смешивает код и разметку
  • код, написанный таким образом, проходит eval
  • и работает в глобальном масштабе

Самым простым было бы добавить nameатрибут к вашему <a>элементу, после чего вы могли бы сделать:

document.myelement.onclick = function() {
    window.popup('/map/', 300, 300, 'map');
    return false;
};

хотя современная передовая практика - использовать idвместо имени и использовать addEventListener()вместо использования, onclickпоскольку это позволяет вам привязать несколько функций к одному событию.

Альнитак
источник
Даже предложение этого способа открывает перед пользователем кучу проблем с коллизиями обработчиков событий.
предварительное действие
3
@preaction, как и встроенный обработчик событий, поэтому в моем ответе говорится, что его лучше использовать addEventListener()и почему.
Alnitak
2
Может кто-нибудь объяснить это подробнее? "код, написанный таким образом, проходит через eval" ... Я ничего не нашел в сети об этом. Вы говорите, что использование встроенного Javascript имеет некоторые недостатки в производительности или безопасности?
MarsAndBack
12

Причин несколько:

  1. Я считаю, что это помогает разделить разметку, то есть HTML и клиентские скрипты. Например, jQuery позволяет легко добавлять обработчики событий программно.

  2. Приведенный вами пример не работает в любом пользовательском агенте, который не поддерживает 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;
    });

})
Грэхем
источник
2
Спасибо, что напомнили мне о прогрессивном улучшении, и поэтому это все еще соответствует этой парадигме:<a href="https://stackoverflow.com/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Daniel Sokolowski
10

пересмотр

Ненавязчивый подход JavaScript был хорош в прошлом - особенно привязка обработчика событий в HTML считалась плохой практикой (в основном потому, onclick events run in the global scope and may cause unexpected errorчто то, что было упомянуто YiddishNinja )

Тем не мение...

В настоящее время кажется, что этот подход немного устарел и нуждается в некотором обновлении. Если кто-то хочет быть профессиональным разработчиком внешнего интерфейса и писать большие и сложные приложения, ему необходимо использовать такие фреймворки, как Angular, Vue.js и т. Д. Однако эти фреймворки обычно используют (или позволяют использовать) HTML-шаблоны, к которым привязаны обработчики событий. непосредственно в коде html-шаблона, и это очень удобно, понятно и эффективно - например, в шаблоне angular обычно пишут:

<button (click)="someAction()">Click Me</button> 

В raw js / html эквивалент этого будет

<button onclick="someAction()">Click Me</button>

Разница в том, что в raw js onclick событии запускается в глобальной области, но фреймворки обеспечивают инкапсуляцию.

Так в чем же проблема?

Проблема в том, что начинающий программист, который всегда слышал, что html-onclick - это плохо и который всегда использует, btn.addEventListener("onclick", ... )хочет использовать какой-то фреймворк с шаблонами ( addEventListenerтакже есть недостатки - если мы обновляем DOM динамически с использованием innerHTML=(что довольно быстро ) - тогда мы теряем события обработчики связываются таким образом). Тогда он столкнется с чем-то вроде дурных привычек или неправильного подхода к использованию фреймворка - и он будет использовать фреймворк очень плохо - потому что он сосредоточится в основном на js-части, а не на части-шаблоне (и будет производить нечеткие и трудные для поддержки код). Чтобы изменить эту привычку, он потеряет много времени (и, вероятно, ему понадобится удача и учитель).

Так что, на мой взгляд, исходя из опыта моих студентов, для них было бы лучше, если бы они использовали html-handlers-bind вначале. Как я уже сказал, обработчики вызываются в глобальном масштабе, но на этом этапе студенты обычно создают небольшие приложения, которыми легко управлять. Для написания приложений большего размера они выбирают несколько фреймворков.

Так что делать?

Мы можем ОБНОВИТЬ подход ненавязчивого JavaScript и разрешить привязку обработчиков событий (в конечном итоге с простыми параметрами) в html (но только обработчик привязки - не помещать логику в onclick, как в OP quesiton). Так что, на мой взгляд, в raw js / html это должно быть разрешено

<button onclick="someAction(3)">Click Me</button>

или

Но приведенные ниже примеры НЕ должны допускаться

<button onclick="console.log('xx'); someAction(); return true">Click Me</button>

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>

Реальность меняется, наша точка зрения тоже должна

Камил Келчевски
источник
Привет, Камил, я новичок в JavaScript и также столкнулся с путаницей по поводу использования onclick, то есть, пожалуйста, не могли бы вы объяснить недостатки onclick следующим образом: 1. Вам может быть назначено только одно встроенное событие. 2. Встроенные события хранятся как свойство элемента DOM и, как и все свойства объекта, могут быть перезаписаны. 3. Это событие будет продолжаться, даже если вы удалите свойство onclick.
миржал
1
Объявление 1. Да, только один, однако мой опыт (с angular) показывает, что этого достаточно в большинстве ситуаций, но если нет, то просто используйте addEventListener. Объявление 2. Да, их можно перезаписать (но обычно этого никто не делает) - в чем проблема? Объявление 3. Обработчик не будет уволен после удаления атрибута onclick - доказательство здесь
Камил Келчевски
8

Это новая парадигма под названием « Ненавязчивый JavaScript ». Текущий «веб-стандарт» говорит о разделении функциональности и представления.

Это не совсем «плохая практика», просто большинство новых стандартов требуют, чтобы вы использовали прослушиватели событий вместо встроенного JavaScript.

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

Ракета Хазмат
источник
2
Странице в Википедии по этой теме более 4 лет. Это больше не новая парадигма. :)
Квентин
@David: Возможно, он не «новый», но есть люди, которые все еще не используют его, поэтому для них это «ново».
Rocket Hazmat
6

Думаю, ваш вопрос вызовет обсуждение. Общая идея состоит в том, что хорошо разделять поведение и структуру. Кроме того, afaik, встроенный обработчик кликов должен eval«стать» настоящей функцией javascript. И это довольно старомодно, хотя это довольно шаткий аргумент. А, ну, почитайте об этом @ quirksmode.org

KooiInc
источник
Я не хочу создавать еще раз священную войну, я пытаюсь найти истину: \
NiLL
2
  • События onclick запускаются в глобальной области и могут вызвать непредвиденную ошибку.
  • Добавление событий onclick ко многим элементам DOM снизит
    производительность и эффективность.
Minghuan
источник
0

Еще две причины не использовать встроенные обработчики:

Они могут потребовать утомительных проблем с выходом цитаты

Учитывая произвольную строку, если вы хотите иметь возможность создать встроенный обработчик, который вызывает функцию с этой строкой, для общего решения вам придется избегать разделителей атрибутов (со связанным объектом 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, '&quot;')
  // 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>:

введите описание изображения здесь

Поскольку disabledэто свойство кнопки, ссылка disabledвнутри встроенного обработчика относится к свойству кнопки, а не к внешней disabledпеременной. Это довольно нелогично. withимеет много проблем: он может быть источником сбивающих с толку ошибок и значительно замедляет код. Это вообще не разрешено даже в строгом режиме. Но со встроенными обработчиками вы вынуждены запускать код через withs - и не только через один with, а через несколько вложенных withs. Это безумие.

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

CertainPerformance
источник