Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

Javascript Workshop - "Programming serious Javascript while staying sane"

... war das Thema des Javascript Workshops für Webentwickler, den wir am vergangenen Samstag ausrichteten. Das Zielpublikum: Webentwickler, die bereits mit Javascript arbeiten können und ihre Javascript Fähigkeiten auf das nächste Level heben wollten. Me
by

Steffen Müller

on 20 May 2011

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of Javascript Workshop - "Programming serious Javascript while staying sane"

Javascript Workshop Programming serious Javascript while staying sane Professional JS Performance Lerne, das Asynchrone zu lieben Javascript ist asynchron. Das ist praktisch, denn unsere JS Programme sind auch immer asynchron (AJAX Callbacks, User Events).

Closures sind das ideale Mittel, um Asynchronität effizient zu handeln! Keeping the right scope Klassiker:

function mysuperobject (node) {
this.isClicked = false;
node.onclick = this.onClick;
};
mysuperobject.prototype.onClick = function() {
this.isClicked = true;
};

Preisfrage: welches Objekt freut sich nach dem Klick über die Eigenschaft "isClicked"? Richtig: window! Aber warum?

Das Event ist nur eine Funktion. Und wie wir gelernt haben, wechselt ein einfacher Funktionsaufruf nicht den Kontext (im Gegensatz zu einem Methodenaufruf).

Das bedeutet, das Event wird im Kontext dessen ausgeführt, das das Event auslöst - und das ist bestimmt nicht meine mysuperobject Instanz.

Lösungsvorschläge? Lösung A: eindeutige Benamung Super Idee:

function mysuperobject (node) {
window.isMySuperObjectClicked = false;
node.onclick = this.onClick;
};
mysuperobject.prototype.onClick = function() {
window.isMySuperObjectClicked = true;
};

Pro: gar nix
Contra: was machen wir bei mehreren mysuperobjects? Lösung B: Closures! Nummer 1: Closures to the rescue!

function mysuperobject (node) {
var isClicked = false;
node.onclick = function() {
isClicked = true;
};
};

Pro: läuft.
Contra: Objektorientierte Methodenverteilung läuft nicht.

Trotzdem manchmal bis meistens ein guter Plan. Lösung C: Hitching Hitch Bibliotheken gibts in Dojo (dojo.hitch) oder jQuery (bauen wir gleich).

function mysuperobject (node) {
this.isClicked = false;
node.onclick = dojo.hitch(this, this.onClick);
};
mysuperobject.prototype.onClick = function() {
this.isClicked = true;
};

Pro: läuft und ist OOP.
Kontra: Etwas mehr "verbose" als eine einfache Closure. Was tut Hitching? Es fixiert 'this' für die übergebene Funktion, indem es eine Closure erzeugt, die dann den Kontext setzt und die Funktion aufruft.

Next: der gesamte Quelltext eines Hitching Plugins für jQuery!
Selbst entwickelt, Baby! Hitching plugin für jQuery (function($) {
$.hitch = function(scope, fn) {
return function () {
fn.apply(scope || this, arguments);
};
};
})(jQuery);

BAM! Von jetzt haben eure Event Callbacks immer den korrekten Kontext! Hintergrund: apply & call apply und call sind mächtige Methoden jeder Funktion. Man kann mit jeder Funktion:

anyfunction.call(mynewself, a,b,c);

oder

anyfunction.apply(mynewself, [a,b,c]);

Ein bisschen wie call_user_func und call_user_func_array, verehrte PHPler. Mit einem Unterschied:

der erste Parameter bestimmt "this" in der Funktion. Klassiker:

function mySpaceshuttle () {
this.destination = "Moon";
window.setTimeout("this.flyToDestination()", 1000);
};
mySpaceshuttle.prototype.flyToDestination = function () {
alert("Flying to "+this.destination);
};

mySpaceshuttle();

Wer kennt das Resultat? Na? Hitching to the rescue!

Was nur wenige wissen: der Funktionsparameter für setTimeout und setInterval kann auch eine Funktion sein.
Hitch it!

