ugrás a tartalomhoz

Javascript szülő osztály hívásának továbbörökítése

inf · 2010. Jan. 2. (Szo), 04.24
Üdv.

Van egy kis gondom, amire nem találtam semmilyen megoldást.
A lényeg, hogy származtatni szeretnék osztályokat egymásból, és a szülő osztály függvényeit elérni. A gondom az, hogy a szülő osztályból hívott függvényben a this.parent nem a nagyszülőre, hanem a szülőre vonatkozik, és így végtelen ciklusba kerül. Ez persze logikus, viszont kíváncsi vagyok létezik e megoldás ennek kikerülésére.

Itt egy példa:

Object.prototype.extend=function (source)
{
	if (source)
	{
		for (var property in source)
		{
			if
			(
				(property in Object.prototype)
				?
					source[property]!==Object.prototype[property]
				&&
					this[property]===Object.prototype[property]
				:
					!(property in this)
			)
			{
				this[property]=source[property];
			}
		}
	}
	return this;
};

Object.prototype.implement=function (source)
{
	if (source)
	{
		for (var property in source)
		{
			if
			(
				!(property in Object.prototype)
			||
				source[property]!==Object.prototype[property]
			)
			{
				this[property]=source[property];
			}
		}
	}
	return this;
};
	
Class=function (source)
{
	return function ()
	{
		if (this.initialize instanceof Function)
		{
			this.initialize.apply(this,arguments);
		}
	}.implement(source);
};

Function.prototype.extend(
{
	extend: function (source)
	{
		this.prototype.extend(source.prototype);
		this.prototype.parent=source.prototype;
		return this;
	},
	implement: function (source)
	{
		this.prototype.implement(source);
		return this;
	}
});
	
var a=new Class(
{
	initialize: function ()
	{
		this.a=1;
	}
});

var b=new Class(
{
	initialize: function ()
	{
		this.parent.initialize.apply(this,arguments);
		this.b=1;
	}
}).extend(a);

var c=new Class(
{
	initialize: function ()
	{
		this.parent.initialize.apply(this,arguments);
		this.c=1;
	}
}).extend(b);

var d=new c;
alert(d.a && d.b && d.c);
 
1

BUÉK

toxin · 2010. Jan. 2. (Szo), 13.08
első blikkre szvsz ott lehetne egyszerűen megfogni,hogy a parent hívásokat

 this.parent.initialize.apply(this,arguments); 
