dedlfix: OOP-Propertys mit Accessor

Beitrag lesen

Tach!

Meine Anliegen ist, zu lernen wie man am besten sowas wie Klassen mit Property-Accessor-Funktionalität mit Javascript aufbaut.

Zunächst eine Hilfsfunktion, die die Eigenschaftswerte des Objekts aus einem übergebenen Objekt nimmt und zuweist. Sie ist nicht Teil des Problems, ich möchte sie nicht weiter erläutern, sie darf aber gern kommentiert werden. (Mir gefällt nicht sonderlich, dass sie global rumliegt. Anderseits, sie an Object.prototype anzuhängen, macht es nicht viel besser.)

/**  
 * @param {object} obj an object to set its values  
 * @param {object} data an object containing the values  
 * @param {string[]} names an array containing allowed names  
 */  
function setData(obj, data, names) {  
	if (data)  
		for (var i = 0; i < names.length; i++) {  
			if (data.hasOwnProperty(names[i]))  
				obj[names[i]] = data[names[i]];  
		}  
}  

Foo ist mein erster Ansatz, nachdem mir Object.defineProperties über den Weg gelaufen ist. Stellvertretend wird hier eine Eigenschaft Name hinzugefügt, die sehr einfach als Getter implementiert ist. In der geplanten Anwendung kommen noch mehr Eigenschaften inklusive Setter hinzu, für die ich zumindest wissen muss, wann sie (via Binding mit AngularJS) beschrieben werden, um daraufhin eine Persistierung im LocalStorage vornehmen zu können.

function Foo(data) {  
	var self = {  
		name: ''  
	};  
  
	setData(self, data, ['name']);  
  
	Object.defineProperties(this, {  
		Name: {  
			enumerable: true,  
			get: function () {  
				return self.name;  
			}  
		}  
	});  
}  
  
var foo1 = new Foo({name: 'Foo1'});  
var foo2 = new Foo({name: 'Foo2'});  
console.log(foo1);  
console.log(foo2);  

Ausgabe:
Foo { Name="Foo1"}
Foo { Name="Foo2"}

Die Variable self ist das, was (zumindest in C#) ein Backing Field ist, die private Eigenschaft, die die Werte aufnimmt, die vom Getter/Setter gelesen/geschrieben wird. setData() initialisiert die Werte aus dem übergebenen Objekt und dann wird die Property hinzugefügt.

Soweit so lala, die Testausgabe zeigt, dass Name eine öffentliche Eigenschaft ist und die richtigen Werte enthält. Unschön ist, dass die Property in jede Instanz (this) neu eingefügt wird.

Nun sah ich, dass man heutzutage gar nicht mehr die Konstruktorfunktion so direkt definiert, weil sie dann global angelegt ist und sich nicht modularisieren lässt. Das wäre in meinem Fall aus meiner Sicht nicht weiter tragisch, weil ich kein Framework sondern eine Anwendung baue. Die verwendeten Frameworks modularisieren sich selbst und kommen nicht mit meinen Dingen ins Gehege.

Nächster Versuch ...

var Bar = (function () {  
	var self = {  
		name: ''  
	};  
  
	function Bar(data) {  
		setData(self, data, ['name']);  
	}  
  
	Object.defineProperties(Bar.prototype, {  
		Name: {  
			enumerable: true,  
			get: function () {  
				return self.name;  
			}  
		}  
	});  
  
	return Bar;  
})();  
  
var bar1 = new Bar({name: 'Bar1'});  
var bar2 = new Bar({name: 'Bar2'});  
console.log(bar1);  
console.log(bar2);  

Ausgabe:
Bar { Name="Bar2"}
Bar { Name="Bar2"}

Das war der zweite Ansatz. Dummerweise ist self in der kapselnden anonymen Funktion angesiedelt und da diese nur einmal existiert, existiert auch self nur einmal und nicht in jeder Objektinstanz separat. Ergebnis ist, dass beide Objekte Bar2 für Name zeigen. Vielleicht lässt sich das ja auch recht einfach beheben, nur weiß ich nicht wie. Die Eingenschaft ist jedenfalls diesmal am Prototypen eingehängt und die Konstruktorfunktionalität schön in der Function Bar zusammengefasst.

var Qux = (function () {  
	function Qux(data) {  
		this.self = {  
			name: ''  
		};  
		setData(this.self, data, ['name']);  
	}  
  
	Object.defineProperties(Qux.prototype, {  
		Name: {  
			enumerable: true,  
			get: function () {  
				return this.self.name;  
			}  
		}  
	});  
  
	return Qux;  
})();  
  
var qux1 = new Qux({name: 'Qux1'});  
var qux2 = new Qux({name: 'Qux2'});  
console.log(qux1);  
console.log(qux2);  

Ausgabe:
Qux { self={...}, Name="Qux1"}
Qux { self={...}, Name="Qux2"}

Mit diesem Ansatz ist das self-Problem behoben. Dies entspricht auch ungefähr dem Code, den TypeScript wählt, wenn es Propertys mit get/set-Accessors nach Javascript übersetzt. Der Preis dafür ist aber, dass self nun öffentlich im Objekt zu sehen ist und nicht mehr privat ist. (Man beachte die hinzugekommenen this im Code.) Das öffentliche self ist sicher nicht kriegsentscheidend, wenn man sich daran hält, es nicht direkt zu verändern. Aber schön geht anders. Was wäre denn empfehlenswert?

Ein JSFiddle gibts dazu auch: http://jsfiddle.net/V5X8b/ (Konsole anzeigen lassen nicht vergessen)

dedlfix.