function mySpaceshuttle (){
this.destination = "Moon";
window.setTimeout($.hitch(this, this.flyToDestination), 1000);
};

mySpaceshuttle.prototype.flyToDestination = function () {
alert("Flying to "+this.destination);
};

mySpaceshuttle(); Timeouts und Intervals So, ihr habt es jetzt also total drauf mit Closures? Beispiel:

var self = this;
for (var i = 0; i < childnodes.length; i++) {
childnodes[i].onclick = function() {
alert("Thank you for clicking my "+i"+th child named "+childnodes[i].nodeValue);
};
}

Sieht gut und einfach aus, oder? Aber was wird wohl passieren? Closure Context Frenzy!

Beim Klick auf JEDES Child kommt die Meldung für das LETZTE Child. Warum?

Der Closure Kontext! Jede Closure verwendet den gleichen Kontext mit dem gleichen, voll aktuellen i.
Was sonst ein Hammerfeature ist, macht uns gerade kaputt.

Lösung? Wrapping anonymous Closure context fixation?

Wäre hitch nicht eine Lösung?

for (var i = 0; i < childnodes.length; i++) {
childnodes[i].onclick = $.hitch(this, function() {
alert("Thank you, "+childnodes[i].nodeValue);
});
}

NEIN - denn hitch fixiert ja nur 'this', aber nicht den restlichen Kontext! Anonymous function fixation!

Die Lösung (tief durchatmen):

for (var i = 0; i < childnodes.length; i++) {
(function(myfixedi) {
childnodes[myfixedi].onclick = $.hitch(this, function() {
alert("Thank you for clicking, "+childnodes[myfixedi].nodeValue);
});
})(i);
}

Nicht unbedingt... leserlich, aber manchmal sehr sinnvoll. Elegantere Lösung

Wann immer man wert auf lesbaren Code legt (IMMER!), sollte man eher eine benannte Funktion nehmen:

for (var i = 0; i < childnodes.length; i++) {
childnodes[i].onclick = this.createChildClickEvent(i);
}

Aber es gibt Fälle, in denen die anonyme Variante sinnvoll ist. Übung 2:
http://jsfiddle.net/mjFK6/1/ OOP in Javascript Objektorientierung, was bringt die eigentlich?

Wiederverwendung von Code
Abstraktion, Modularisierung, Kapselung - und dadurch schnelleres Verständnis
Verlässliche Gleichbehandelbarkeit von Objekten (bspw. in Observer Patterns)

Und was wird davon von JS unterstützt? JS unterstützt von Haus aus kein OOP wirkliches OOP. Alles, was wir kriegen, ist die Prototype Eigenschaft. (Wer erinnert sich?)

Aber da ist einiges rauszuholen! Prototype ist ein normales Objekt. Das bedeutet, sowas geht:

function dog() {};
dog.prototype = new animal("Wuff wuff");
dog.prototype.findDrugs = function(){...};

var lassie = new dog();
lassie.beep(); // Magic! Wuff wuff inherited from animal!
lassie.findDrugs(); // Double magic - dog-specific extension! Prototype Inheritance Prototype inheritance ist nicht so komfortabel auf die Dauer. Enter dojo.declare:

dojo.declare("Dog", animal, {
findDrugs: function() {
alert("Found dope! "+this.beep());
}
});

Mit etwas Phantasie liest es sich fast wie eine "normale" Klassendefinition. Dojo to the rescue! Konstruktoren und Methoden:

dojo.declare("Person", null, {
constructor: function(name, age, currentResidence) {
this.name=name;
this.age=age;
this.currentResidence = currentResidence;
},
moveToCity: function(newState) {
this.currentResidence = newState;
}
});

var folk = new Person("Peter Lustig", 62, "Dortmund");
folk.moveToCity("Frankfurt");
alert(folk.currentResidence); Constructors and methods Mehrfachvererbung ist möglich, da es sich beim Erben ja lediglich um einen "Mixin" handelt.

dojo.declare("Mandog", [Person, Dog], {
moveToCity: function(newState) {
// Calls Person::moveToCity unless Wolf implements it...
this.inherited(arguments);
// This will call Dog::beep as inherited.
this.beep();
}
});

