OOP in JavaScript. Oder: Wenn ich nochmal "this is undefined" lese, schreie ich!

Mal ehrlich, das mit der „Objektorientierung“ und vor allem das mit this haben sie in JavaScript schon richtig verbockt, oder nicht?

Man kann natürlich etliches dazu lesen. Und eins der Probleme IST vielleicht gerade, dass jedes Script-Kiddie, das es geschafft hat, auf seiner HTML-Seite mit JavaScript einen MouseOver-Effekt über einen Button zu legen, meint, seine wertlosen „Schaut mal, wie ich das mache“-Blog-Posts in die Welt drücken zu müssen. (Also, GERADE die. Welcher FORTRAN-Programmierer hat schon einen Blog?). Man kann natürlich versuchen, sich an dem zu orientieren, was der Autor von „The Good Parts“ geschrieben hat (das Buch, das ja (im Gegensatz zu „The Definitive Guide“) The Definitive Guide zu sein scheint)).

Das ist auch schon fast 10 Jahre alt, und wohl nicht mehr ganz aktuell, aber zumindest bestimmte Teile scheinen ja zumindest die grobe Richtung vorzugeben.

Langer rant, kurzer Sinn: Im Moment strukturiere ich „eine Klasse“ etwa so:


var meinNamespace = meinNamespace || {};

(function(meinNamespace) {
    'use strict';

    // "Konstruktor"
    var MeineKlasse = function (meinParameter) {
        this.meinField= meinParameter;
        this.einArray = [];
    };

    var einePrivateMethode = function(einParameter) {
        var that = this;
        einParameter.forEach(function(element) {
            eineAnderePrivateMethode.call(that, element, that.meinField); 
        });        

    };

    var eineAnderePrivateMethode = function(einParameter, einAndererParameter) {
        var that = this;
        that.irgendeinField = einParameter;
        console.printf("%4.2f", einAndererParameter); // ;-) 
    };


    MeineKlasse.prototype.einePublicMethode = function()
    {
        var that = this;
        that.einArray.forEach(function(element) {
            einePrivateMethode.call(that, element); 
        });        
    };
    
    
    meinNamespace.MeineKlasse = MeineKlasse;
    
})(meinNamespace);

Was mich daran (abgesehen von vielen Kleinigkeiten) am meisten irritiert oder nervt:

  1. Schon fast mechanisch fange ich JEDE Funktion mit
    var that = this;
    an, und verwende IMMER that wo man in normalen Sprachen ( :smiley: ) eigentlich this verwenden würde. Ist das normal?

  2. Die Aufrufe sind deswegen ja ein Krampf. Schon fast mechanisch schreibe ich Aufrufe IMMER in der Form
    irgendeineFunktion.call(that, ...)
    Ist das normal?

Was ich nicht verstehe - wieso quälst Du Dich selber ?!

TL;DR : Mal etwas provozierend gefragt: Wer braucht schon OOP? :wink: [emoji205]

Das “that” braucht man nur, wenn du einen neuen Scope einführst. Es gibt keine “Lösung” dafür, weil es so gedacht ist. Es gibt dennoch ein paar Wege, das “In Java ist es schöner”-Problem umzugehen.

Eine elegante Möglichkeit bieten die ES6 Arrow Functions. Bei einer arrow function wird this nicht an diese gebunden. ES6 kannst du in Node.js nativ nutzen, für den Browser kannst du Babel verwenden, um Abwärtskompatibilität zu älteren Browsern zu gewährleisten.

Typescript bietet auch einige Lösungsvorschläge an.

Ich habe deinen Code ein bischen verbessert, sodass du ohne .call oder var that = this; auskommmen solltest. Wenn nicht, kannst du noch auf die Variable self zugreifen. Ich weiß jedenfalls noch nicht ob das funktioniert, aber du kannst es ja überprüfen.
[JAVASCRIPT]var meinNamespace = meinNamespace || {};