függetlenítjük a this-től, azaz a parent-et, a konstruktor függvények - mint objektumok - tagváltozójává tesszük >

 Function.prototype.extend(  
 {  
     extend: function (source)  
     { 
//...
 this.prototype.parent=source.prototype;  
//...
helyett :

 Function.prototype.extend(  
 {  
     extend: function (source)  
     { 
//...
 this.parent = source.prototype; 
//...
a kód első feléhez nem nyúltam, a második fele :

Function.prototype.extend({
    extend: function(source){
        this.prototype.extend(source.prototype);
        this.parent = source.prototype;
        return this;
    },
    implement: function(source){
        this.prototype.implement(source);
        return this;
    }
});
            
var A = new Class({
    initialize: function(){
        this.a = 1;
    },
	
	getA : function(){
		return this.a;
	}
});

var B = new Class({
    initialize: function(){
        B.parent.initialize.apply(this, arguments);
        this.b = 2;
    },
	
	getB : function(){
		return this.b;
	}
}).extend(A);

var C = new Class({
    initialize: function(){
        C.parent.initialize.apply(this, arguments);
        this.c = 3;
    },
	
	getC : function(){
		return this.c;
	},
	
	getAll : function(){
		return this.getC() + ":" + this.getB() + ":" + this.getA();
	}
	
}).extend(B);

var c = new C;
alert(c.getA() + ":" + c.getB() + ":" + c.getC()); // 1:2:3
alert(c.getAll()); // 3:2:1
az osztály változókat átírtam nagybetűssé...

üdv Csaba
2

Ez is egy lehetőség

inf · 2010. Jan. 2. (Szo), 15.03
Igen, ez is egy lehetőség, de úgy szeretném megoldani a dolgot, hogy az osztály nevek ne szerepeljenek. Mert amit írtál, ahelyett ennyi erővel ki is lehetne hatni ezt a parent-es dolgot:

	
Class=function (source)
{
	return function ()
	{
		if (this.initialize instanceof Function)
		{
			this.initialize.apply(this,arguments);
		}
	}.implement(source);
};

Function.prototype.extend(
{
	extend: function (source)
	{
		this.prototype.extend(source.prototype);
		return this;
	},
	implement: function (source)
	{
		this.prototype.implement(source);
		return this;
	}
});
	
var A=new Class(
{
	initialize: function ()
	{
		this.a=1;
	}
});

var B=new Class(
{
	initialize: function ()
	{
		A.prototype.initialize.apply(this,arguments);
		this.b=1;
	}
}).extend(A);

var C=new Class(
{
	initialize: function ()
	{
		B.prototype.initialize.apply(this,arguments);
		this.c=1;
	}
}).extend(B);

Szóval a dolog lényege pont az, hogy az osztályok nevei ne szerepeljenek a függvényekben. Viszont a dolog lényege meg pont az, hogy a függvények-et hozzá kéne kötni valahogyan az osztályokhoz. Pl a szülő init-jét a szülőhöz stb.. Ezzel viszont az a probléma, hogy nem egyszerű :-) Pl azt nem lehet mondani, hogy arguments.callee.ownerClass, mert egy függvényt interface-ből is hozzáadhatok több osztályhoz, és azokból örököltethetem. Interface-ben mondjuk nem lehet parent-et hívni, hmm nemsokára előállok valamivel.

No az interface-el annyi a gond, hogy js-ben csak megvalósítást lehet megadni absztrakció helyett. Ettől függetlenül megoldottam úgy, hogy az alapból megadott függvényeket az osztályhoz társítja, az implementáltakat pedig nem.

Object.prototype.each=function (method,context)
{
	if (!context)
	{
		context=this;
	}
	for (var property in this)
	{
		if
		(
			!(property in Object.prototype)
		||
			this[property]!==Object.prototype[property]
		)
		{
			method.call(context,property,this[property]);
		}
	}
	return context;
};

Object.prototype.implement=function (source)
{
	if (source)
	{
		source.each(function (property,value)
		{
			this[property]=source[property];
		},this);
	}
	return this;
};

Object.prototype.extend=function (source)
{
	if (source)
	{
		source.each(function (property,value)
		{
			if (
				!(property in this)
			||
				(
					(property in Object.prototype)
				&&
					this[property]===Object.prototype[property]
				)
			)
			{
				this[property]=source[property];
			}
		},this);
	}
	return this;
};

Function.prototype.each=function (method,context)
{
	if (!context)
	{
		context=this;
	}
	return this.prototype.each(method,context);
};

Function.prototype.implement=function (source)
{
	this.prototype.implement(source);
	return this;
};

Function.prototype.extend=function (source)
{
	if (!this.parent)
	{
		this.parent=source;
		this.prototype.extend(source.prototype);
		this.each(function (property,method)
		{
			if ((method instanceof Function) && method.Class==this)
			{
				method.parent=source.prototype;
			}
		});
	}
	return this;
};


Class=function (source)
{
	return function ()
	{
		if (this.initialize instanceof Function)
		{
			this.initialize.apply(this,arguments);
		}
	}.implement(source).each(function (property,method)
	{
		if (method instanceof Function)
		{
			method.Class=this;
		}
	});
};


var a=new Class(
{
	initialize: function ()
	{
		this.a=1;
	}
});

var b=new Class(
{
	initialize: function ()
	{
		arguments.callee.parent.initialize.apply(this,arguments);
		this.b=1;
	}
}).extend(a);

var c=new Class(
{
	initialize: function ()
	{
		arguments.callee.parent.initialize.apply(this,arguments);
		this.c=1;
	}
}).extend(b);

var d=new c;
alert(d.a && d.b && d.c);

Persze lehetne valami this.getParent()-es dolog is arguments.callee.caller-el, de úgy emlékszem az nem cross-browser.
3

második blikk : klauzúra

toxin · 2010. Jan. 2. (Szo), 16.28
ha annyi módosítás belefér, hogy

this.parent.initialize.apply(this,arguments);
helyett

this._parent_initialize(arguments);
stb. akkor :

bind = function(f,context){
	return function(){
		f.apply(context, arguments);
	}
}
			
Object.prototype.extend = function(source){
    if (source) {
        for (var property in source)(bind(function(property){
            if ((property in Object.prototype) ? source[property] !== Object.prototype[property] &&
            this[property] === Object.prototype[property] : !(property in this)) {
               this[property] = source[property];			  
            }else if (!(/_parent_/.test(property)) && !(property in Object.prototype) && property in this){				
				var prop = property, target = null;
				this["_parent_" + prop] = function(){
					// we are in the instance !!!
					target = !target ? source : target.parent;
					var ret = target[prop].apply(this,arguments);
					target = null;
					return ret;								
				}
			} 
        },this))(property);
		return this;
    }                
};

Object.prototype.implement = function(source){
    if (source) {
        for (var property in source) {
            if (!(property in Object.prototype) ||
            source[property] !== Object.prototype[property]) {
                this[property] = source[property];
            }
        }
    }
    return this;
};

Class = function(source){
    return function(){
        if (this.initialize instanceof Function) {
            this.initialize.apply(this, arguments);
        }
    }
.implement(source);
};

Function.prototype.extend({
    extend: function(source){
        this.prototype.extend(source.prototype);
        this.prototype.parent = source.prototype;
        return this;
    },
    implement: function(source){
        this.prototype.implement(source);
        return this;
    }
});

var A = new Class({
    initialize: function(){
        this.fooVar = 1;
		this.a = 1;
    },
	
	get : function(){
		alert( this.fooVar );
	},
	
	getA : function(){
		return this.a;
	}
});

var B = new Class({
    initialize: function(){
        this._parent_initialize(arguments);
        this.fooVar = 2;
		this.b = 2;
    },
	
	get : function(){
		this._parent_get();
		alert( this.fooVar );
	},
	
	getB : function(){
		return this.b;
	}
	
}).extend(A);

var C = new Class({
    initialize: function(){
        this._parent_initialize(arguments);
        this.fooVar = 3;
		this.c = 3;
    },
	
//	get : function(){} a szülő osztályból
	
	getC : function(){
		return this.c;
	},
	
	getAll : function(){
		alert( this.getC() + ":" + this.getB() + ":" + this.getA() );
	}
	
}).extend(B);

var c = new C;
c.get(); // 3 3 3
c.getAll(); // 3 : 2 : 1
a lényegi rész az eleje, azután nincs lényeges módosítás

bind = function(f,context){
	return function(){
		f.apply(context, arguments);
	}
}
			
Object.prototype.extend = function(source){
    if (source) {
        for (var property in source)(bind(function(property){
            if ((property in Object.prototype) ? source[property] !== Object.prototype[property] &&
            this[property] === Object.prototype[property] : !(property in this)) {
               this[property] = source[property];			  
            }else if (!(/_parent_/.test(property)) && !(property in Object.prototype) && property in this){				
				var prop = property, target = null;
				this["_parent_" + prop] = function(){
					// we are in the instance !!!
					target = !target ? source : target.parent;
					var ret = target[prop].apply(this,arguments);
					target = null;
					return ret;								
				}
			} 
        },this))(property);
		return this;
    }                
};
bind a for/klauzúra miatt kellett, elv : ha adott osztályban van már olyan metódus mint a szűlőben : _parent_ prefixel elérhetővé tettem, és közben klauzúra segítségével nyilvántartottam hogy melyik szinten vagyunk az öröklődési fában (amin megyünk felfele), ha vége az adott hívási láncnak : akkor vissza alaphelyzetbe + return ... nem mondom hogy hibátlan de gondolkodjon más is :) ill. egyszer már nekifutottam ennek lásd : http://weblabor.hu/forumok/temak/21044#comment-52091

üdv Csaba
4

Beleférne

inf · 2010. Jan. 2. (Szo), 16.30
Hasonlót már csináltam static-al, ott valami ilyesmi volt:

var X=new Class(
{
	static_inspect: function ()
	{
		alert("Huhh!");
	}
});
X.inspect();//Huhh!
A this.parent_initialize beleférne, viszont szerintem nem megoldható. A kód, amit írtál nagyon nincs rendben.

Ott kezdődik, hogy a Object.prototype.extend csak egy segédfüggvény, aminek semmi köze nincs az osztályokhoz. Annyit csinál, hogy hozzáadja a source tulajdonságait az objektumhoz, ha még nincsenek megadva.

pl:

var o=({a:1}).extend({a:2,b:2});
alert(o.a+","+o.b); //1,2
this-ből kiindulva a bind-hoz hasonló módon lehet megoldani, csak bonyolultabban.
A függvények köré olyan burkoló függvényt kell tenni, ami a this.parent értékét folyamatosan átírja. Meg lehet oldani ezen az úton is, de az már inkább gányolás kategória. A burkolófüggvényes megoldásokat nem szívesen használom amúgy sem, mert egy függvény hívásból kettőt csinálnak. (Mondjuk az apply vagy a call is, de azok legalább beépítettek...)
5

javítás

toxin · 2010. Jan. 2. (Szo), 17.41

Object.prototype.extend = function(source){
    if (source) {
		var that = this;
        for (var property in source)(function(){
            if ((property in Object.prototype) ? source[property] !== Object.prototype[property] &&
            that[property] === Object.prototype[property] : !(property in that)) {
               that[property] = source[property];			  
            }else if (!(/_parent_/.test(property)) && !(property in Object.prototype) && property in that){				
				
				var prop = property, target = null;
				that["_parent_" + prop] = function(){
					// we are in the instance !!!
					var prevTarget = target;
					target = !target ? source : target.parent;
					var ret = target[prop].apply(this,arguments);
					target = prevTarget;
					return ret;								
				}
				
			} 
        })();
		return that;
    }                
};
bind-ot kiszedtem, és az osztályhierarchia nyilvántartást is finomítottam, előbbi verzióban gond lett volna, ha adott szintről egymás után többször is hívjuk az odatartozó _parent_ tagmetódust

üdv Csaba

ui: hogy most gány vagy nem, ha lett volna jobb ötletem azt írom be ;), jó rég foglalkoztam ilyesmivel komolyabban...sajnos nem tipikus napi feladat, "hogy a Object.prototype.extend csak egy segédfüggvény" ha már müxik oda rakod ahova akarod :) engem most csak a probléma izgatott (mint logikai feladat :) )
6

Tetszik

inf · 2010. Jan. 2. (Szo), 17.46
Ja, valami ilyenre gondoltam, amikor burkolófüggvényes megoldásról beszéltem. Az elve az szép, viszont többszörös öröklődésnél nem vagyok benne biztos, hogy jól hívja a függvényeket. Gondolok itt valami ilyesmire:

var A=new Class(
{
	test: function ()
	{
		this.a=1;
	}
});

var B=new Class(
{
	test: function ()
	{
		this.parent_test();
		this.a2=2;
	}
}).extend(A);

var C=new Class(
{
	b: function ()
	{
		this.test();
	}
}).extend(B);

var d=new C;
d.b();

Ránézésre a te megoldásod ettől végtelen ciklusba kerülne, de nem próbáltam ki. :-) Majd holnap összehozok valami hasonlót, mint amit csináltál.
7

