Dlaczego używanie onClick () w HTML jest złą praktyką?

133

Wiele razy słyszałem, że używanie zdarzeń JavaScript, takich jak onClick()HTML, jest złą praktyką, ponieważ nie jest dobre dla semantyki. Chciałbym wiedzieć, jakie są wady i jak naprawić następujący kod?

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
NiLL
źródło
4
Nie ma nic złego w onlickc, ale po utworzeniu atrybutu href #każdy, kto zdecyduje się na wyłączenie javascript, utknie i nie będzie mógł nic zrobić. W przypadku niektórych witryn to dobrze, ale po prostu otwierając okno, po prostu głupotą jest nie podawać „prawdziwego” linku.
Marc B
2
Zdecydowanie przeczytaj ten link. Ma to bardzo niewiele wspólnego z semantyką, a więcej z ... wszystkimi rzeczami na tej stronie :-)
morewry
Spróbuj otworzyć swój link w nowej karcie, a zobaczysz przykład, dlaczego jest źle ...
Sebastien C.
2
Powinien to być znak <button>, ponieważ linki mają wskazywać rzeczywisty zasób. W ten sposób jest bardziej semantyczny i bardziej przejrzysty dla użytkowników czytników ekranu.
programista5000

Odpowiedzi:

172

Prawdopodobnie mówisz o dyskretnym skrypcie JavaScript , który wyglądałby tak:

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

z logiką w centralnym pliku javascript wyglądającym mniej więcej tak:

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

Zalety są

  • zachowanie (JavaScript) jest oddzielone od prezentacji (HTML)
  • bez mieszania języków
  • używasz frameworka javascript, takiego jak jQuery, który może obsłużyć większość problemów z różnymi przeglądarkami
  • Możesz dodać zachowanie do wielu elementów HTML naraz bez powielania kodu
Michael Borgwardt
źródło
30
Wymieniłeś kilka zalet, ale co z wadami? Debugowanie niebieskiego dymu?
abelito
2
@abelito: Nie wiem, czy debugowanie jest wadą. Jeśli cokolwiek, debugowanie jest zaletą, ponieważ możesz przejść przez JavaScript w dowolnym narzędziu do debugowania przeglądarki. Nie jestem pewien, czy możesz to zrobić tak łatwo, jeśli kod jest wbudowany w plik onClick. Możesz także napisać testy jednostkowe, jeśli chcesz, aby przeciwdziałać swoim skryptom, co jest również bardzo trudne, założyłbym, że kod znajduje się wewnątrz onClicki żadna logika nie jest oddzielona. Osobiście nie mam problemu z debugowaniem dyskretnego JavaScript, a korzyści płynące z zarządzania i testowania kodu JavaScript są zbyt duże, aby go nie używać.
Nie,
45
@ François Wahl: główną wadą jest wykrywalność: przeglądanie kodu HTML nie powie Ci, które wywołania zwrotne są do niego dołączone, nawet czy istnieją.
Michael Borgwardt
1
@MichaelBorgwardt: Całkowicie zgadzam się z tobą, że jest to wada. Jeśli kod jest dobrze ustrukturyzowany i zorganizowany, nie uważam tego za poważny problem podczas pracy nad projektem lub debugowania bazy kodu innej osoby. Ogólnie rzecz biorąc, zgadzam się z tobą, ale nie uważam, że wykrywalność jest dobrym powodem do pisania skryptów in-line, korzyści związanych z sakrafiką, takich jak testowalność kodu i możliwość oddzielenia kodu funkcji / zachowania z dala od DOM. Nie mówię, że kod wbudowany jest zły i nie zadziała. Działa i jest absolutnie w porządku, w zależności od tego, czy interesują Cię takie rzeczy, jak testowalność, czy nie.
Nie,
4
Kolejna kwestia w tej dyskusji, z nowicjuszami - onclickmogą bardziej dokładnie odwzorować związki przyczynowe między interakcją elementów a efektem (wywołanie funkcji), a także zmniejszyć obciążenie poznawcze. Wiele lekcji rzuca uczniom lekcje, addEventListenerktóre zawierają o wiele więcej pomysłów w trudniejszej składni. Co więcej, najnowsze frameworki oparte na komponentach, takie jak Riot i React, używają tego onclickatrybutu i zmieniają pojęcie, czym powinna być separacja. Przeniesienie z HTML onclickdo komponentu, który to obsługuje, może być bardziej efektywnym „krokowym” procesem uczenia się.
jmk2142
41