(function(meinNamespace) {
‘use strict’;

function MeineKlasse (meinParameter) {
    var self = this; /* Würde ich wohl eher benutzen als var that = this; Mit dieser Variable ersparst du dir die Neudefinition bei jeden Methodenanfang */
    this.meinField = meinParameter;
    this.einArray = [];

    function einePrivateMethode(einParameter) {
        einParameter.forEach(function(element) {
            eineAnderePrivateMethode(element, this.meinField); 
        }, this);
    }

    function eineAnderePrivateMethode (einParameter, einAndererParameter) {
        this.irgendeinField = einParameter;
        console.printf("%4.2f", einAndererParameter);
    }

    MeineKlasse.prototype.einePublicMethode = function()
    {
        einArray.forEach(function(element) {
            einePrivateMethode(element);
        }, this);        
    }

}

meinNamespace.MeineKlasse = MeineKlasse;

})(meinNamespace);
[/JAVASCRIPT]

*** Edit ***

Korrektur des vorherigen Codes:
[JAVASCRIPT](function(meinNamespace) {
‘use strict’;

function MeineKlasse (meinParameter) {
    var self = this;
    this.meinField = meinParameter;
    this.einArray = [];

    function einePrivateMethode(einParameter) {
        einParameter.forEach(function(element) {
            eineAnderePrivateMethode(element, this.meinField); 
        }, self);
    }

    function eineAnderePrivateMethode (einParameter, einAndererParameter) {
        self.irgendeinField = einParameter;
        console.printf("%4.2f", einAndererParameter);
    }

    MeineKlasse.prototype.einePublicMethode = function()
    {
        einArray.forEach(function(element) {
            einePrivateMethode(element);
        });        
    }

}

meinNamespace.MeineKlasse = MeineKlasse;

})(meinNamespace);
[/JAVASCRIPT]
Man kann nur auf private Felder mit privaten Funktionen zugreifen, daher braucht man eigentlich self, wenn man auf öffentliche Felder zugreifen will. Beispiel entsprechend angepasst.

[QUOTE=darekkay]Das „that“ braucht man nur, wenn du einen neuen Scope einführst. Es gibt keine „Lösung“ dafür, weil es so gedacht ist. Es gibt dennoch ein paar Wege, das „In Java ist es schöner“-Problem umzugehen.

Eine elegante Möglichkeit bieten die ES6 Arrow Functions. Bei einer arrow function wird this nicht an diese gebunden. ES6 kannst du in Node.js nativ nutzen, für den Browser kannst du Babel verwenden, um Abwärtskompatibilität zu älteren Browsern zu gewährleisten.

Typescript bietet auch einige Lösungsvorschläge an.[/QUOTE]

Die Arrow-Functions hatte ich schonmal gesehen. Allerdings finde ich, dass der syntaktische Zucker, der mit
[javascript]
var x = a.map( s => s.length ); vs.
var y = a.map(function(s){ return s.length });
[/Javascript]
verbunden ist, vernachlässigbar ist (zumindest im Vergleich zu dem, was man in Java mit Lambdas vs. anonymous classes spart), und zweitens wird damit ja NOCH ein Fass aufgemacht, und das komplizierte, fragile soping (um das ich meinen Kopf ja eh noch nicht drumgewickelt habe) noch ein Stück komplizierter. Einen direkten Zusammenhang zum Bread+Butter-Fall von "Public und Private Methods, wo das this mühsam durch foo.call(that..) rumgereicht werden muss, sehe ich spontan auch nicht.

Node.js und Babel - ja, … ersteres ist wohl aus mehreren Gründen einen Blick wert, aber ansonsten sind es für mich jetzt erstmal irgendwelche Libraries, die die Sprache potentiell wie eine komplett andere aussehen lassen. Sorry, aber mir reicht es schon, wenn bei $_jeder->$(Websuche)_mit.Promises.$diese_obskure_jQuery!Syntax(auftaucht). :sick:

Dass Typescript für sowas „besser“ ist, habe ich schon mitgekriegt, aber … es sollte schon reines JavaScript sein…

Wenn ich das richtig sehe (mein Parser ist da noch etwas langsam) dann ist der Hauptunterschied der, dass das alles „IM“ Konstruktor steht. Ich meine, das auch schonmal gehabt zu haben, und da auch über irgendeinen Maulwurfshügel gestolpert zu sein, aber werde das nochmal prüfen.