még bírja :)

toxin · 2010. Jan. 2. (Szo), 17.58

Object.prototype.extend = function(source){
    if (source) {
		var that = this;
        for (var property in source)(function(){
            if ((property in Object.prototype) ? source[property] !== Object.prototype[property] &&
            that[property] === Object.prototype[property] : !(property in that)) {
               that[property] = source[property];			  
            }else if (!(/_parent_/.test(property)) && !(property in Object.prototype) && property in that){				
				
				var prop = property, target = null;
				that["_parent_" + prop] = function(){
					// we are in the instance !!!
					var prevTarget = target;
					target = !target ? source : target.parent;
					var ret = target[prop].apply(this,arguments);
					target = prevTarget;
					return ret;								
				}
				
			} 
        })();
		return that;
    }                
};

Object.prototype.implement = function(source){
    if (source) {
        for (var property in source) {
            if (!(property in Object.prototype) ||
            source[property] !== Object.prototype[property]) {
                this[property] = source[property];
            }
        }
    }
    return this;
};

Class = function(source){
    return function(){
        if (this.initialize instanceof Function) {
            this.initialize.apply(this, arguments);
        }
    }
.implement(source);
};

Function.prototype.extend({
    extend: function(source){
        this.prototype.extend(source.prototype);
        this.prototype.parent = source.prototype;
        return this;
    },
    implement: function(source){
        this.prototype.implement(source);
        return this;
    }
});

