sobota, 20 października 2012

[HTML|JS|CSS] JavaScript Patterns: Code Reuse

JavaScript jest językiem, w którym nie występują klasy, a zatem klasyczne dziedziczenie znane chociażby z C++ musimy zastępować pewnymi wzorcami. Dzięki ich zastosowaniu będziemy mogli wiele razy używać niektórych fragmentów kodu.

  • Classical pattern

Wzorzec ten ma imitować dziedziczenie klas z języków obiektowych, stąd nazwa. Istnieje kilka sposobów jego implementacji. Poniżej przykład, w którym wykorzystuje się obiekt prototype. Zasada działania jest prosta. Jeżeli przy wywołaniu funkcji nie ma jej w konstruktorze danego obiektu, jej sygnatura poszukiwana jest w referencji __proto__ wskazującej na obiekt prototype. Podobnie rzecz ma się z properties.


function Parent1(n){
 this.name = n || 'Parent2';
}

Parent1.prototype.say = function(){
 console.log('My name is: ' + this.name);
};

function Child1(n){
 Parent1.apply(this,arguments);
}
Child1.prototype = new Parent1();

var c1 = new Child1('Child1');
console.log(c1.name);
c1.say();
delete c1.name;
c1.say();

Konsola:

Child1
My name is: Child1
My name is: Parent1

  • Klass

Ideą tego wzorca jest emulacja klas z języków obiektowych, a więc doprowadzenie do takiego API jak w poniższym przykładzie.


var car1 = new car('simple car');
car1.printName();
var car2 = new bmw('bmw car');
car2.printName();

Definicję funkcji konstruktorów wprowadza się za pomocą specjalnej funkcji klass. Przyjmuje ona funkcję konstruktora dla parenta oraz listę properties (jako obiekt), charakterystycznych dla nowego obiektu.


var car = klass(null, {
	__construct : function(name){
		console.log('constructing car...');
		this.name = name;
	},
	printName : function(){
		console.log(this.name);
	}
});

var bmw = klass(car, {
	__construct : function(){
		console.log('constructing bmw');
	},
	printName : function(){
		console.log('bmw printer');
		bmw.uber.printName.call(this);
	}
});

Cała "magia" zawarta jest w funkcji klass:

var klass = function(Parent, props){
	var Child, F, i;
	Child = function(){
		if(Child.uber && Child.uber.hasOwnProperty("__construct")){
			Child.uber.__construct.apply(this, arguments);
		}
		if(Child.prototype.hasOwnProperty("__construct")){
			Child.prototype.__construct.apply(this, arguments);
		}
	};
	Parent = Parent || Object;
	F = function(){};
	F.prototype = Parent.prototype;
	Child.prototype = new F();
	Child.uber = Parent.prototype;
	Child.prototype.constructor = Child;

	for(i in props){
		if (props.hasOwnProperty(i)){
			Child.prototype[i] = props[i];
		}
	}
	return Child;
};

Przy użyciu pomocniczego obiektu funkcji F(), obiekt Child przejmuje prototype swojego parenta podanego jako argument funkcji. Obiekty uber i prototype służą do wywoływania konstruktorów będących wyżej w hierarchii dziedziczenia w momencie konstrukcji obiektu. Ostatecznie kopiowane są również properties obiektu parent.

  • Prototypical inheritance

Jest to prostszy od powyższych i często wystarczający wzorzec, w którym mechanizm dziedziczenia polega na przekazaniu prototypu rodzica do prototypu dziecka. Warto zwrócić uwagę na to jak w obiekcie dziedziczącym zachowuje się funkcja definiowana w prototype, a jak ta z obiektu parenta.

function object(parent){
	var F = function(){};
	F.prototype = parent;
	return new F();
}

function Parent3(){
	this.name = 'Parent3';
	var that = this;
	this.print = function(){
		console.log(that.name);
	};
}

Parent3.prototype.printName = function(){
	console.log(this.name);
};

var newparent = {
	name: "ParentName"
};

var child = object(newparent);
console.log(child.name);

//errors
//child.printName();
//child.print();

var parent3 = new Parent3('Parent3');
var child3 = object(parent3);

child3.name = "Child3";
child3.printName();
child3.print();

Konsola wypisze dwa różne wyniki, ponieważ jedynie funkcja z prototypu będzie wywołana z property z obiektu dziedziczącego. Ponadto obiekt z którego chcemy dziedziczyć musi być tworzony przez operator new.

ParentName
Child3
Parent3

  • Mixin


Jest to specjalny wzorzec umożliwiający dziedziczenie z kilku prostych obiektów w jeden. Tworzy się specjalną funkcję mix, do której przekazywane są jako argumenty obiekty, które chcemy złączyć w całość.


function mix(){
	var arg, prop, child = {};
	for (arg = 0; arg < arguments.length; arg++) {
		for(prop in arguments[arg]){
			if(arguments[arg].hasOwnProperty(prop)){
				child[prop] = arguments[arg][prop];
			}
		}
	}
	return child;
}

var obj = mix({i : 1, one : "one"},
			{j : 1, two : "two"},
			{j : 2, three : "three"});

console.dir(obj);

W przypadku dwóch identycznie nazywających się properties w obiektach, wybrany zostanie ostatni.

{ i: 1, one: 'one', j: 1, two: 'two', three: 'three' }

Brak komentarzy:

Prześlij komentarz