Das kann PHP nicht. Zack. Mehrfachvererbung Dojo ist nicht das einzige Inheritance Framework:

John Resigs Simple JS Inheritance
Prototype
UIZE
...

Empfehlung: für dicke Projekte Dojo, für Leichtgewichte John Resig. OOP Frameworks Übung 3:
http://jsfiddle.net/pVEnA/ Load Time Wie lange braucht die Seite, bis der Nutzer interagieren kann?

3 goldene Regeln und alles ist gut:

Pack it
Zip it
Delay it 1. Pack it Requests sind kostbar:
Für kleine Dateien ist der Overhead größer als die Datei selbst.
Browser haben nur eine begrenzte Menge an Slots für gleichzeitige Requests
Die Netzwerklatenz - gerade mobile!

Lösung:
Füge mehrere JS Dateien zu einer zusammen (das stört die Dinger nicht)
Benutze für Icons und Bilder Sprites. Das ist aber ganz schön aufwändig.
Kombiniere die CSS Dateien zu einer. Die Performance von JS lässt sich gut in zwei wesentliche Bereiche aufteilen: 2. Zip It Der Browser muss jetzt nur noch selten abholen. Jetzt müssen wir dafür sorgen, dass er nur wenig abholen muss. Logisch, oder?

GZIP Komprimierung aktivieren (sehr sehr wichtig!)
JS und CSS komprimieren (mehr dazu bei Deployment)
HTML aufräumen 3. Delay it Der Browser holt jetzt selten und wenig vom Server. Sehr gut. Aber es geht noch besser. Enter Perceived Performance:

Der Browser rendert nicht, bevor das letzte externe <script> geladen ist
(jedes von denen könnte die Seite ja umkrempeln).

Idee: schnell normale Webseite liefern, dann erst die Skripte laden.

Lösung:
Nur minimaler Einsatz von <script> Tags, stattdessen die Skripte am Ende per AJAX laden.
Die ganz Harten fügen die wenigen <script> Tags direkt vor dem </body> Tag ein. Pro: schnelles visuelles Feedback für Benutzer.

Con: insgesamt evtl. etwas längere Ladezeit, außerdem sind manche Buttons vielleicht noch ungebunden, bevor das Skript geladen ist. Run Time Das wichtigste zuerst: do as Parreto does! (Parreto war der 80/20 Typ).
Das sagt uns:

Keine Premature Optimization.
(Nicht über a["bar"] vs. a.bar nachdenken, keine Schleifen optimieren.)

Die wirklichen Optimierungspotentiale sind (Prozentangaben subjektiv geschätzt):
a) 90% DOM Selection, Manipulationen & Reflows
b) 5% CSS Usage
c) 3% Event Propagation
d) 2% Closure Diet DOM Selection Trouble! Das DOM zu durchsuchen ist eine teure Angelegenheit, da es groß ist und lange dauern kann. Man sollte dem Browser helfen, es einfacher zu haben.
Daher Queries auf möglichst kleinen Subsets ausführen. In jQuery bedeutet das: möglichst oft den Kontext setzen. Beispiele:

$("*").find(mydiv).find("*.title").html("Performance fail");
$("SPAN.title", mydiv).html("Performance win"); Document fragments and innerHTML Zuerst in Document Fragment arbeiten, dann erst ins große DOM einklinken. Immer viel Code als String übergeben, das ist schneller:

var $x = $("<div class='mycontainer'><span class='title'></span><span class='body'></span></div>");
$x.find("SPAN.title").html(mytitle);
var $body = $x.find("SPAN.body");
for (var i = 0; i < 16000; i++)
$body.append(this.getSuperFreakyItem(i));
$x.appendTo("BODY"); Fear the reflow Die größte Arbeit für einen Browser ist das Layout der Seite - der sogenannte Reflow. Nach Änderungen im DOM muss aber immer ein Großteil der Seite neu gerendert werden. Logisch.

Der Browser versucht standardmäßig, das Rendern zu verschieben, bis das Javascript fertig gerechnet hat.
Diesen Zustand zu erhalten, ist das oberste Ziel.