var A=new Class(
{
	test: function ()
	{
		console.log("3");
		this.a=1;
	}
});

var B=new Class(
{ 
	test: function ()
	{
		console.log("2");
		this._parent_test();
		console.log("4");
		this.a2=2;
	}
}).extend(A);

var C=new Class(
{
	b: function ()
	{
		console.log("1");
		this.test();
		console.log("5");
	}
}).extend(B);

var d=new C;
d.b();
konzolban: 1 2 3 4 5

üdv Csaba
8

azért nem tökéletes

inf · 2010. Jan. 2. (Szo), 19.21

var A=new Class(  
{  
   test: function ()  
   {  
	   alert("2");
   }  
});  
 
var B=new Class(  
{   
   test: function ()  
   {  
	   alert("1");  
	   this._parent_test();  
	   alert("3"); 
   }  
}).extend(A);  
 
var C=new Class({}).extend(B);  

var D=new Class(
{
   test: function ()  
   {  
	   alert("0");  
	   this._parent_test();  
	   alert("4");  
   }
}).extend(C);
 
var d=new D;  
d.test();  
9

javitás

toxin · 2010. Jan. 3. (V), 13.43
kiraktam külön függvénybe a _parent_ -el kapcsolatos módosításkoat

Object.prototype.extend = function(source){
    if (source) {
        for (var property in source) {
            if ((property in Object.prototype) ? source[property] !== Object.prototype[property] &&
            this[property] === Object.prototype[property] : !(property in this)) {
                this[property] = source[property];
            }
        }
    }
    return this;
};