(Was es mit dem zweiten Parameter bei „forEach“ auf sich hat, hat sich mir noch nicht erschlossen (und davon steht auch nichts in Array.prototype.forEach() - JavaScript | MDN …). (Trotzdem: Eine weitere Sache, die man ausprobieren kann, wenn irgendwas nicht funktioniert :wink: )).

[QUOTE=mdickie;138733]
Man kann nur auf private Felder mit privaten Funktionen zugreifen, daher braucht man eigentlich self, wenn man auf öffentliche Felder zugreifen will. Beispiel entsprechend angepasst.[/QUOTE]

Dieses „self“ oder „that“ wäre noch akzeptabel, aber gerade die „.call(that…“-Sache ist halt extrem häßlich. Ich werd’ mal schauen, ob es hilft, einfach ALLES in den Konstruktor zu ziehen, und poste dann später (vermutlich) was der Maulwurfshügel war, wegen dem ~das doch irgendwie nicht geht…

Marco es wird Zeit dass du deinen eigenen Web-Standard entwickelst.

1 „Gefällt mir“

Babel ist ein Compiler, der deinen fertigen JS-Code in abwärtskompatiblen Code übersetzt. Du musst deinen Code also gar nicht anpassen, um Babel irgendwo darin zu verwenden. Es ist bloß eine zusätzliche Zeile in deinem Build-Skript.

Nun, es gibt ja Alternativen - TypeScript oder Dart (letzteres sieht ja zumindest ganz vernünftig aus). Aber aus einer Mischung von Geekigen Gründen (grob: “das hinschreiben, was der Browser dann wirklich ausführt, und nicht irgendwas absrahierendes”), und politischen Gründen (WebGL programmiert man eben mit JavaScript. Das haben unsere Großväter ja schon so gemacht) bleibe ich erstmal bei möglichst “reinem JavaScript”.
Den ganzen Kram direkt in den Konstruktor zu ziehen, scheint zu gehen - bei “statischen” Methoden braucht man dann halt doch wieder andere Konstrukte, aber wirklich einheitlich-stimmig wird das wohl primzipbedingt kaum werden können…

Also, nach dem ersten Test in IE und einem “Promise is undefined” scheint hier gerade ein bodenloses Fass aufzugehen. Ohja, das ist zwar unterstützt, aber man braucht trotzdem irgendeine obskure “es6-promise.js”, die man einbinden muss - und auf IE mit polyfill und dafür braucht man require.js und dann kann man die Scripte importen (die vorher einfach im HTML standen), aber die werden asynchron geladen, also, quasi mit Promises. Moment. Wie hat der Absatz hier nochmal angefangen?
Also, nicht nur “this”, sondern die gesamte umgebende Infrastruktur haben die schon GRÜNDLICHST verbockt. kopfschüttel

Zugegebenermaßen bin ich mit den Modulsystemen von JavaScript (CommonJS, AMD) frustriert, wo ich immer noch nicht gecheckt habe, wie man ein Modul erstellt. Beide scheinen irgendwie im Browser (CommonJS mit browserify, AMD einfach so) zu gehen, aber AMD scheint optimaler für Webanwendung (asynchronicity). Trotzdem sind die meisten Module über CommonJS verfügbar. ES6 Modulsystem scheint irgendwie die Lösung zu sein, trotzdem scheint es sich nicht wirklich irgendwo durchgesetzt zu haben. Ich neige bis jetzt noch meine Namespace ohne intelligenten Modulsystem zu deklarieren

Ja, ich habe jetzt hier zwar ein paar
[javascript]
define([
‘Utils’
], function(
Utils
) {
… // Eigentlicher code
});
[/javascript]
stehen, aber trotzdem noch an anderen Stellen sowas wie
[javascript]
var resolveUri = gltfTutorial.IoUtils.resolveUri;
[/javascript]
um einen “static import” zu emulieren - was wohl eigentlich mit irgendwelchen “export modules” erreicht werden sollte. Ich find’ das gelinde gesagt ziemlich ätzend: Was ist “require.js”? Irgendeine Library, die irgendjemand gebastelt hat, und ohne die JavaScript für ernsthafte Programmierung praktisch nicht nutzbar ist! :suspect:

Und das alles nur, weil der IE11 keine “Promise” kennt. Aber inzwischen habe ich gemerkt, dass es auch dann nicht funktioniert, wenn man die bluebird-Promises einbindet, weil der $"§&/"§!-IE keine lokalen JSON-Dateien per XMLHttpRequest laden kann.

[ot] "Mein Auto fährt nicht, weil es keine Räder hat!" - "Ahja, da musst du den Motor ersetzen, durch den Motor von einem Akkuschrauber, den du vom Schrottplatz holen kannst" - "Aber, dann hat es immernoch keine Räder!?" - "Naja, wenn du VIER Akkuschraubermotoren hast, kannst du an jeden ein altes Kinderwagen-Rad dranschweißen, und dann hat das Auto 4 Räder, die sich drehen" - "Und ist das dann alles stabil genug?" - "Wenn man die Karosserie abnimmt, und keiner im Auto sitzt, geht es" - "Und bewegt sich das Auto damit auch?" - "Naja, du musst natürlich noch anschieben, aber dann geht's - solange es nicht bergauf geht. Wenn's bergauf geht, musst du ein Pferd vor's Auto spannen. Dann überhitzen sich aber die Akkuschrauber-Motoren. Bergauf sollte man dann also eher ein anderes Auto nehmen." ... :rolleyes: [/ot]

Ich will eine Datei lesen. Einfach nur eine Datei lesen. (Hat jetzt nichts mehr mit OOP zu tun, aber … es wäre halt schon toll, wenn man sich mit OOP beschäftigen könnte, und nicht seine Nachmittage damit verbringen müßte, festzustellen, dass (OOP nicht praktikabel und) es praktisch unmöglich ist, eine Datei zu lesen…)

Eh JSON oder? Dann wäre es überlegenswert JSONP zu verwenden, wenn es nicht anders geht.

*** Edit ***

Noch eine Erinnerung: Es ist sehr wohl möglich statische Methoden für eine Klasse zu erstellen:
[JAVASCRIPT]
function EineKlasse() {
}
EineKlasse.addiere = function(a, b) {
return a+b;
}
[/JAVASCRIPT]

*** Edit ***

Die Herangehensweise von JSONP sieht so aus. Du ladest eine JavaScript-Datei über das hinzufügen tags und diese JavaScript-Datei meldet sich über eine eine Funktion mit dem eigentlichen Inhalt der Datei (praktischerweise zur Identifizierung auch mit der URL, wenn man mehrere Dateien lädt).
[javascript]meldeMich(‘http://example.com/absolute-url/’, ‘Eigentlicher Inhalt’);
[/javascript]
oder
[javascript]meldeMich(‘http://example.com/absolute-url/’, {“whatever”: “Hello World!”});
[/javascript]

Scheint zumindest einfacher zu sein als mit XMLHttpRequest.

Einfach webpack mit babel-js verwenden, entry point basteln, fertig :troll:

Es ging darum, dass man/ich, wenn man diese Funktion von woanders verwenden will nicht jedes mal
var foo = namespace.Klasse.funktion();
schreiben will, sondern es auch mit
var foo = funktion();
gehen soll (oder alternativ
var foo = Klasse.funktion();
was ja auch geht - man kann ja alles allem zuweisen. Wir sind schon ein paar Hippies, ne? :D)

[QUOTE=mdickie;138820]Eh JSON oder? Dann wäre es überlegenswert JSONP zu verwenden, wenn es nicht anders geht.

Scheint zumindest einfacher zu sein als mit XMLHttpRequest.[/QUOTE]

JSONP hatte ich auch schonmal irgendwo gelesen. Aber wie es das Trollen wohl auch andeuten sollte: „Eine Datei Lesen? Kein Problem: require.js und es6.js und jquery.js per node.js installieren, dann noch mit Webstorm und Grunt auf einem Apache Deployen und schon hast du einen Cloudbasierten Webservice. Äh, Moment, was war die Frage? Ach, Detei lesen. Nee, das geht halt nicht“. Also, dass es praktisch unmöglich zu sein scheint, mit JavaScript eine JavaScript-ON-Datei zu lesen ist gelinde gesagt absurd. Aber auf Firefox gehen Promises und XMLHttpRequest, und wer IE verwendet ist selbst schuld :rolleyes:

Verstehe dass mit dem Lesen von Dateien nicht.

Reading local files in JavaScript - HTML5 Rocks

Braucht dann eben die Benutzerinteraktion Datei auswählen oder DnD etc. Sonst könnte ja jede popelige Website alle deine Dateien auslesen.

Ansonsten NodeJS

var fs = require(„fs“);

fs.readFileSync(„FILE_NAME“).toString().split(’
').forEach(function(line) {
if (line !== „“) {
console.log(line);
}
});

Auch diesen Link hatte ich schon gelesen :wink:

Es will mir einfach nicht in den Kopf:


project\
    index.html
    scripts\
        script.js
    data\
        file.json

Und irgendwo in „script.js“ soll jetzt stehen
[javascript]
var jsonPromise = readFile("…\data\file.json");
[/javascript]
oder, wegen dieser (einerseits durch den Sandbox- und Sicherheitsgedanken gerechtfertigten, aber in ihrer konkreten Ausprägung absurden Beschränkung der Zugriffsrechte auf „nur untegeordnete Ordner, nicht Cross-Site“ etc, meinetwegen auch
[javascript]
var jsonPromise = readFile(".\data\file.json");
[/javascript]
)
Und es erscheint praktisch unmöglich, so eine „readFile“-Funktion zu schreiben. :verzweifel:

Aber jetzt habe ich, entgegen meiner ursprünglichen Bestrebung, das ganze mit „reinem JavaScript“ (ohne einen aufgeblasenen Behemoth von dependencies) zu machen, ohnehin schon require.js drin. (Eine dependency, die diem Zweck dient, dependencies haben zu können. Klingt das sinnvoll?). Von daher kann ich auch noch irgendwo „fs“ dazuschreiben (und bei der Gelegenheit mal schauen, wie DIE das „readFile“ implementiert haben… :rolleyes: )

EDIT: Der Kontext, um den es geht, ist das, was ich gerade mal auf http://javagl.de/gltfTutorialWorkspace-2016-09-06.zip geworfen habe. Zumindest auf dem Firefox rendert er immerhin schon die „Duck“. Falls es sich jemand ansieht, wäre es gut, wenn die Schreie der Verzweiflung und das Kopfschütteln hier in Form von Verbesserungsvorschlägen kanalisiert werden würden. (Bei vielen Dingen WEISS ich schon, dass sie Sche!ße sind, aber bei vielen auch nicht, und bei denen, bei denen ich das weiß, weiß ich nicht notwendigerweise, wie die „beste“ Lösung dafür aussieht)

Wenn du tatsächlich IE verwenden willst, könntest du ja ActiveX verwenden: javascript - read file using ActiveX separate by ; and put in select - option - Stack Overflow

Ich hatte natürlich schon danach gesucht. Man findet etliches, sowas wie ajax - Read file:// URLs in IE XMLHttpRequest - Stack Overflow , und irgendwann hatte ich dann auch mal mit sowas um
var request = new ActiveXObject(„Microsoft.XMLHTTP“);
herum rumprobiert. Aber

  1. Das, was man im Web findet, funktioniert so direkt meistens nicht
  2. Es scheint keine allgemeingültige Lösung zu geben
  3. IE 6,7,8,9,10 oder 11, CORS, Sicherheitseinstellungen, Cross-Domain-Access, externe libraries, WTF?
  4. Es ist ein absoluter Krampf. Ich will nur eine verd… JSON-Datei lesen.
  5. Alles egal. Im FF geht’s und wer verwendet schon IE? :wink:

Du könntest sowieso WebGL nur mit IE11 und höher verwenden. Daher würde ich IE11, wenn überhaupt in deinem Fall für ein Target erachten (über HTTP dürfte ja XMLHTTPRequest ja funktionieren). Du könntest dich aber auch auf stur stellen und nur GC und FF unterstützen.