diff --git a/lib/class.js b/lib/class.js index fd37b17..26cd973 100644 --- a/lib/class.js +++ b/lib/class.js @@ -109,9 +109,11 @@ var extend = ( function( extending ) prototype.parent = base.prototype; // set up the new class - var new_class = create_ctor( result_data, properties ); + var new_class = create_ctor( result_data ); setup_props( new_class, result_data ); + attach_prop_init( prototype, properties ); + new_class.prototype = prototype; new_class.constructor = new_class; @@ -143,15 +145,7 @@ var extend = ( function( extending ) { return function() { - // initialize each of the properties for this instance to - // ensure we're not sharing prototype values - var value; - for ( prop in properties ) - { - // initialize the value with a clone to ensure that they do - // not share references (and therefore, data) - this[ prop ] = util.clone( properties[ prop ] ); - } + this.__initProps(); // call the constructor, if one was provided if ( this.__construct instanceof Function ) @@ -190,6 +184,47 @@ function setup_props( func, class_data ) } +/** + * Attaches __propInit() method to the class prototype + * + * The __propInit() method will initialize class properties for that instance, + * ensuring that their data is not shared with other instances (this is not a + * problem with primitive data types). + * + * The __propInit() method will also initialize any parent properties + * (recursive) to ensure that subtypes do not have a referencing issue, and + * subtype properties take precedence over those of the parent. + * + * @param {Object} prototype prototype to attach method to + * @param {Object} properties properties to initialize + * + * @return {undefined} + */ +function attach_prop_init( prototype, properties ) +{ + util.defineSecureProp( prototype, '__initProps', function() + { + // first initialize the parent's properties, so that ours will overwrite + // them + var parent_init = prototype.parent.__initProps; + if ( parent_init instanceof Function ) + { + parent_init.call( this ); + } + + // initialize each of the properties for this instance to + // ensure we're not sharing prototype values + var value; + for ( prop in properties ) + { + // initialize the value with a clone to ensure that they do + // not share references (and therefore, data) + this[ prop ] = util.clone( properties[ prop ] ); + } + }); +} + + /** * Attaches isAbstract() method to the class * diff --git a/test/test-class-extend.js b/test/test-class-extend.js index 4d61083..8dccbfa 100644 --- a/test/test-class-extend.js +++ b/test/test-class-extend.js @@ -166,3 +166,22 @@ assert.ok( "Multiple instances of the same class do not share object references" ); +var arr_val = 1; +var SubAnotherFoo = AnotherFoo.extend( +{ + arr: [ arr_val ], +}); + +var SubObj1 = new SubAnotherFoo(), + SubObj2 = new SubAnotherFoo(); + +assert.ok( + ( ( SubObj1.arr !== SubObj2.arr ) && ( SubObj1.obj !== SubObj2.obj ) ), + "Instances of subtypes do not share property references" +); + +assert.ok( + ( ( SubObj1.arr[ 0 ] === arr_val ) && ( SubObj2.arr[ 0 ] === arr_val ) ), + "Subtypes can override parent property values" +); +