Jeśli używasz jQuery, to:

HTML:

 <a id="openMap" href="/map/">link</a>

JS:

$(document).ready(function() {
    $("#openMap").click(function(){
        popup('/map/', 300, 300, 'map');
        return false;
    });
});

Ma to tę zaletę, że nadal działa bez JS lub jeśli użytkownik kliknie link w środku.

Oznacza to również, że mógłbym obsłużyć ogólne wyskakujące okienka, przepisując ponownie do:

HTML:

 <a class="popup" href="/map/">link</a>

JS:

$(document).ready(function() {
    $(".popup").click(function(){
        popup($(this).attr("href"), 300, 300, 'map');
        return false;
    });
});

Umożliwiłoby to dodanie wyskakującego okienka do dowolnego łącza, po prostu nadając mu klasę wyskakującą.

Ten pomysł można rozszerzyć jeszcze bardziej w ten sposób:

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;
    });
});

Mogę teraz używać tego samego fragmentu kodu do wielu wyskakujących okienek w całej mojej witrynie bez konieczności pisania mnóstwa rzeczy onclick! Brawo za ponowne użycie!

Oznacza to również, że jeśli później uznam, że wyskakujące okienka są złą praktyką (a tak są!) I chcę je zastąpić modalnym oknem w stylu lightbox, mogę zmienić:

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

do

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

a wszystkie moje wyskakujące okienka w całej mojej witrynie działają teraz zupełnie inaczej. Mógłbym nawet wykonać wykrywanie funkcji, aby zdecydować, co zrobić w wyskakującym okienku, lub zapisać preferencje użytkowników, aby im zezwolić lub nie. Dzięki inline onclick wymaga to ogromnego wysiłku w kopiowaniu i wklejaniu.

Rich Bradshaw
źródło
4
Działa bez JavaScript ?? To jQuery. Do działania wymaga włączonej obsługi Javascript w przeglądarce, prawda?
Thomas Shields
2
Tak, ale w tym przypadku odsyłacz może przekierowywać do strony wykonującej akcję bez JavaScript.
ThiefMaster
12
Link działa bez JavaScript. Zobacz, jak link ma normalny atrybut href. Jeśli użytkownik nie ma JavaScript, link nadal będzie linkiem, a użytkownik nadal będzie miał dostęp do treści.
morewry
3
@Thomas Shields: Nie, ma na myśli, że jeśli nie masz Javascript, link nadal prowadzi do / map / ...
Robert
2
Ach Rich! Wszyscy kochamy Cię za to najpiękniejsze rozwiązanie!
Nirmal
21

W przypadku bardzo dużych aplikacji JavaScript programiści używają większej enkapsulacji kodu, aby uniknąć zanieczyszczenia globalnego zakresu. Aby funkcja była dostępna dla akcji onClick w elemencie HTML, musi znajdować się w zasięgu globalnym.

Być może widzieliście pliki JS, które wyglądają tak ...

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

Są to natychmiast wywoływane wyrażenia funkcyjne (IIFE) i każda zadeklarowana w nich funkcja będzie istnieć tylko w ich wewnętrznym zakresie.

Jeśli zadeklarujesz function doSomething(){}wewnątrz IIFE, a następnie wykonasz doSomething()akcję onClick elementu na swojej stronie HTML, pojawi się błąd.

