diff --git a/lib/class.js b/lib/class.js index 5ad0558..185f40d 100644 --- a/lib/class.js +++ b/lib/class.js @@ -24,6 +24,16 @@ var util = require( './util' ); +/** + * Stores class metadata internally (ensures data is encapsulated) + * + * The data in this object is hashed on the class object itself. Therefore, to + * look up an item, you would provide the class itself as the property. + * + * @type {Object. }>} + */ +var class_meta = {}; + /** * Creates a class, inheriting either from the provided base class or the @@ -66,13 +76,7 @@ exports.implement = function() } var class_new = exports.extend( dest ); - - // we cannot reassign the value since it is frozen, so copy the values into - // the array - while ( implemented[ 0 ] ) - { - class_new.implemented.push( implemented.shift() ); - } + getMeta( class_new ).implemented = implemented; return class_new; }; @@ -130,6 +134,8 @@ exports.isClassInstance = function( obj ) */ exports.isInstanceOf = function( type, instance ) { + var meta, implemented, i; + try { // check prototype chain (with throw an error if type is not a @@ -141,16 +147,22 @@ exports.isInstanceOf = function( type, instance ) } catch ( e ) {} - var implemented = type.implemented; - if ( implemented instanceof Array ) + // if no metadata is available, then our remaining checks cannot be + // performed + if ( !( meta = getMeta( type ) ) ) { - var i = implemented.length; - while ( i-- ) + return false; + } + + implemented = meta.implemented; + i = implemented.length; + + // check implemented interfaces + while ( i-- ) + { + if ( implemented[ i ] === type ) { - if ( implemented[ i ] === type ) - { - return true; - } + return true; } } @@ -272,6 +284,9 @@ var extend = ( function( extending ) // members at runtime util.freeze( new_class ); + // create internal metadata for the new class + createMeta( new_class ); + // we're done with the extension process extending = false; @@ -352,7 +367,6 @@ function setupProps( func, abstract_methods ) { attachAbstract( func, abstract_methods ); attachExtend( func ); - attachImplemented( func ); } @@ -452,19 +466,6 @@ function attachExtend( func ) } -/** - * Attaches empty list that may be populated with the implemented interfaces - * - * @param {Function} func function (class) to attach method to - * - * @return {undefined} - */ -function attachImplemented( func ) -{ - util.defineSecureProp( func, 'implemented', [] ); -} - - /** * Attaches partially applied isInstanceOf() method to class instance * @@ -483,3 +484,34 @@ function attachInstanceOf( instance ) util.defineSecureProp( instance, 'isA', method ); } + +/** + * Initializes class metadata for the given class + * + * @param {Class} obj class to initialize metadata for + * + * @return {undefined} + */ +function createMeta( obj ) +{ + class_meta[ obj ] = { + implemented: [], + }; +} + + +/** + * Returns reference to metadata for the requested class + * + * Since a reference is returned (rather than a copy), the returned object can + * be modified to alter the metadata. + * + * @param {Class} obj class to retrieve metadata for + * + * @return {Object} + */ +function getMeta( obj ) +{ + return class_meta[ obj ]; +} + diff --git a/test/test-class-implement.js b/test/test-class-implement.js index fb3cad6..06aab33 100644 --- a/test/test-class-implement.js +++ b/test/test-class-implement.js @@ -45,24 +45,11 @@ assert.ok( var Foo = {}, PlainFoo = Class.extend(); -assert.ok( - ( PlainFoo.implemented instanceof Array ), - "Class contains empty list of implemented interfaces if " + - "none are implemented" -); - assert.doesNotThrow( function() { Foo = Class.implement( Type, Type2 ); }, Error, "Class can implement interfaces" ); -assert.ok( - ( ( Foo.implemented[ 0 ] === Type ) - && ( Foo.implemented[ 1 ] === Type2 ) - ), - "Class contains list of implemented interfaces" -); - assert.ok( ( ( Foo.prototype.foo instanceof Function ) && ( Foo.prototype.foo2 instanceof Function )