Object.prototype.implement = function(source){
    if (source) {
        for (var property in source) {
            if (!(property in Object.prototype) ||
            source[property] !== Object.prototype[property]) {
                this[property] = source[property];
            }
        }
    }
    return this;
};

Class = function(source){
    return function(){
        if (this.initialize instanceof Function) {
            this.initialize.apply(this, arguments);
        }
    }
.implement(source);
};

Function.prototype.extend({
    extend: function(source){
        this.prototype.extend(source.prototype);
        this.prototype.parent = source.prototype;
        patchInheritance(this.prototype, source.prototype); 
        return this;
    },
    implement: function(source){
        this.prototype.implement(source);
        return this;
    }
});

function patchInheritance(_class, _parentClass){
    for (var property in _parentClass)(function(){
        if (!(/_parent_/.test(property)) &&
		 	!(property in Object.prototype) && 
			property in _class
			) {
            var prop = property, klass = null;
            _class["_parent_" + property] = function(){
                var prevKlass = klass;
                klass = !klass ? _parentClass : klass.parent;
                var ret = klass[prop].apply(this, arguments);
                klass = prevKlass;
                return ret;
            }
        }
    })();               
}

var A = new Class({
    test: function(){
        console.log(this.getClass() + ":" + "2");
    },
	
	getClass : function(){
		return "A";
	}
					
});

var B = new Class({
    test: function(){
        console.log(this.getClass() + ":" +"1");
        this._parent_test();
        console.log(this.getClass() + ":" +"3");
    },
	
	getClass : function(){
		return "B";
	}
	
}).extend(A);