Z drugiej strony, jeśli utworzysz eventListener dla tego elementu w ramach tego IIFE i wywołasz, doSomething()gdy detektor wykryje zdarzenie kliknięcia, jesteś dobry, ponieważ słuchacz i doSomething()współdzielą zakres IIFE.

W przypadku małych aplikacji internetowych z minimalną ilością kodu nie ma to znaczenia. Ale jeśli aspirujesz do pisania dużych, łatwych w utrzymaniu baz kodów, onclick=""jest to nawyk, którego powinieneś unikać.

LetMyPeopleCode
źródło
1
jeśli chcesz pisać duże, łatwe w utrzymaniu bazy kodów, użyj frameworków JS, takich jak Angular, Vue, React ... które zalecają wiązanie programów obsługi zdarzeń w ich szablonach HTML
Kamil Kiełczewski
20

Nie jest to dobre z kilku powodów:

  • łączy kod i znaczniki
  • kod napisany w ten sposób przechodzi eval
  • i działa w zasięgu globalnym

Najprościej byłoby dodać nameatrybut do swojego <a>elementu, wtedy możesz zrobić:

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

chociaż nowoczesną najlepszą praktyką byłoby użycie znaku idzamiast nazwy i użycie addEventListener()zamiast używania, onclickponieważ umożliwia to powiązanie wielu funkcji z jednym zdarzeniem.

Alnitak
źródło
Nawet sugerowanie tego sposobu otwiera użytkownika na wiele problemów z kolizjami obsługi zdarzeń.
preakcja
3
@preaction, tak jak robi to wbudowany program obsługi zdarzeń, dlatego moja odpowiedź mówi, że lepiej jest używać addEventListener()i dlaczego.
Alnitak
2
Czy ktoś może to bardziej wyjaśnić? „kod napisany w ten sposób przechodzi przez eval”… Nie znajduję w sieci nic na ten temat. Czy chcesz powiedzieć, że korzystanie z wbudowanego JavaScript ma pewne wady w zakresie wydajności lub bezpieczeństwa?
MarsAnd Back
12

Jest kilka powodów:

  1. Uważam, że pomaga to w utrzymaniu oddzielenia znaczników, tj. Kodu HTML i skryptów po stronie klienta. Na przykład jQuery ułatwia programowe dodawanie programów obsługi zdarzeń.

  2. Przykład, który podasz, byłby zepsuty w każdym kliencie użytkownika, który nie obsługuje javascript lub ma wyłączony javascript. Koncepcja stopniowego ulepszania zachęcałaby do prostego hiperłącza do /map/programów klienckich bez javascript, a następnie dodawania programu obsługi kliknięć prgramatycznie do programów użytkownika obsługujących javascript.

Na przykład:

Narzut:

<a id="example" href="/map/">link</a>

JavaScript:

$(document).ready(function(){

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

})
Graham
źródło
2
Dziękuję za przypomnienie mi o stopniowym ulepszaniu, więc to nadal pasuje do tego paradygmatu:<a href="https://stackoverflow.com/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Daniel Sokolowski
10

Rewizja

Dyskretne podejście JavaScript było dobre w PAST-ie - szczególnie wiązanie obsługi zdarzeń w HTML zostało uznane za złą praktykę (głównie z powodu onclick events run in the global scope and may cause unexpected errortego, o czym wspomniał YiddishNinja )

Jednak...

Obecnie wydaje się, że to podejście jest trochę przestarzałe i wymaga aktualizacji. Jeśli ktoś chce być profesjonalnym programistą frontendowym i pisać duże i skomplikowane aplikacje, musi używać frameworków takich jak Angular, Vue.js itp ... Jednak te frameworki zwykle używają (lub pozwalają na użycie) szablonów HTML, w których są powiązane programy obsługi zdarzeń bezpośrednio w kodzie html-template i jest to bardzo poręczne, przejrzyste i efektywne - np. w szablonach kątowych zazwyczaj piszą:

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

