Vor doch schon einiger Zeit bin ich über diesen Artikel http://www.heise.de/developer/artikel/Hinterfragt-Woran-erkennt-man-einen-guten-JavaScript-Entwickler-2652128.html bei Heise gestolpert. Die Fragen die dort verwendet werden, um zu beurteilen, ob jemand ein guter JavaScript-Entwickler ist, halte ich persönlich für zum Teil voll kommen nichts sagend.
Gut man prüft das Wissen über die Interna von JavaScript, aber man will ja erstmal jemanden der mit JavaScript eine Anwendung schreiben kann und nicht jemand der mir eine JavaScript-Engine schreiben. Ich kann aus meiner Erfahrung heraus sagen, so wie ich JavaScript gelernt habe, dass nur eine relevante Frage dabei ist und zwar die nach den Closures. Wenn man das erstmal verstanden hat und kann sind auch die technischen Gegebenheiten hinter den Fragen davor leicht verständlich.
Die letzte Frage ist auch kaum brauchbar. Wenn ich mit einem Canvas arbeite gibt es natürlich ganz andere Probleme und Lösungen, als wenn ich viel Berechne und in Script-Timeouts laufe. Wenn ich schlecht programmiere und unnötig Methoden immer und immer wieder in einer Schleife aufrufe, sagt es auch eher allgemein was über mich als Entwickler aus und weniger über meine Fähigkeiten mit JavaScript.
Also ich würde bei JavaScript mehr auf Closures achten und wenn mir jemand erzählt sowas bräuchte man nicht. Aber mir gleichzeitig von seiner langjährigen Erfahrungen mit JavaScript erzählt und dann aber auch beim Zuweisen einer Function zu einem Object scheitert..
object.onSomething=func();
weil es wird nur einmal bei der Zuweisung die Function aufgerufen, aber onSomething bleibt null.. dann sollte man nochmal gut überlegen, ob die Person geeignet ist. (Das war jetzt ein Beispiel das sich genau so zugetragen hat.. und er hat einige Jahre mit JavaScript gearbeitet... nur eben trotzdem keine Ahnung davon)
Wenn man nicht das tollste Smartphone mit dem schnellsten Internet hat, hat man öfters das Problem, dass man ein Foto macht und es dann gerne irgendwo hin hochladen möchte. Dann dauert es ewig, bricht manchmal dann mittendrin ab und am Ende hat es viel Volumen verbraucht. Nur damit die doch einiger Massen gute Kamera des Smartphones ein belangloses Foto gemacht hat, dass die volle Auflösung nicht gebraucht hätte und am Ende auf Serverseite sowie so noch mal kaputt komprimiert wird. Es wird aber selten die Auflösung noch angepasst als eher die JPEG-Qualität runter gedreht (auf etwas mal man klassisch so bei 70% oder weniger schätzen würde).
Hoch aufgelöste Bilder wo alle Details durch die Kompression kaputt gemacht wurden. Wenn es nicht in der vollen Auflösung angezeigt wird merkt man es weniger (Super-Sampling.. auch gut um Rauschen in Bildern zu entfernen). Man könnte natürlich auch das Foto schon auf dem Client/Smartphone schon so weit verkleinern, dass es nicht unnötig Volumen und Bandbreite verbraucht. Aber auf welche Auflösung sollte man das Bild runter rechnen?
Wenn der Benutzer super Qualität mit vielen Details und guten Farben am Ende erwartet.. also im Grunde genau das was er hochgeladen hat und mit etwas Glück aus einer modernen DSLR stammt.. ja.. dann ist 6MP schon Minimum. Mit minimalen Vergrößern sollte es sogar auf 4K noch hinnehmbar aussehen. Sonst muss es leider schon 10MP sein.
Aber im normal Fall wird man eine Website/Anwendung haben, die eine Reihe kleinerer Bilder anzeigt und dann bei einem Klick darauf das Bild vergrößert anzeigt. Entweder auf Vollbild, wobei oft Ränder noch da sind, sowie Bereich für Titel, Beschreibung und Datum. In vielen Fällen auch noch Platz für Kommentare.
Kaum jemand hat eine Auflösung über 1920x1200 oder 1920x1080. Also die Breite auf max 1920 oder die Höhe auf max 1080 zu skalieren wird für normale Anwendungen, die nur zum Betrachten da sind und nicht um die Bilder nochmal runter zu laden und zu bearbeiten, vollkommen reichen.
Seiten die viele Bilder halten im Zusammenhang mit Texten (Foren und Imageboards) brauchen noch sehr viel geringere Auflösungen. Selbst Seiten wie 9gag haben bis auf wenige Ausnahmen Bilder in hohen Auflösungen. Wir reden hier nur von Bildern und nicht von GIFs.. das wäre nochmal ein Thema für sich.
Außer bei diesen sehr langgezogenen Bildern wäre eine Breite über 1000 Pixeln kaum nötig. Also 1MP reicht für die meisten Zwecke. Die Vorschaubilder sind noch bedeutend geringer aufgelöst. Und auch bei Facebook reichen Bilder mit 1000px in der Breite eigentlich immer aus, wenn man nicht detaillierte Landschaften zeigen möchte.
1000x1000 Pixel sind schon mal eine ganz andere Größe. Wir gelangen da von 2MB auf gut einige 100KB. Das macht auch beim Upload extreme Unterschiede.
Ideal wäre eine Check-Box, die man setzen kann, wenn es ein Bild mit vielen Details ist. Das Problem wären normale Benutzer, die Abends in der Cocktailbar ihr Essen im Kerzenschein fotografieren müssen und keine ruhige Hand haben und nun glauben, es wäre ein hoch detailreiches fotografisches Kunst entstanden. Wenn man genug solche Benutzer hat, würde die Checkbox immer angeklickt werden, weil kein Wissen darüber besteht, wann was von Vorteil ist und dann lieber die "bessre" Qualität gewählt wird.
Am Ende bleibt die Frage, wie kann ich Bilder vor dem Upload verkleinern? Früher wurde das doch immer auf dem Server erledigt.
Mit dem Canvas-Element auf HTML5 geht es extrem einfach. Man braucht nur das Bild am img-Element vorliegen.
Beispiel (wie man an $scope sieht ist es AngularJS-Code). $scope.longestSide gibt die max Breite hier an.
var canvas=document.createElement("canvas");
var factor=1;
if($scope.newPostPreviewImage.width>$scope.longestSide){
factor=($scope.newPostPreviewImage.width/$scope.longestSide);
}
canvas.width=$scope.newPostPreviewImage.width/factor;
canvas.height=$scope.newPostPreviewImage.height/factor;
var ctx=canvas.getContext("2d");
ctx.drawImage($scope.newPostPreviewImage,0,0,canvas.width,canvas.height);
blob=dataURItoBlob(canvas.toDataURL("image/jpeg",0.7));
Wie man hier sieht ist eines der großen Geheimnise, wie man die DataURL vom Canvas wieder in
ein Binär-Object zurück wandelt, so dass es wieder wie ein normal File-Upload gehandhabt werden kann.
Den Code haben ich nach langer Suche im Internet gefunden und er funktioniert!
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var arrayBuffer = new ArrayBuffer(byteString.length);
var _ia = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteString.length; i++) {
_ia = byteString.charCodeAt(i);
}
var dataView = new DataView(arrayBuffer);
var blob = new Blob([dataView], { type: mimeString });
return blob;
}
Damit haben wir dann alles um es hochladen zu können. Wie man etwas hochlädt erkläre ich später vielleicht nochmal, aber da gibt es sonst genug Erklärungen auf anderen Seiten, die einfach und gut verständlich sind.
Aber hier kommt nochmal auf die schnelle der Code um ein Image aus einem Input des Types "file" heraus zu bekommen. Die Methode wird über das onchange-Event des Input aufgerufen.
$scope.openFile=function(event){
var files=event.target.files;
if(files.length>0 && (files[0].type.match('image.*') || files[0].type.match('image/*'))){
console.log("load file: "+files[0].name);
$scope.newPostFile=files[0];
$scope.newPostPreviewImage=document.createElement("img");
var URL = window.URL || window.webkitURL;
var imgURL = URL.createObjectURL($scope.newPostFile);
$scope.newPostPreviewImage.src=imgURL;
URL.revokeObjectURL(imgURL);
var reader = new FileReader();
reader.onload=function(e){
$scope.newPostPreviewURL=e.target.result;
console.log("add preview image");
try{
$scope.$apply();
}
catch(e){
}
};
reader.readAsDataURL($scope.newPostFile);
}
};
Hier wird die Datei in eine Object-URL umgewandelt und einmal ein img-Element erzeugt und diese Object-URL als src gesetzt. Zusätzlich wird nochmal eine DataURL von der Bild-Datei erzeugt, um ein kleines Vorschau-Bild anzeigen zu können. Die DataURL wird, wenn sie fertig
geladen ist, bei einem bestimmten img-Element als src gesetzt. Da wird über CSS skaliert. Alle Verkleinerungsoperationen werden aber auf dem internen separat gehaltenen img-Element ausgeführt.
<img ng-src=""/>
Eine der Stärken bei Java sind die Threads und die ExecutorServices, die die Workloads auf einen festen Pool von Threads verteilen und dann die Ergebnisse Sammeln (Future<...>). Man kann natürlich auch alles in einer großen Schleife erledigen. Bei JavaScript hat man aber das Problem, dass nicht stoppende Scripte sehr schnell gestoppt werden. Das mit Timeouts zu lösen scheitert ganz schnell wenn einzelne Workloads zu lange dauern. Aus diesem Grund wurden die WebWorker entwickelt, die es erlauben nebenläufige Vorgänge in JavaScript zu realisieren und somit auch diese Probleme mit Timeouts von Scripts zu umgehen.
WebWorker und das Hauptscript kommunizieren dabei über Nachrichten, die hin und her geschickt werden. WebWorker können dabei entweder die ganze Zeit existieren oder man kann diese auch direkt nach dem erledigen der Aufgabe wieder beenden. In den meisten Fällen ist
dieses Verhalten wohl das Beste.
Man kann z.B. auch in einem Spiel AI-Gegner mit WebWorkern realisieren. Dann muss der WebWorker natürlich nach dem Starten so lange existieren bis er von außen die Nachricht erhält sich zu beenden.
Ein echt guter Einstieg in die Welt der WebWorker ist diese Seite hier http://www.webworkercontest.net
Man schreibt einen WebWorker, der gegen einen zweiten antritt und versucht möglichst viel Fläche des Spielfeldes mit seiner Farbe zu markieren. Die Kommunikation ist sehr einfach. Der WebWorker gibt über postMessage() in einen JavaScript-Object eine Direction an das Hauptscript zurück und bekommt im nächsten Schritt das Ergebnis ob der letzte Schritt funktioniert hat. Anhang dieses Ergebnisses muss der WebWorker seinen nächsten Schritt planen. Der eigentliche WebWorker ist in der onmessage-Function gekapselt.
Also schickt das Hauptscript eine Message, onmessage des Workers reagiert und darin wird mit postMessage ein Ergebnis zurück geschickt.
Das ist die Struktur nach der WebWorker funktionieren.
Die Kommunikation hat natürlich Grenzen. Ein Element aus dem DOM an einen WebWorker zu über geben und dann dort zu ändern funktioniert (wie man wohl schon erwartet hat) nicht. Wenn man mit einem Canvas etwas machen will muss man die getImageData() Methode bemühen.
Wenn man mit einem WebWorker arbeitet hat man oft den Wunsch auch hier mit Scripten aus externen JS-Files zu arbeiten. Die klassische Variante mit den <script>-Tags funktioniert hier natürlich nicht. Dafür gibt es die importScripts-Function. Der Pfad ist relativ zur Datei des
WebWorkers anzugeben.
Beispiel:
importScripts('../lib/libwebp-0.1.3.demin.js');
Anstelle von onmessage direkt im Script kann man die Haupt-Function auch natürlich über addEventListener setzen, was sehr viel sauberer
aussieht.
Beispiel:
self.addEventListener('message', function(event) {...},false);
Es sollte sowie so beim WebWorker immer "self" verwendet werden.
Die an den WebWorker übermittelten Daten erhält man ganz klassisch über das Event.
var data=event.data;
Was in den Daten drin steht hat man ja selbst bestimmt.
Am Ende der Haupt-Function wird dann das Ergebnis zurück geschickt und wenn gewünscht der WebWorker von sich heraus auch beendet. Der WebWorker kann auch von außen beendet werden, aber es ist wohl sicherer, wenn er sich selbst schließt.
Beispiel:
self.postMessage({index:i,image:result});
self.close();
So. Nachdem wir nun wissen wie der WebWorder intern funktioniert, bleibt am Ende eigentlich nur noch die Frage, wie man nun so einen WebWorker aus dem Haupt-Script heraus startet und wie man die Ergebnisse entgegen nehmen kann. Das ist aber an sich nicht wirklich kompliziert und da alles ja auch Event-Listener und Events setzt, ist nicht schwer zu erraten wie zurück geschickt Messages verarbeitet werden können.
function func(controller){
return function(event){
controller.doSomeThing(event.data);
};
};
var worker = new Worker('./controllers/webpWorker.js');
worker.addEventListener('message', func(this), false);
worker.postMessage(post);
Das Closure der Funktion ist noch das komplexeste hier dran. Der Code hier wird in einer Methode des Controllers ausgeführt und um die Verarbeitung des Ergebnisses in einer anderen Methode des Controllers durch zu führen muss eben der Controller der Function mit einem Binding an das WebWorker-Object im Hauptscript bekannt sein. Closures sind sehr wichtig und ohne diese JavaScript zu schreiben ist extrem umständlich und depremierent. Also wenn das Konzept noch nicht kennt, sich das als erstes erst einmal ansehen!
Die in "post" übergeben Daten findet man im Event im WebWorker unter event.data wieder.
Das hier war jetzt doch relativ kurz gehalten, aber zeigt hoffentlich die Hauptstrukturen sehr gut und reicht für erste Experimente.