var C = new Class({				
	getClass : function(){
		return "C";
	}				
}).extend(B);

var D = new Class({
    test: function(){
        console.log(this.getClass() + ":" +"0");
        this._parent_test();
        console.log(this.getClass() + ":" +"4");
    },
	
	getClass : function(){
		return "D";
	}			
	
}).extend(C);

var d = new D;
d.test();
konzolban :
D:0
D:1
D:1
D:2
D:3
D:3
D:4

kontextus nem változik, de lépked felfele az öröklődőséi fán, ha átírom a

var ret = klass[prop].apply(this, arguments); 

var ret = klass[prop].apply(klass, arguments); 
-ra, azaz ha a kontextust adott prototype objektumra állítom, a kimenet:
D:0
C:1
B:1
A:2
B:3
C:3
D:4

jónak látszik

üdv Csaba

jav. :

és ígymár a tegnapi

var A=new Class(
{
	test: function ()
	{
		console.log("3");
		this.a=1;
	}
});

var B=new Class(
{ 
	test: function ()
	{
		console.log("2");
		this._parent_test();
		console.log("4");
		this.a2=2;
	}
}).extend(A);

var C=new Class(
{
	b: function ()
	{
		console.log("1");
		this.test();
		console.log("5");
	}
}).extend(B);

var d=new C;
d.b();
is a helyes :
1
2
2
3
4
4
5

kimenetet adja, 1 2 3 4 5 helyett, ooopsi
10

:D

inf · 2010. Jan. 3. (V), 19.38
Egy kis plusz logikát kéne belevinni, hogy bizonyos esetekben máshogy viselkedjen... (Ne csak egyet lépjen felfele a fán etc...)
11

Saját

inf · 2010. Jan. 6. (Sze), 13.12
Csináltam én is burkolófüggvényeset az én megközelítésemmel.

Object.prototype.each=function(method,context)
{
	if (!context)
	{
		context=this;
	}
	for (var property in this)
	{
		var value=this[property];
		if (
			!(property in Object.prototype)
		||
			value!==Object.prototype[property]
		)
		{
			method.call(context,property,value);
		}
	}
	return context;
};

Object.prototype.extend=function(source)
{
	if (source)
	{
		for (var property in source)
		{
			if (
				(property in Object.prototype)
				?
					(
						source[property]!==Object.prototype[property]
					&&
						this[property]===Object.prototype[property]
					)
				:
					(
						!(property in this)
					)
			)
			{
				this[property]=source[property];
			}
		}
	}
	return this;
};

Object.prototype.implement=function(source)
{
	if (source)
	{
		for (var property in source)
		{
			if (
				!(property in Object.prototype)
			||
				source[property]!==Object.prototype[property]
			)
			{
				this[property]=source[property];
			}
		}
	}
	return this;
};

Class=function(source)
{
	return function()
	{
		if (this.initialize instanceof Function)
		{
			this.initialize.apply(this,arguments);
		}
	}.implement(source);
};

Function.prototype.extend(
{
	extend: function(parent)
	{
		if (this.parent)
		{
			return this;
		}
		this.parent=parent;
		this.prototype.extend(parent.prototype);
		return this;
	},
	implement: function(source)
	{
		
		var Class=this;
		return source.each(function (property,value)
		{
			this.prototype[property]=(value instanceof Function)
				?function ()
				{
					var parent=this.parent;
					this.parent=Class.parent && Class.parent.prototype;
					var result=value.apply(this,arguments);
					this.parent=parent;
					return result;
				}
				:value;
		},this);
	}
});

var A=new Class(
{
	initialize: function ()
	{
		alert("0");
	}
});

var B=new Class(
{
	initialize: function ()
	{
		this.parent.initialize.apply(this,arguments);
		alert("1");
	}
}).extend(A);

var C=new Class(
{
	initialize: function ()
	{
		this.parent.initialize.apply(this,arguments);
		alert("2");
	}
}).extend(B);

var D=new Class(
{
	initialize: function ()
	{
		this.parent.initialize.apply(this,arguments);
		alert("3");
	}
}).extend(C);

new D;