W surowym js / html będzie to odpowiednik

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

Różnica polega na tym, że w surowym js onclickzdarzenie jest uruchamiane w zasięgu globalnym - ale frameworki zapewniają hermetyzację.

Więc gdzie jest problem?

Problem polega na tym, że początkujący programista, który zawsze słyszał, że html-onclick jest zły i który zawsze używa, btn.addEventListener("onclick", ... )chce użyć jakiegoś frameworka z szablonami ( addEventListenerma też wady - jeśli aktualizujemy DOM w sposób dynamiczny za pomocą innerHTML=(co jest dość szybkie ) - wtedy tracimy zdarzenia handlery wiążą się w ten sposób). Wtedy będzie musiał zmierzyć się z czymś w rodzaju złych nawyków lub złego podejścia do używania frameworka - i będzie używał frameworka w bardzo zły sposób - ponieważ skupi się głównie na części js, a nie na części szablonu (i stworzy niejasne i trudne do utrzymania kod). Aby zmienić te nawyki, straci sporo czasu (i prawdopodobnie będzie potrzebował trochę szczęścia i nauczyciela).

Więc moim zdaniem, opierając się na doświadczeniach z moimi studentami, lepiej byłoby dla nich, gdyby na początku użyli html-handlers-bind. Jak mówię, prawdą jest, że programy obsługi są wywoływane w zakresie globalnym, ale na tym etapie uczniowie zwykle tworzą małe aplikacje, które są łatwe do kontrolowania. Do pisania większych aplikacji wybierają frameworki.

Więc co robić?

Możemy ZAKTUALIZOWAĆ podejście Unobtrusive JavaScript i zezwolić na obsługę zdarzeń bind (ewentualnie z prostymi parametrami) w html (ale tylko handler bind - nie umieszczać logiki w onclick jak w OP quesiton). Więc moim zdaniem w surowym js / html powinno to być dozwolone

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

lub

Ale poniższe przykłady NIE powinny być dozwolone

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

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

Rzeczywistość się zmienia, nasz punkt widzenia też powinien

Kamil Kiełczewski
źródło
Cześć Kamil, Jestem początkującym użytkownikiem JavaScript i także miałem problemy z używaniem onclick, to znaczy, czy możesz wyjaśnić wady onclick w następujący sposób: 1. Możesz mieć przypisane tylko jedno zdarzenie inline. 2. Zdarzenia wbudowane są przechowywane jako właściwość elementu DOM i, podobnie jak wszystkie właściwości obiektu, można je nadpisać. 3. To zdarzenie będzie nadal uruchamiane, nawet jeśli usuniesz właściwość onclick.
mirzhal
1
Ad 1. Tak, tylko jeden, jednak moje doświadczenie (z angularem) pokazuje, że to wystarcza w większości sytuacji - jeśli nie to po prostu użyj addEventListener. Ad 2. Tak, można je nadpisać (zwykle jednak nikt tego nie robi) - na czym polega problem? Ad 3. Handler nie zostanie zwolniony po usunięciu atrybutu onclick - dowód tutaj
Kamil Kiełczewski
8

To nowy paradygmat o nazwie „ Dyskretny JavaScript ”. Obecny „standard sieciowy” mówi o oddzieleniu funkcjonalności od prezentacji.

To naprawdę nie jest „zła praktyka”, po prostu większość nowych standardów wymaga, abyś używał detektorów zdarzeń zamiast wbudowanego JavaScript.

Może to być również sprawa osobista, ale myślę, że jest to znacznie łatwiejsze do odczytania, gdy używasz detektorów zdarzeń, zwłaszcza jeśli masz więcej niż jedną instrukcję JavaScript, którą chcesz uruchomić.

