Heim >Web-Frontend >js-Tutorial >Enthüllung der Magie von JavaScript
Wir verwenden jeden Tag Tonnen von Werkzeugen. Verschiedene Bibliotheken und Frameworks sind Teil unseres täglichen Jobs. Wir verwenden sie, weil wir das Rad für jedes Projekt nicht neu erfinden wollen, auch wenn wir nicht verstehen, was unter der Motorhaube vor sich geht. In diesem Artikel werden wir einige der magischen Prozesse in den beliebtesten Bibliotheken enthüllen. Wir werden auch sehen, ob wir ihr Verhalten replizieren können.
Mit dem Aufstieg einzelner Seitenanwendungen machen wir viele Dinge mit JavaScript. Ein großer Teil der Logik unserer Anwendung wurde in den Browser verschoben. Es ist eine häufige Aufgabe, Elemente auf der Seite zu generieren oder zu ersetzen. Der Code ähnelt dem, was unten gezeigt wird.
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Das Ergebnis ist ein neues
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Wir haben unsere eigene Versorgungsmethode StringTodom definiert, die ein temporäres
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Visuell gibt es auf der Seite keine Unterschiede. Wenn wir jedoch das generierte Markup mit den Entwicklertools von Chrome überprüfen, erhalten wir ein interessantes Ergebnis:
Es sieht so aus, als ob unsere StringTodom -Funktion nur ein Textknoten und nicht das tatsächliche
jQuery löst das Problem erfolgreich, indem er den richtigen Kontext erstellt und nur den erforderlichen Teil extrahiert. Wenn wir ein wenig in den Code der Bibliothek graben, sehen wir eine Karte wie diese:
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Jedes Element, das eine spezielle Behandlung erfordert, hat ein Array zugewiesen. Die Idee ist, das richtige DOM -Element zu konstruieren und sich von der Nestnegerebene abhängig zu machen, um das zu holen, was wir brauchen. Zum Beispiel müssen wir für das
Wenn wir eine Karte haben, müssen wir herausfinden, welche Art von Tag wir am Ende wollen. Der folgende Code extrahiert den TR aus
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Der Rest findet den richtigen Kontext und die Rückgabe des DOM -Elements. Hier ist die letzte Variante des FunktionsstringTodoms:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Beachten Sie, dass wir überprüfen, ob es in der Zeichenfolge ein Tag gibt - Übereinstimmung! = NULL. Wenn nicht, geben wir einfach einen Textknoten zurück. Es wird immer noch ein temporäres
Hier ist ein CodePen, der unsere Implementierung zeigt:
Siehe den Pen XLCGN von Krasimir TSonev (@Krasimir) auf CodePen.
Lassen Sie uns die wundervolle AngularJS -Abhängigkeitsinjektion untersuchen.
Wenn wir angularJS mithilfe von AngularJs beginnen, beeindruckt es mit seiner Zwei-Wege-Datenbindung. Das zweite, was wir bemerken, ist seine magische Abhängigkeitsinjektion. Hier ist ein einfaches Beispiel:
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
Das ist ein typischer AngularJS -Controller. Es führt eine HTTP -Anforderung aus, holt Daten von einer JSON -Datei ab und übergibt sie an den aktuellen Bereich. Wir führen die TodoctRL -Funktion nicht aus - wir haben keine Chance, Argumente zu verabschieden. Das Rahmen tut. Woher kommen diese $ Scope und $ HTTP -Variablen? Es ist eine super coole Funktion, die schwarzen Magie sehr ähnelt. Mal sehen, wie es gemacht wird.
Wir haben eine JavaScript -Funktion, die die Benutzer in unserem System anzeigt. Die gleiche Funktion benötigt Zugriff auf ein DOM -Element, um das generierte HTML und einen AJAX -Wrapper zu setzen, um die Daten zu erhalten. Um das Beispiel zu vereinfachen, werden wir die Daten und die HTTP-Anfrage verspottet.
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Wir werden das
Tag als Inhaltshalter verwenden. AjaxWrapper ist das Objekt, das die Anforderung simuliert, und DataMockup ist ein Array, das unsere Benutzer enthält. Hier ist die Funktion, die wir verwenden werden:<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Und wenn wir DisplayUser (Body, AjaxWrapper) ausführen, werden die drei Namen auf der Seite und /API /in unserer Konsole angeforderten Benutzer angezeigt. Wir könnten sagen, dass unsere Methode zwei Abhängigkeiten hat - Körper und Ajaxwrapper. Jetzt ist es also die Idee, die Funktion funktionieren zu lassen, ohne Argumente zu übergeben, d. H. Wir müssen das gleiche Ergebnis erzielen, indem wir nur DisplayUser () anrufen. Wenn wir dies bisher mit dem Code tun, ist das Ergebnis:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Und das ist normal, weil der Ajax -Parameter nicht definiert ist.
Die meisten Frameworks, die Mechanismen für die Injektion von Abhängigkeiten bereitstellen, haben ein Modul, das normalerweise Injektor bezeichnet wird. Um eine Abhängigkeit zu verwenden, müssen wir sie dort registrieren. Später, irgendwann, wird unsere Ressource der Logik der Anwendung von demselben Modul zur Verfügung gestellt.
Erstellen wir unseren Injektor:
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
Wir brauchen nur zwei Methoden. Der erste, registriert, akzeptiert unsere Ressourcen (Abhängigkeiten) und speichert sie intern. Der zweite akzeptiert das Ziel unserer Injektion - die Funktion, die Abhängigkeiten hat und sie als Parameter erhalten muss. Der Schlüsselmoment hier ist, dass der Injektor unsere Funktion nicht aufrufen sollte. Das ist unsere Aufgabe und wir sollten in der Lage sein, das zu kontrollieren. Was wir in der Auflösungsmethode tun können, ist, einen Schließ zurückzugeben, der das Ziel umrundet und es aufruft. Zum Beispiel:
<span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span><span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, '');</span>
Mit diesem Ansatz haben wir die Möglichkeit, die Funktion mit den erforderlichen Abhängigkeiten aufzurufen. Gleichzeitig ändern wir den Workflow der Anwendung nicht. Der Injektor ist immer noch etwas Unabhängiges und hält keine logischen Funktionen.
Die Funktion der DisplayUlers an die Auflösungsmethode hilft natürlich nicht.
<span>var stringToDom = function(str) { </span> <span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span> <span>}; </span> wrapMap<span>.optgroup = wrapMap.option; </span> wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span> wrapMap<span>.th = wrapMap.td; </span> <span>var element = document.createElement('div'); </span> <span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span> <span>if(match != null) { </span> <span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, ''); </span> <span>var map = wrapMap[tag] || wrapMap._default, element; </span> str <span>= map[1] + str + map[2]; </span> element<span>.innerHTML = str; </span> <span>// Descend through wrappers to the right content </span> <span>var j = map[0]+1; </span> <span>while(j--) { </span> element <span>= element.lastChild; </span> <span>} </span> <span>} else { </span> <span>// if only text is passed </span> element<span>.innerHTML = str; </span> element <span>= element.lastChild; </span> <span>} </span> <span>return element; </span><span>}</span>
wir bekommen immer noch den gleichen Fehler. Der nächste Schritt besteht darin, herauszufinden, was das übergebene Ziel braucht. Was sind seine Abhängigkeiten? Und hier ist der schwierige Teil, den wir von AngularJs übernehmen können. Ich habe wieder ein bisschen in den Code des Framework gegraben und fand Folgendes:
<span>function <span>TodoCtrl</span>($scope<span>, $http</span>) { </span> $http<span>.get('users/users.json').success(function(data) { </span> $scope<span>.users = data; </span> <span>}); </span><span>}</span>
Wir haben einige Teile absichtlich übersprungen, weil sie eher Implementierungsdetails sind. Das ist der Code, der für uns interessant ist. Die Annotate -Funktion ist so etwas wie unsere Auflösungsmethode. Es konvertiert die übergebene Zielfunktion in eine Zeichenfolge, entfernt die Kommentare (falls vorhanden) und extrahiert die Argumente. Nutzen wir das und sehen die Ergebnisse:
<span>var dataMockup = ['John', 'Steve', 'David']; </span><span>var body = document.querySelector('body'); </span><span>var ajaxWrapper = { </span> <span>get: function(path<span>, cb</span>) { </span> <span>console.log(path + ' requested'); </span> <span>cb(dataMockup); </span> <span>} </span><span>}</span>
Hier ist die Ausgabe in der Konsole:
Wenn wir das zweite Element des Argdecl -Arrays erhalten, werden wir die Namen der erforderlichen Abhängigkeiten finden. Genau das brauchen wir, denn die Namen können die Ressourcen aus der Speicherung des Injektors liefern. Hier ist die Version, die unsere Ziele funktioniert und erfolgreich abdeckt:
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Beachten Sie, dass wir .Split (/,?/G) verwenden, um den String -Domel Ajax in ein Array umzuwandeln. Danach prüfen wir, ob die Abhängigkeiten registriert sind und ob wir sie an die Zielfunktion weitergeben. Der Code außerhalb des Injektors sieht so aus:
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Der Vorteil einer solchen Implementierung besteht darin, dass wir das DOM -Element und den Ajax -Wrapper in vielen Funktionen injizieren können. Wir könnten sogar die Konfiguration unserer Anwendung wie diese verteilen. Es ist nicht erforderlich, Objekte von Unterricht zu Unterricht zu übergeben. Es sind nur die Register- und Auflösungsmethoden.
Natürlich ist unser Injektor nicht perfekt. Es gibt noch einige Raum für Verbesserungen, wie zum Beispiel die Unterstützung der Bereichsumfangsdefinition. Die Zielfunktion ist derzeit mit einem neu erstellten Bereich aufgerufen, aber normalerweise wollen wir unsere eigenen übergeben. Wir sollten auch unterstützen, auch benutzerdefinierte Argumente zusammen mit den Abhängigkeiten zu senden.
Der Injektor wird noch komplizierter, wenn wir unseren Code nach der Minifikation funktionieren möchten. Wie wir wissen, ersetzen die Minifiker die Namen der Funktionen, Variablen und sogar die Argumente der Methoden. Und weil unsere Logik auf diesen Namen beruht, müssen wir über Workaround nachdenken. Eine mögliche Lösung stammt erneut von AngularJs:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Anstelle der Displayser übergeben wir die Namen der tatsächlichen Abhängigkeiten.
unser Beispiel in Aktion:
Siehe den Stift BXDAR von Krasimir TSonev (@Krasimir) auf CodePen.
Ember ist heutzutage eines der beliebtesten Frameworks. Es hat unzählige nützliche Funktionen. Es gibt eine besonders interessante - berechnete Eigenschaften. Zusammenfassend sind berechnete Eigenschaften Funktionen, die als Eigenschaften fungieren. Sehen wir uns ein einfaches Beispiel aus der Dokumentation des Embers:
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
Es gibt eine Klasse, die FirstName- und Lastname -Eigenschaften hat. Der Fullname der berechneten Eigenschaft gibt die verkettete Zeichenfolge zurück, die den vollständigen Namen der Person enthält. Das Seltsame ist der Teil, in dem wir eine .Property -Methode gegen die auf Vollnamen angewendete Funktion verwenden. Ich persönlich habe das nirgendwo anders gesehen. Und wiederum enthüllt das schnelle Aussehen des Code des Frameworks die Magie:
<span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span><span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, '');</span>
Die Bibliothek optimiert den Prototyp des globalen Funktionsobjekts durch Hinzufügen einer neuen Eigenschaft. Es ist ein schöner Ansatz, während der Definition einer Klasse eine Logik auszuführen.
Ember verwendet Getter und Setter, um mit den Daten des Objekts zu arbeiten. Dies vereinfacht die Implementierung der berechneten Eigenschaften, da wir zuvor eine weitere Ebene haben, um die tatsächlichen Variablen zu erreichen. Es wird jedoch noch interessanter sein, wenn wir berechnete Eigenschaften mit den einfachen JavaScript -Objekten verwenden können. Wie zum Beispiel:
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Name wird als reguläre Eigenschaft verwendet, aber in der Praxis ist eine Funktion, die FirstName und Lastname erhält oder setzt.
Es gibt ein Build-In-Merkmal von JavaScript, das uns helfen könnte, die Idee zu verwirklichen. Schauen Sie sich den folgenden Ausschnitt an:
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Das Object.DefineProperty -Methode könnte einen Umfang, einen Namen einer Eigenschaft, einen Gettter und einen Setter akzeptieren. Wir müssen nur den Körper der beiden Methoden schreiben. Und das war's. Wir werden den obigen Code ausführen und die erwarteten Ergebnisse erhalten:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Object.DefineProperty ist genau das, was wir brauchen, aber wir möchten den Entwickler nicht zwingen, ihn jedes Mal zu schreiben. Möglicherweise müssen wir eine Polyfüllung bereitstellen, zusätzliche Logik oder ähnliches ausführen. Im idealen Fall möchten wir eine Schnittstelle ähnlich wie die von Ember bereitstellen. Nur eine Funktion ist Teil der Klassendefinition. In diesem Abschnitt schreiben wir eine Dienstprogrammfunktion namens Computize, die unser Objekt verarbeitet und irgendwie die Namensfunktion mit demselben Namen in eine Eigenschaft umwandelt.
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
wir möchten die Namensmethode als Setter und gleichzeitig wie ein Getter verwenden. Dies ähnelt den berechneten Eigenschaften von Ember.
Lassen Sie uns jetzt unsere eigene Logik in den Prototyp des Funktionsobjekts hinzufügen:
<span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span><span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, '');</span>
Sobald wir die obigen Zeilen hinzufügen, können wir. Computed () zum Ende jeder Funktionsdefinition hinzufügen:
<span>var stringToDom = function(str) { </span> <span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span> <span>}; </span> wrapMap<span>.optgroup = wrapMap.option; </span> wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span> wrapMap<span>.th = wrapMap.td; </span> <span>var element = document.createElement('div'); </span> <span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span> <span>if(match != null) { </span> <span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, ''); </span> <span>var map = wrapMap[tag] || wrapMap._default, element; </span> str <span>= map[1] + str + map[2]; </span> element<span>.innerHTML = str; </span> <span>// Descend through wrappers to the right content </span> <span>var j = map[0]+1; </span> <span>while(j--) { </span> element <span>= element.lastChild; </span> <span>} </span> <span>} else { </span> <span>// if only text is passed </span> element<span>.innerHTML = str; </span> element <span>= element.lastChild; </span> <span>} </span> <span>return element; </span><span>}</span>
Infolgedessen enthält die Namenseigenschaft keine Funktion mehr, sondern ein Objekt, das die Eigenschaft der tatsächlichen und func -Eigenschaft berechnet hat, die mit der alten Funktion gefüllt ist. Die wirkliche Magie geschieht bei der Implementierung des Computize -Helfers. Es durchläuft alle Eigenschaften des Objekts und verwendet Object.defineProperty, wo wir Eigenschaften berechnet haben:
<span>function <span>TodoCtrl</span>($scope<span>, $http</span>) { </span> $http<span>.get('users/users.json').success(function(data) { </span> $scope<span>.users = data; </span> <span>}); </span><span>}</span>
Beachten Sie, dass wir den ursprünglichen Eigenschaftsnamen löschen. In einigen Browser -Objekten. DefineProperty arbeitet nur für Eigenschaften, die noch nicht definiert sind.
Hier ist die endgültige Version des Benutzerobjekts, das die. Computed () -Funktion verwendet.
<span>var dataMockup = ['John', 'Steve', 'David']; </span><span>var body = document.querySelector('body'); </span><span>var ajaxWrapper = { </span> <span>get: function(path<span>, cb</span>) { </span> <span>console.log(path + ' requested'); </span> <span>cb(dataMockup); </span> <span>} </span><span>}</span>
Eine Funktion, die den vollständigen Namen zurückgibt, wird zum Ändern von FirstName und LastName verwendet. Das ist die Idee hinter der Überprüfung bestandener Argumente und der Verarbeitung des ersten. Wenn es existiert, teilen wir es auf und wenden die Werte auf die normalen Eigenschaften an.
Wir haben bereits die gewünschte Verwendung erwähnt, aber lassen Sie es uns noch einmal sehen:
<span>var displayUsers = function(domEl<span>, ajax</span>) { </span> ajax<span>.get('/api/users', function(users) { </span> <span>var html = ''; </span> <span>for(var i=0; i < users.length; i++) { </span> html <span>+= '<p>' + users[i] + '</p>'; </span> <span>} </span> domEl<span>.innerHTML = html; </span> <span>}); </span><span>}</span>
Die folgende CodePen zeigt unsere Arbeit in der Praxis:
Siehe den Pen Ahpqo von Krasimir TSonev (@Krasimir) auf CodePen.
Sie haben wahrscheinlich von Facebooks Framework React gehört. Es basiert auf der Idee, dass alles eine Komponente ist. Interessant ist die Definition der Komponente. Schauen wir uns das folgende Beispiel an:
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Das erste, worüber wir nachdenken, ist, dass dies ein JavaScript ist, aber es ist ein ungültiges. Es gibt eine Renderfunktion und wird wahrscheinlich einen Fehler werfen. Der Trick ist jedoch, dass dieser Code mit einem benutzerdefinierten Typ -Attribut in <script> -TAG eingegeben wird. Der Browser verarbeitet es nicht, was bedeutet, dass wir vor Fehlern sicher sind. React hat einen eigenen Parser, der den von uns geschriebenen Code in ein gültiges JavaScript übersetzt. Die Entwickler von Facebook bezeichneten die XML -ähnliche Sprache <em> jsx . Ihr JSX -Transformator ist 390.000 und enthält ungefähr 12000 Codezeilen. Es ist also etwas komplex. In diesem Abschnitt werden wir etwas Einfaches, aber immer noch ziemlich mächtig schaffen. Eine JavaScript -Klasse, die HTML -Vorlagen im Reaktionsstil analysiert. </script>
Der Ansatz, den Facebook verfolgte, besteht darin, den JavaScript -Code mit HTML Markup zu mischen. Nehmen wir also an, wir haben die folgende Vorlage:
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
und eine Komponente, die es verwendet:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Die Idee ist, dass wir auf die ID der Vorlage hinweisen und die angewendeten Daten definieren. Der letzte Teil unserer Implementierung ist die tatsächliche Engine, die die beiden Elemente verschmilzt. Nennen wir es Engine und starten Sie es so:
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
Wir erhalten den Inhalt des
Schreiben wir jetzt unsere Parse -Funktion. Unsere erste Aufgabe ist es, die HTML von den Ausdrücken zu unterscheiden. Mit Ausdrücken meinen wir Strings zwischen . Wir werden einen Regex verwenden, um sie und eine einfache während der Schleife zu finden, um alle Übereinstimmungen durchzugehen:
<span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span><span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, '');</span>
Das Ergebnis des obigen Codes lautet wie folgt:
<span>var stringToDom = function(str) { </span> <span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span> <span>}; </span> wrapMap<span>.optgroup = wrapMap.option; </span> wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span> wrapMap<span>.th = wrapMap.td; </span> <span>var element = document.createElement('div'); </span> <span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span> <span>if(match != null) { </span> <span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, ''); </span> <span>var map = wrapMap[tag] || wrapMap._default, element; </span> str <span>= map[1] + str + map[2]; </span> element<span>.innerHTML = str; </span> <span>// Descend through wrappers to the right content </span> <span>var j = map[0]+1; </span> <span>while(j--) { </span> element <span>= element.lastChild; </span> <span>} </span> <span>} else { </span> <span>// if only text is passed </span> element<span>.innerHTML = str; </span> element <span>= element.lastChild; </span> <span>} </span> <span>return element; </span><span>}</span>
Es gibt nur einen Ausdruck und sein Inhalt ist der Titel. Der erste intuitive Ansatz, den wir verfolgen können, ist die Verwendung der Ersatzfunktion des JavaScripts und Ersatz für den Title %> durch die Daten aus dem bestandenen Comp -Objekt. Dies funktioniert jedoch nur mit den einfachen Eigenschaften. Was ist, wenn wir verschachtelte Objekte haben oder auch wenn wir eine Funktion verwenden möchten? Wie zum Beispiel:
<span>function <span>TodoCtrl</span>($scope<span>, $http</span>) { </span> $http<span>.get('users/users.json').success(function(data) { </span> $scope<span>.users = data; </span> <span>}); </span><span>}</span>
Anstatt einen komplexen Parser zu erstellen und eine neue Sprache fast zu erfinden, verwenden wir möglicherweise reines JavaScript. Das einzige, was wir tun müssen, ist die neue Funktionssyntax zu verwenden.
<span>var dataMockup = ['John', 'Steve', 'David']; </span><span>var body = document.querySelector('body'); </span><span>var ajaxWrapper = { </span> <span>get: function(path<span>, cb</span>) { </span> <span>console.log(path + ' requested'); </span> <span>cb(dataMockup); </span> <span>} </span><span>}</span>
Wir sind in der Lage, den Körper einer Funktion zu konstruieren, die später ausgeführt wird. Wir kennen also die Position unserer Ausdrücke und was genau dahinter steht. Wenn wir ein temporäres Array und einen Cursor verwenden, sieht unser While -Zyklus so aus:
<span>var text = $('<div>Simple text</div>'); </span> <span>$('body').append(text);</span>
Die Ausgabe in der Konsole zeigt, dass wir uns auf dem richtigen Weg befinden:
<span>var stringToDom = function(str) { </span> <span>var temp = document.createElement('div'); </span> temp<span>.innerHTML = str; </span> <span>return temp.childNodes[0]; </span><span>} </span><span>var text = stringToDom('<div>Simple text</div>'); </span> <span>document.querySelector('body').appendChild(text);</span>
Das Code -Array sollte in eine Zeichenfolge umgewandelt werden, die eine Funktion eines Funktion ist. Zum Beispiel:
<span>var tableRow = $('<tr><td>Simple text</td></tr>'); </span><span>$('body').append(tableRow); </span> <span>var tableRow = stringToDom('<tr><td>Simple text</td></tr>'); </span><span>document.querySelector('body').appendChild(tableRow);</span>
Es ist ziemlich einfach, dieses Ergebnis zu erzielen. Wir können eine Schleife schreiben, die alle Elemente des Code -Arrays durchläuft und überprüft, ob es sich bei dem Element um eine Zeichenfolge oder ein Objekt handelt. Dies deckt jedoch erneut nur einen Teil der Fälle ab. Was ist, wenn wir die folgende Vorlage haben:
<span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span><span>}; </span>wrapMap<span>.optgroup = wrapMap.option; </span>wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span>wrapMap<span>.th = wrapMap.td;</span>
Wir können nicht nur die Ausdrücke verkettet und erwarten, dass die Farben aufgelistet sind. Anstatt eine Zeichenfolge an eine String anzuhängen, werden wir sie in einem Array sammeln. Hier ist die aktualisierte Version der Parse -Funktion:
<span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span><span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, '');</span>
Sobald das Code -Array gefüllt ist, beginnen wir mit der Erstellung des Körpers der Funktion. Jede Zeile der Vorlage wird in einem Array r gespeichert. Wenn es sich bei der Zeile um eine Zeichenfolge handelt, klären wir sie ein wenig, indem wir den Zitaten entkommen und die neuen Zeilen und Registerkarten entfernen. Das Array wird über die Push -Methode hinzugefügt. Wenn wir ein Code -Snippet haben, überprüfen wir, ob es sich nicht um einen gültigen JavaScript -Operator handelt. Wenn ja, dann fügen wir es dem Array nicht hinzu, sondern löschen es einfach als neue Zeile. Die Konsole.log am Ende Ausgänge:
<span>var stringToDom = function(str) { </span> <span>var wrapMap = { </span> <span>option: [1, '<select multiple="multiple">', '</select>'], </span> <span>legend: [1, '<fieldset>', '</fieldset>'], </span> <span>area: [1, '<map>', '</map>'], </span> <span>param: [1, '<object>', '</object>'], </span> <span>thead: [1, '<table>', '</table>'], </span> <span>tr: [2, '<table><tbody>', '</tbody></table>'], </span> <span>col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], </span> <span>td: [3, '<table><tbody><tr>', '</tr></tbody></table>'], </span> <span>_default: [1, '<div>', '</div>'] </span> <span>}; </span> wrapMap<span>.optgroup = wrapMap.option; </span> wrapMap<span>.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; </span> wrapMap<span>.th = wrapMap.td; </span> <span>var element = document.createElement('div'); </span> <span>var match = <span>/<<span>\s*\w.*?></span>/g</span>.exec(str); </span> <span>if(match != null) { </span> <span>var tag = match[0].replace(<span>/</g</span>, '').replace(<span>/>/g</span>, ''); </span> <span>var map = wrapMap[tag] || wrapMap._default, element; </span> str <span>= map[1] + str + map[2]; </span> element<span>.innerHTML = str; </span> <span>// Descend through wrappers to the right content </span> <span>var j = map[0]+1; </span> <span>while(j--) { </span> element <span>= element.lastChild; </span> <span>} </span> <span>} else { </span> <span>// if only text is passed </span> element<span>.innerHTML = str; </span> element <span>= element.lastChild; </span> <span>} </span> <span>return element; </span><span>}</span>
Schön, nicht wahr? Ordnungsgemäß formatiertes funktionierendes JavaScript, das im Kontext unserer Komponente ausgeführt wird, erzeugt das gewünschte HTML -Markup.
Das Letzte, was übrig bleibt, ist das tatsächliche Ausführen unserer praktisch erstellten Funktion:
<span>function <span>TodoCtrl</span>($scope<span>, $http</span>) { </span> $http<span>.get('users/users.json').success(function(data) { </span> $scope<span>.users = data; </span> <span>}); </span><span>}</span>
Wir haben unseren Code in eine mit Anweisung eingewickelt, um ihn im Kontext der Komponente auszuführen. Ohne das müssen wir diesen und diese. Colors anstelle von Titel und Farben verwenden.
Hier zeigt ein Codepen das Endergebnis:
Siehe den Pen Gahej von Krasimir TSonev (@Krasimir) auf CodePen.
Hinter den großen Frameworks und Bibliotheken befinden sich intelligente Entwickler. Sie fanden und verwenden knifflige Lösungen, die nicht trivial und sogar irgendwie magisch sind. In diesem Artikel haben wir einige dieser Magie enthüllt. Es ist schön, dass wir in der JavaScript -Welt aus dem Besten lernen und ihren Code verwenden können.
Der Code aus diesem Artikel kann von Github
heruntergeladen werden let person = {
FirstName: "John",
LastName: "doe",
toString: function () {
return this.firstname "zurück" "this.lastName;
}
}; // "John Doe"
Welche Bedeutung haben magische Funktionen in JavaScript? Sie können Ihren Code intuitiver und einfacher zu verstehen und Ihre Daten zu verkapulieren und zu schützen. Hier sind einige Beispiele für magische Funktionen in JavaScript:
1. HasownProperty (): Diese Methode gibt einen Booleschen zurück, der angibt, ob das Objekt die angegebene Eigenschaft hat. Magische Methoden können sehr nützlich sein, sie haben auch einige Einschränkungen. Zum einen können sie Ihren Code komplexer und debuggerer machen, insbesondere wenn Sie nicht mit der Funktionsweise vertraut sind. Sie können auch zu unerwartetem Verhalten führen, wenn sie nicht korrekt verwendet werden. "Magische Methoden". Es gibt jedoch bestimmte Methoden, die sich ähnlich verhalten, wie z. B. toString () und valueof (). Diese Methoden werden in bestimmten Situationen automatisch aufgerufen, ähnlich wie magische Methoden in anderen Sprachen. Geben Sie das Verständnis ein, wann und warum Sie sie verwenden, um sie sparsam zu verwenden, um Komplexität zu vermeiden, und testen Sie Ihren Code immer gründlich, um sicherzustellen, dass er sich wie erwartet verhält. Frameworks wie React oder Vue? Die Art und Weise, wie sie verwendet werden, kann jedoch je nach Rahmen variieren. Es ist immer am besten, sich auf die Dokumentation des spezifischen Frameworks zu beziehen.
Das obige ist der detaillierte Inhalt vonEnthüllung der Magie von JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!