Dieses Ziel wird gerne verfehlt indem der Browser zum neu rendern gezwungen wird. Reflow Partay! Man zwingt den Browser zum Rendern, indem

man Metriken aus dem DOM abfragt (width() etc.)
Pausen zwischen den DOM Updates hat, in denen der Browser neu zeichnet

Lösung:

Änderung in einem Rutsch
Keine "Pausen" oder asynchrones
Keine Abfragen von DOM Metriken während der Änderung
Allgemeinplatz: DOM flach halten CSS Usage Apropos Allgemeinplatz:

Besonders heiß auf Mobilen Geräten: wenn es eine CSS Transformation gibt, nehme besser die
statt einer Javascript Animation! Event Propagation Random Fun Fact: Events werden hochgereicht bis zum document, wenn es keiner aufhält:

Klick in SPAN > DIV > TABLE > BODY > DOCUMENT.

Das kann man nutzen:

Anstatt ein Event an 5000 Listenelemente zu binden, lieber das Event an den Listencontainer binden und über event.target das Listenelement identifizieren. Das spart auf einen Schlag 5000 Funktionen. Der Erzschurke: Dr. Leaks Hier geht es um Memory Holes, nicht um Assange. Javascript hat einen Garbage Collector.
Dieser ist manchmal nicht in der Lage zu erkennen, dass er etwas löschen muss. Beispiel:

Object A -> Object B -> Object C -> Object A

Oh noes! Alle sind referenziert! Kein Aufräumen! Wenn das passiert, läuft der Speicher voll
und Firefox braucht 700 MB RAM. Übung 4:
http://jsfiddle.net/Xv9wz/3/ Anhang: Deploying Javascript Was heißt Deployment? Deployment heißt:

Scriptgrößen minimieren
Scripte zusammenfassen
"Layers" bauen Layers sind ein Dojo Konzept:

Für verschiedene Anwendungsbereiche jeweils eine JS Datei, die allen Code für diesen Bereich enthält.

Die Layer "überlappen" sich, sind nur darauf optimiert, schnell geladen zu werden. JS Build Tools

Für die URLs möge man googlen:

Dojo Build System Shrinksafe
YUI Compiler
Google Closure Compiler
UglifyJS Vielen Dank für eure Geduld!
Fragen? Language Basics Functions vs. Objects ECMA Script: Array Boolean Date Function Math Number Object RegExp String + window vom Browser SCHOCK: (Function instanceof Object) === true DOPPELSCHOCK: (Object instanceof Function) === true In Javascript ist alles ein Objekt.
Auch Funktionen.

Und Objekte haben einen Konstruktor, der ist eine Funktion.
Der Standardkonstruktor für alles ist Object.

Daher ist Object eine Funktion, aber:

var o = new Object();
(o instanceof Function) === false // HALLELUJA! Das heißt: aus Funktionen, die der Konstruktor sind, erzeugen wir Objekte. Diese Objekte sind dann
keine Funktion mehr, sondern "nur" ein Objekt. Sie erben von der Konstruktorfunktion ihre Eigenschaften. Aber stopp: was heißt eigentlich "erben"? Der "prototype" prototype ist eine Eigenschaft jeder Function. prototype ist ein Objekt.
Er enthält eine beliebige Menge an Eigenschaften, die verwendet werden,
wenn man mit der Funktion als Konstruktor ein neues Objekt instanziiert.

Der Prototype wird als Basis für das neue Objekt genommen, das durch den Konstruktor dann noch manipuliert werden kann. Ein Beispiel: // Eine leere Funktion
function myfunction (){}

// Ihr Prototype
myfunction.prototype.grillen = function() {
alert("Brutzel!");
};

// Instanziieren und grillen!
var a = new myfunction();
a.grillen(); // Brutzel! Verwirrt? Alle Objektdefinitionen in Javascript sind eine Funktion. Diese Funktion wird zum Instanziieren neuer
Objekte als Konstruktor aufgerufen. Die restlichen Eigenschaften des Objektes kommen aus dessen Prototyp.