Rocket Hazmat
źródło
2
Strona Wikipedii na ten temat ma już ponad 4 lata. To już nie jest nowy paradygmat. :)
Quentin
@David: Może nie jest „nowy”, ale są ludzie, którzy go nadal nie używają, więc jest dla nich „nowy”.
Rocket Hazmat
6

Przypuszczam, że twoje pytanie wywoła dyskusję. Ogólna idea jest taka, że ​​dobrze jest oddzielić zachowanie od struktury. Co więcej, afaik, wbudowany moduł obsługi kliknięć musi eval„stać się” prawdziwą funkcją javascript. I jest dość staroświecki, ale to dość chwiejny argument. Ach, cóż, przeczytaj trochę o tym na quirksmode.org

KooiInc
źródło
Nie chcę ponownie tworzyć świętej wojny, próbuję znaleźć prawdę: \
NiLL
2
  • Zdarzenia onclick działają w zakresie globalnym i mogą powodować nieoczekiwany błąd.
  • Dodanie zdarzeń onclick do wielu elementów DOM spowolni
    wydajność i wydajność.
Minghuan
źródło
0

Jeszcze dwa powody, aby nie używać wbudowanych programów obsługi:

Mogą wymagać żmudnej wyceny, unikając problemów

Biorąc pod uwagę dowolny ciąg znaków, jeśli chcesz być w stanie skonstruować obsługi inline, który wywołuje funkcję z tego łańcucha, dla ogólnego rozwiązania, trzeba będzie uciec atrybutów ograniczniki (z podmiotem powiązanym HTML), a będziesz trzeba zmienić separator używany dla ciągu znaków wewnątrz atrybutu, jak poniżej:

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>'
);

To niesamowicie brzydkie. Z powyższego przykładu, jeśli nie zamieniłeś 's, wynikałby błąd SyntaxError, ponieważ alert('foo'"bar')nie jest to poprawna składnia. Jeśli nie zastąpisz "s, przeglądarka zinterpretuje to jako koniec onclickatrybutu (rozdzielony znakami "s powyżej), co również byłoby niepoprawne.

Jeśli ktoś zwykle używa wbudowanych programów obsługi, należy pamiętać, aby za każdym razem robić coś podobnego do powyższego (i robić to dobrze ) , co jest żmudne i trudne do zrozumienia na pierwszy rzut oka. Lepiej jest całkowicie unikać obsługi wbudowanych, aby dowolny ciąg mógł być użyty w prostym zamknięciu:

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);

Czy to nie jest o wiele przyjemniejsze?


Łańcuch oscyloskopu wbudowanego programu obsługi jest niezwykle osobliwy

Jak myślisz, co zarejestruje następujący kod?

let disabled = true;
<form>
  <button onclick="console.log(disabled);">click</button>
</form>

Spróbuj, uruchom fragment. Prawdopodobnie nie tego się spodziewałeś. Dlaczego produkuje to, co robi? Ponieważ procedury obsługi wbudowane działają wewnątrz withbloków. Powyższy kod znajduje się w trzech with blokach: jeden dla document, jeden dla <form>i jeden dla <button>:

wprowadź opis obrazu tutaj

Ponieważ disabledjest właściwością przycisku, odwołanie disabledwewnątrz procedury obsługi wewnętrznej odnosi się do właściwości przycisku, a nie do disabledzmiennej zewnętrznej . Jest to dość sprzeczne z intuicją. withma wiele problemów: może być źródłem mylących błędów i znacznie spowalnia kod. Nie jest to nawet dozwolone w trybie ścisłym. Ale dzięki wbudowanym programom obsługi jesteś zmuszony uruchamiać kod przez withs - a nie tylko przez jeden with, ale przez wiele zagnieżdżonych withs. To jest szalone.

withnigdy nie powinny być używane w kodzie. Ponieważ procedury obsługi wbudowane wymagają niejawnie withwraz z całym swoim mylącym zachowaniem, należy również unikać obsługi wbudowanych.

CertainPerformance
źródło