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>
javascript
html
NiLL
źródło
źródło
#
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.<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.Odpowiedzi:
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ą
źródło
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ątrzonClick
i ż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ć.onclick
mogą 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,addEventListener
któ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ą tegoonclick
atrybutu i zmieniają pojęcie, czym powinna być separacja. Przeniesienie z HTMLonclick
do komponentu, który to obsługuje, może być bardziej efektywnym „krokowym” procesem uczenia się.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.
źródło
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 wykonaszdoSomething()
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 idoSomething()
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ć.źródło
Nie jest to dobre z kilku powodów:
eval
Najprościej byłoby dodać
name
atrybut 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
id
zamiast nazwy i użycieaddEventListener()
zamiast używania,onclick
ponieważ umożliwia to powiązanie wielu funkcji z jednym zdarzeniem.źródło
addEventListener()
i dlaczego.Jest kilka powodów:
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ń.
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; }); })
źródło
<a href="https://stackoverflow.com/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
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 error
tego, 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
onclick
zdarzenie 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 (addEventListener
ma 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
Pokaż fragment kodu
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>
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
źródło
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 tutajTo
nowyparadygmat 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ć.
źródło
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źródło
wydajność i wydajność.
źródło
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, '"') // 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 konieconclick
atrybutu (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
with
bloków. Powyższy kod znajduje się w trzechwith
blokach: jeden dladocument
, jeden dla<form>
i jeden dla<button>
:Pokaż fragment kodu
let disabled = true;
<form> <button onclick="console.log(disabled);">click</button> </form>
Ponieważ
disabled
jest właściwością przycisku, odwołaniedisabled
wewnątrz procedury obsługi wewnętrznej odnosi się do właściwości przycisku, a nie dodisabled
zmiennej zewnętrznej . Jest to dość sprzeczne z intuicją.with
ma 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 przezwith
s - a nie tylko przez jedenwith
, ale przez wiele zagnieżdżonychwith
s. To jest szalone.with
nigdy nie powinny być używane w kodzie. Ponieważ procedury obsługi wbudowane wymagają niejawniewith
wraz z całym swoim mylącym zachowaniem, należy również unikać obsługi wbudowanych.źródło