1
0
Fork 0

isInstanceOf() now works with interfaces

closure/master
Mike Gerwitz 2011-01-04 00:37:54 -05:00
parent c3b00ab9b7
commit 56c13b757b
3 changed files with 86 additions and 16 deletions

View File

@ -27,10 +27,9 @@ var util = require( './util' );
/** /**
* Stores class metadata internally (ensures data is encapsulated) * Stores class metadata internally (ensures data is encapsulated)
* *
* The data in this object is hashed on the class object itself. Therefore, to * The data in this object is hashed a class id.
* look up an item, you would provide the class itself as the property.
* *
* @type {Object.<Object, { implemented: Array.<string> }>} * @type {Object.<number, { implemented: Array.<string> }>}
*/ */
var class_meta = {}; var class_meta = {};
@ -76,7 +75,7 @@ exports.implement = function()
} }
var class_new = exports.extend( dest ); var class_new = exports.extend( dest );
getMeta( class_new ).implemented = implemented; getMeta( class_new.__cid ).implemented = implemented;
return class_new; return class_new;
}; };
@ -149,8 +148,9 @@ exports.isInstanceOf = function( type, instance )
// if no metadata is available, then our remaining checks cannot be // if no metadata is available, then our remaining checks cannot be
// performed // performed
if ( !( meta = getMeta( type ) ) ) if ( !instance.__cid || !( meta = getMeta( instance.__cid ) ) )
{ {
console.log( instance );
return false; return false;
} }
@ -203,6 +203,8 @@ function Class() {};
*/ */
var extend = ( function( extending ) var extend = ( function( extending )
{ {
var class_id = 0;
/** /**
* Mimics class inheritance * Mimics class inheritance
* *
@ -274,18 +276,20 @@ var extend = ( function( extending )
// set up the new class // set up the new class
var new_class = createCtor( abstract_methods ); var new_class = createCtor( abstract_methods );
setupProps( new_class, abstract_methods );
attachPropInit( prototype, properties ); attachPropInit( prototype, properties );
new_class.prototype = prototype; new_class.prototype = prototype;
new_class.constructor = new_class; new_class.constructor = new_class;
// important: call after setting prototype
setupProps( new_class, abstract_methods, ++class_id );
// lock down the new class (if supported) to ensure that we can't add // lock down the new class (if supported) to ensure that we can't add
// members at runtime // members at runtime
util.freeze( new_class ); util.freeze( new_class );
// create internal metadata for the new class // create internal metadata for the new class
createMeta( new_class ); createMeta( new_class, base.prototype.__cid );
// we're done with the extension process // we're done with the extension process
extending = false; extending = false;
@ -360,13 +364,15 @@ var extend = ( function( extending )
* *
* @param {Function} func function (class) to set up * @param {Function} func function (class) to set up
* @param {Array.<string>} abstract_methods list of abstract method names * @param {Array.<string>} abstract_methods list of abstract method names
* @param {number} class_id unique id to assign to class
* *
* @return {undefined} * @return {undefined}
*/ */
function setupProps( func, abstract_methods ) function setupProps( func, abstract_methods, class_id )
{ {
attachAbstract( func, abstract_methods ); attachAbstract( func, abstract_methods );
attachExtend( func ); attachExtend( func );
attachId( func, class_id );
} }
@ -466,6 +472,25 @@ function attachExtend( func )
} }
/**
* Attaches the unique id to the class and its prototype
*
* The unique identifier is used internally to match a class and its instances
* with the class metadata. Exposing the id breaks encapsulation to a degree,
* but is a lesser evil when compared to exposing all metadata.
*
* @param {Function} func function (class) to attach method to
* @param {number} id id to assign
*
* @return {undefined}
*/
function attachId( func, id )
{
util.defineSecureProp( func, '__cid', id );
util.defineSecureProp( func.prototype, '__cid', id );
}
/** /**
* Attaches partially applied isInstanceOf() method to class instance * Attaches partially applied isInstanceOf() method to class instance
* *
@ -488,15 +513,30 @@ function attachInstanceOf( instance )
/** /**
* Initializes class metadata for the given class * Initializes class metadata for the given class
* *
* @param {Class} obj class to initialize metadata for * @param {Class} func class to initialize metadata for
* *
* @return {undefined} * @return {undefined}
*/ */
function createMeta( obj ) function createMeta( func, parent_id )
{ {
class_meta[ obj ] = { var id = func.__cid,
parent_meta = ( ( parent_id ) ? getMeta( parent_id) : undefined );
// copy the parent prototype's metadata if it exists (inherit metadata)
if ( parent_meta )
{
// todo: deep (1) util.clone() when available
class_meta[ id ] = {
implemented: util.clone( parent_meta.implemented ),
};
}
else
{
// create empty
class_meta[ id ] = {
implemented: [], implemented: [],
}; };
}
} }
@ -506,12 +546,12 @@ function createMeta( obj )
* Since a reference is returned (rather than a copy), the returned object can * Since a reference is returned (rather than a copy), the returned object can
* be modified to alter the metadata. * be modified to alter the metadata.
* *
* @param {Class} obj class to retrieve metadata for * @param {number} id id of class to retrieve metadata for
* *
* @return {Object} * @return {Object}
*/ */
function getMeta( obj ) function getMeta( id )
{ {
return class_meta[ obj ]; return class_meta[ id ];
} }

View File

@ -77,3 +77,21 @@ assert.ok(
"Abstract methods list contains names of implemented methods" "Abstract methods list contains names of implemented methods"
); );
// concrete implementation so that we can instantiate it
var ConcreteFoo = Foo.extend(
{
foo: function() {},
foo2: function() {},
}),
concrete_inst = new ConcreteFoo();
assert.ok(
( concrete_inst.isInstanceOf( Type )
&& concrete_inst.isInstanceOf( Type2 )
),
"Instances of classes implementing interfaces are considered to be " +
"instances of the implemented interfaces"
);

View File

@ -132,3 +132,15 @@ assert.equal(
"Class instance contains isA() alias for isInstanceOf() partially applied function" "Class instance contains isA() alias for isInstanceOf() partially applied function"
); );
assert.ok(
( Foo.__cid !== undefined ),
"Class id available via class"
);
assert.ok(
( Foo.prototype.__cid !== undefined ),
"Class id available via class prototype"
);