Ein schönes Beispiel:

function animal (sound) {
this.sound = sound;
};
animal.prototype.beep = function() {
alert(this.sound);
};

// animal ist eine ganz normale Funktion und kann daher als solche verwendet werden.
animal("Wolpertinger"); // Macht halt nix.

// Aber wir können sie auch instanziieren! BAM!
var lassie = new animal("Wruff");
lassie.beep(); // "Wruff!" Nochmal zum Mitschreiben: Next: für den visuellen Lerntyp... Funktionen sind wie Superman. Nicht unbedingt im Sinne des Kleidungsstils. Hoffentlich nicht. Aber:

Am Tage werden sie aufgerufen wie alle anderen Funktionen auch, doch werden Sie mit "new" konfrontiert, werfen Sie die Brille ab und werden zu echten Objektkonstruktoren, die Instanzen mit ihrem Prototyp erzeugen. Praktische Anwendung: Prototype Inheritance.

function dog() {};
dog.prototype = new animal("Wuff wuff");

var lassie = new dog();
lassie.beep(); // Magic! Wuff wuff! Lambda Functions Funktionen sind Objekte, genauso wie bspw. Zahlen. Und deshalb können wir überall Funktionen erzeugen, egal, wo.

Auch in anderen Funktionen oder Schleifen.

Und weil solche Schleifenfunktionen nicht jedesmal einen tollen Namen haben müssen, gibt es auch anonyme Funktionen:

var mytemp = function () {...}; aka Closures Wofür? Das benutzen wir bspw. permanent, wenn wir jQuery Eventhandler bauen:

$("FOO").click(function() {
alert("HI!");
});

Preisfrage: was kann man damit anfangen? Präziser: auf was hat eine solche Funktion Zugriff? Enter the scope! Der Variablenscope Was ist ein Scope? Wie in den meisten Programmiersprachen gibt es globale und lokale Variablen in Javascript.

Global: von überall immer erreichbar
Lokal: nur in der Funktion, in der die Variable definiert wurde, erreichbar.

Jede Funktion hat ihren eigenen Scope, bestehend aus den globalen Variablen, den lokalen Variablen und Argumenten an die Funktion. Its all about the var! "var". Wirklich nur "var". Das ist der ganze Unterschied zwischen lokalen und globalen Variablen.

Lokale Variablen werden mit "var" definiert. Lässt man das "var" weg, ist die Variable global.

Das ist wichtig, deshalb: LÄSST MAN var WEG, IST DIE VARIABLE GLOBAL! Nächste Frage:
was hat das mit Closures zu tun? Closure time! Funktionen, die in Funktionen definiert wurden, erben VOLLSTÄNDIG den Function Scope der Elterfunktion.

Und zwar nicht nur eine Kopie - sie leben direkt in diesem Scope, können dessen Variablen verändern und erhalten auch neue Variablen, die im äußeren Scope entstehen. Closures behalten den Scope - auch wenn sie außerhalb der Originalfunktion aufgerufen werden. this Was denn, ein ganzer Frame nur für this? "this" ist ein Schlüsselwort. Und wie gewohnt sollte es wohl das aktuelle Objekt referenzieren, oder? Und zum Scope gehören?

Schön verlässlich immer auf das gleiche zeigen in der gleichen Funktion?

DAS TUT ES ABER NICHT! this ist in Javascript etwas komplett anderes als in den gewohnten Programmiersprachen. this ist unabhängig von der Funktion, in dem es verwendet wird - es ist sozusagen ein impliziter Funktionsparameter. // The class definition
function Person(place) {this.place = place;}

// People can tell their place with this function
Person.protoype.getPlace = function () {
return this.place;
}

// People can visit places returned by generic place functions
// (we want to prepare for the class "company", so no passing
// the person).
Person.prototype.visitPlace = function (placeFunc) {
var plc = placeFunc();
alert("Visiting "+plc);
}

// Create some persons
var alice = new Person("Hamburg");
var bob = new Person("Berlin");

