Wenn ich dies mit reinem CSS umsetze. Ich begann, JavaScript und Stilklassen zu verwenden, um die Funktionalität abzurunden.
Dann habe ich einige Ideen: Ich möchte Delegierte Ereignisse (Ereignisdelegierung) verwenden, aber keine Abhängigkeiten haben und keine Bibliotheken einbinden, einschließlich jQuery. Ich muss die Ereignisdelegation selbst implementieren.
Werfen wir zunächst einen Blick darauf, was Event-Delegation ist. Wie funktionieren sie und wie wird dieser Mechanismus implementiert?
OK, welches Problem wird damit gelöst?
Schauen wir uns zunächst ein einfaches Beispiel an.
Nehmen wir an, wir haben eine Reihe von Schaltflächen. Ich klicke jeweils auf eine Schaltfläche und möchte dann, dass der angeklickte Status auf „aktiv“ gesetzt wird. Bei erneutem Klick aktiv abbrechen.
Dann können wir etwas HTML schreiben:
<ul class="toolbar"> <li><button class="btn">Pencil</button></li> <li><button class="btn">Pen</button></li> <li><button class="btn">Eraser</button></li> </ul>
Ich kann einige Standard-Javascript-Ereignisse verwenden, um die obige Logik zu verarbeiten:
var buttons = document.querySelectorAll(".toolbar .btn"); for(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
Es sieht gut aus, aber Es funktioniert tatsächlich nicht so, wie Sie es erwarten.
Fallen von Schließungen
Wenn Sie Erfahrung in der JavaScript-Entwicklung haben, wird dieses Problem offensichtlich sein.
Für Uneingeweihte ist die Schaltflächenvariable geschlossen und die entsprechende Schaltfläche wird jedes Mal gefunden ... aber tatsächlich gibt es hier nur eine Schaltfläche, die bei jeder Schleife neu zugewiesen wird.
Die erste Schleife zeigt auf die erste Schaltfläche, dann auf die zweite. Wenn Sie jedoch klicken, zeigt die Schaltflächenvariable immer auf das letzte Schaltflächenelement. Dies ist das Problem.
Was wir brauchen, ist ein stabiler Bereich.
var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
Hinweis* Die Struktur des obigen Codes ist etwas kompliziert. Sie können die aktuelle Schaltflächenvariable auch einfach mit einem Abschluss schließen und speichern, wie unten gezeigt:
var buttons = document.querySelectorAll(".toolbar .btn"); for(var i = 0; i < buttons.length; i++) { (function(button) { button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); })(buttons[i]) }
Jetzt funktioniert es jetzt einwandfrei. Auf die richtige Schaltfläche zu zeigen ist immer
Was ist also falsch an dieser Lösung?
Diese Lösung sieht in Ordnung aus, aber wir können es tatsächlich besser machen.
Zuerst haben wir zu viele Verarbeitungsfunktionen erstellt. An jede passende .toolbar-Schaltfläche sind ein Ereignis-Listener und ein Callback-Handler gebunden. Wenn nur drei Schaltflächen vorhanden sind, kann diese Ressourcenzuweisung ignoriert werden.
Was aber, wenn wir 1.000 haben?
<ul class="toolbar"> <li><button id="button_0001">Foo</button></li> <li><button id="button_0002">Bar</button></li> // ... 997 more elements ... <li><button id="button_1000">baz</button></li> </ul>
Es wird nicht abstürzen, aber das ist nicht die beste Lösung. Wir weisen viele unnötige Funktionen zu. Lassen Sie es uns so umgestalten, dass es nur einmal angehängt wird und nur eine Funktion bindet, um potenziell Tausende von Aufrufen verarbeiten zu können.
Anstatt die Schaltflächenvariable zu schließen, um das Objekt zu speichern, auf das wir zu diesem Zeitpunkt geklickt haben, können wir das Ereignisobjekt verwenden, um das Objekt abzurufen, auf das wir zu diesem Zeitpunkt geklickt haben.
Das Ereignisobjekt verfügt über einige Metadaten. Bei mehreren Bindungen können wir currentTarget verwenden, um das aktuell gebundene Objekt abzurufen:
var buttons = document.querySelectorAll(".toolbar button"); var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
Nicht schlecht! Dies vereinfacht jedoch nur eine einzelne Funktion und macht sie besser lesbar, ist aber dennoch mehrfach gebunden.
Allerdings können wir es besser machen.
Nehmen wir an, dass wir dieser Liste dynamisch einige Schaltflächen hinzufügen. Anschließend fügen wir auch Ereignisbindungen für diese dynamischen Elemente hinzu und entfernen sie. Dann müssen wir die von diesen Verarbeitungsfunktionen verwendeten Variablen und den aktuellen Kontext beibehalten, was unzuverlässig klingt.
Vielleicht gibt es auch andere Möglichkeiten.
Lassen Sie uns zunächst vollständig verstehen, wie Ereignisse funktionieren und wie sie im DOM bereitgestellt werden.
Wie Ereignisse funktionieren
Wenn der Benutzer auf ein Element klickt, wird ein Ereignis generiert, um den Benutzer über das aktuelle Verhalten zu informieren. Es gibt drei Phasen, in denen ein Ereignis ausgelöst wird:
Erfassungsphase: Erfassung
Auslösephase: Ziel
Bubbling-Phase: Bubbling
Dieses Ereignis beginnt mit dem Start von vor dem Dokument und arbeiten Sie sich nach unten vor, um das Objekt zu finden, auf das das aktuelle Ereignis geklickt hat. Wenn das Ereignis das angeklickte Objekt erreicht, kehrt es entlang des ursprünglichen Pfads zurück (Blasenprozess), bis es den gesamten DOM-Baum verlässt.
Hier ist ein HTML-Beispiel:
<html> <body> <ul> <li id="li_1"><button id="button_1">Button A</button></li> <li id="li_2"><button id="button_2">Button B</button></li> <li id="li_3"><button id="button_3">Button C</button></li> </ul> </body> </html>
Wenn Sie auf Schaltfläche A klicken, sieht der Pfad des Ereignisses wie folgt aus:
START
|
|. HTML 🎜> V #document/
Ende
Beachten Sie, dass Sie es auf dem erfassen können Pfad des Ereignisses, das durch Ihren Klick generiert wird. Wir sind sehr sicher, dass dieses Ereignis das übergeordnete Element ul durchläuft. Wir können unsere Ereignisverarbeitung an das übergeordnete Element binden und unsere Lösung vereinfachen. Dies wird als Ereignisdelegation und Proxy (delegierte Ereignisse) bezeichnet.
注* 其实Flash/Silverlight/WPF开发的事件机制是非常近似的,这里有一张他们的事件流程图。 除了Silverlight 3使用了旧版IE的仅有冒泡阶段的事件模型外,基本上也都有这三个阶段。(旧版IE和SL3的事件处理只有一个从触发对象冒泡到根对象的过程,可能是为了简化事件的处理机制。)
事件委托代理
委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。
让我们看一个具体的例子,我们看看上文的那个工具栏的例子:
<ul class="toolbar"> <li><button class="btn">Pencil</button></li> <li><button class="btn">Pen</button></li> <li><button class="btn">Eraser</button></li> </ul>
因为我们知道单击button元素会冒泡到UL.toolbar元素,让我们将事件处理放到这里试试。我们需要稍微调整一下:
var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
这样我们清理了大量的代码,再也没有循环了。注意我们使用了e.target代替了之前的e.currentTarget。这是因为我们在一个不同的层次上面进行了事件侦听。
e.target 是当前触发事件的对象,即用户真正单击到的对象。
e.currentTarget 是当前处理事件的对象,即事件绑定的对象。
在我们的例子中e.currentTarget就是UL.toolbar。
注* 其实不止事件机制,在整个UI构架上FLEX(不是Flash) /Silverlight /WPF /Android的实现跟WEB也非常相似,都使用XML(HTML)实现模板及元素结构组织,Style(CSS)实现显示样式及UI,脚本(AS3,C#,Java,JS)实现控制。不过Web相对其他平台更加开放,不过历史遗留问题也更多。但是几乎所有的平台都支持Web标准,都内嵌有类似WebView这样的内嵌Web渲染机制,相对各大平台复杂的前端UI框架和学习曲线来说,使用Web技术实现Native APP的前端UI是非常低成本的一项选择。