alice.getPlace(); // Hamburg!
bob.visitPlace(alice.getPlace); // Visiting UNDEFINED! WTF? Ein Problembeispiel Use the force! Was auf Anhieb nach einer dummen Idee aussieht, ist andersrum betrachtet ein echtes Feature!

Mit diesem Spezialverhalten von this lassen sich abgedrehte Dinge wie bspw. jQuery's each realisieren.
Aber: man muss bei Events etc. damit rechnen, dass this nicht mehr das ist was es sein soll.

Lösung? Hitching. Der Browser Darf ich vorstellen? Das Problem. Javascript läuft (im Kontext von Webanwendungen) in einem Browser. Und das bringt einige Besonderheiten mit:

Single Threadedness
Cross Browser Compatibility Single Threadedness? Javascript läuft im Browser immer in einem (1) Thread, der aus irgendwelchen Gründen der gleiche ist wie die Browser UI.

Hängt das Script, hängt der Browser! Das ist nicht nuur schlecht - durch nur einen Thread gibt es keine Concurrency Probleme. Cross Browser Compatibility Ja, ja, das alte Problem. Wobei es mit dem IE6 etwas stirbt. Hauptproblem: DOM & Events Unterschiedliche Interpretationen aus Zeiten, in denen es noch keine Standards gab. Das macht Arbeiten im DOM und mit Events schwierig:

Wird das Event jetzt "bubblen" oder nicht?
Zählen Leerzeichen als Textnode im DOM oder nicht? Kein Problem: Javascript an sich Die Umsetzung von Javascript an sich ist immer ähnlich gut. Hat man DOM und Events hinter sich und programmiert was "Echtes", treten die Probleme in den Hintergrund. Häufiges Problem: Fehler des Programmierers Fast alle Probleme, die wir hatten, lagen an uns selbst:

Ungültige Tagverschachtelungen (Verschachtelte Forms, anybody?)
Race Conditions
Implizite Annahmen über den Garbage Kollektor (wenn man im IE eine Funktion löscht, die gerade ausgeführt wird, stirbt er - Firefox führt die Funktion fertig aus) Fazit: Benutze jQuery, programmier ordentlich, teste früh & oft.

Bibliotheken fangen die Probleme mit dem DOM ab, der Rest kann durch häufiges Testen isoliert werden und erhöht die Codequalität. Übung 1:
http://jsfiddle.net/AK4QQ/ Funktion Variablenkontext this (inkl. Parametern) Die Einflussgrößen auf die Funktionsausführung: Wenn eine Funktion wie eine Methode aufgerufen wird, also Wo kommt this her? someobject.someMethod(); dann ist this someobject (wie man es erwarten würde). In allen anderen Fällen wird this zum globalen Objekt - bei uns meistens window. var meth = someobject.someMethod;
meth(); Obwohl es die gleiche Funktion ist, ist this nun window. Erleuchtung mit this! Für die Schnellmerker: die Besonderheiten von this vorhin sind äquivalent zu call/apply aufrufen: obj.myMethod(123) <=> obj.myMethod.call(obj, 123)

meth(123) <=> meth.call(window, 123); TMI: Was genau heißt hier "Basis"? "Basis" bedeutet hier, dass die Instanzen eine Referenz auf den Prototyp der Klasse als Prototypen erhalten.

Das bedeutet: alle Instanzen einer Klasse teilen sich zunächst den gleichen Prototypen. Ändert man den Prototypen in einer der Instanzen, ändert man ihn für alle.

Aaaaaber: es gelten dabei die ganz normalen Regeln für "Objektpointer": man kann die Prototypen einfach durch ein anderes Objekt austauschen und damit die Verbindung trennen.

(Nicht, dass man das normalerweise wollte). TMI: Was genau heißt hier "Basis"? function dog() {}
dog.prototype.foo = "bar";

var d = new dog();
alert(d.foo); // bar
dog.prototype = {foo: "schnitzel"}; // Austauschen durch neues Objekt!
var d2 = new dog();

alert(d.foo); // Immer noch bar!
alert(d2.foo); // Teilt sich den neuen Prototype - Schnitzel! Ein Beispiel
Full transcript