Initial implementation of protected members
- This was quite the pain in the ass - There are additional considerations. I DO NOT recommend using this commit. Check out a later commit.closure/master
parent
2af7bcf45d
commit
74c2fc57c1
115
lib/class.js
115
lib/class.js
|
@ -34,6 +34,16 @@ var util = require( './util' ),
|
||||||
*/
|
*/
|
||||||
var class_meta = {};
|
var class_meta = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores class instance visibility object
|
||||||
|
*
|
||||||
|
* For each instance id, an object exists that contains the private and
|
||||||
|
* protected members.
|
||||||
|
*
|
||||||
|
* @type {Object.<number, Object>}
|
||||||
|
*/
|
||||||
|
var class_instance = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a class, inheriting either from the provided base class or the
|
* Creates a class, inheriting either from the provided base class or the
|
||||||
|
@ -188,7 +198,8 @@ function Class() {};
|
||||||
*/
|
*/
|
||||||
var extend = ( function( extending )
|
var extend = ( function( extending )
|
||||||
{
|
{
|
||||||
var class_id = 0;
|
var class_id = 0,
|
||||||
|
instance_id = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mimics class inheritance
|
* Mimics class inheritance
|
||||||
|
@ -215,8 +226,8 @@ var extend = ( function( extending )
|
||||||
hasOwn = Array.prototype.hasOwnProperty;
|
hasOwn = Array.prototype.hasOwnProperty;
|
||||||
|
|
||||||
var properties = {},
|
var properties = {},
|
||||||
members = member_builder.initMembers( prototype ),
|
|
||||||
prop_init = member_builder.initMembers(),
|
prop_init = member_builder.initMembers(),
|
||||||
|
members = member_builder.initMembers( prototype ),
|
||||||
|
|
||||||
abstract_methods =
|
abstract_methods =
|
||||||
util.clone( getMeta( base.__cid ).abstractMethods )
|
util.clone( getMeta( base.__cid ).abstractMethods )
|
||||||
|
@ -261,7 +272,7 @@ var extend = ( function( extending )
|
||||||
method: function( name, func, is_abstract, keywords )
|
method: function( name, func, is_abstract, keywords )
|
||||||
{
|
{
|
||||||
member_builder.buildMethod(
|
member_builder.buildMethod(
|
||||||
members, null, name, func, keywords
|
members, null, name, func, keywords, getMethodInstance
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( is_abstract )
|
if ( is_abstract )
|
||||||
|
@ -294,7 +305,7 @@ 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 );
|
||||||
|
|
||||||
attachPropInit( prototype, prop_init );
|
attachPropInit( prototype, prop_init, members );
|
||||||
|
|
||||||
new_class.prototype = prototype;
|
new_class.prototype = prototype;
|
||||||
new_class.constructor = new_class;
|
new_class.constructor = new_class;
|
||||||
|
@ -334,6 +345,7 @@ var extend = ( function( extending )
|
||||||
{
|
{
|
||||||
var args = null;
|
var args = null;
|
||||||
|
|
||||||
|
// constructor function to be returned
|
||||||
var __self = function()
|
var __self = function()
|
||||||
{
|
{
|
||||||
if ( !( this instanceof __self ) )
|
if ( !( this instanceof __self ) )
|
||||||
|
@ -344,6 +356,10 @@ var extend = ( function( extending )
|
||||||
return new __self();
|
return new __self();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate and store unique instance id
|
||||||
|
attachInstanceId( this, ++instance_id, __self );
|
||||||
|
|
||||||
|
initInstance( instance_id, this );
|
||||||
this.__initProps();
|
this.__initProps();
|
||||||
|
|
||||||
// call the constructor, if one was provided
|
// call the constructor, if one was provided
|
||||||
|
@ -455,6 +471,29 @@ function setupProps( func, abstract_methods, class_id )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes class instance
|
||||||
|
*
|
||||||
|
* This process will create the instance visibility object containing private
|
||||||
|
* and protected members. The class instance is part of the prototype chain.
|
||||||
|
* This will be passed to all methods when invoked, permitting them to access
|
||||||
|
* the private and protected members while keeping them encapsulated.
|
||||||
|
*
|
||||||
|
* @param {number} iid instance id
|
||||||
|
* @param {Object} instance instance to initialize
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function initInstance( iid, instance )
|
||||||
|
{
|
||||||
|
var prot = function() {};
|
||||||
|
prot.prototype = instance;
|
||||||
|
|
||||||
|
// add the visibility objects to the data object for this class instance
|
||||||
|
class_instance[ iid ] = new prot();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches __initProps() method to the class prototype
|
* Attaches __initProps() method to the class prototype
|
||||||
*
|
*
|
||||||
|
@ -469,14 +508,20 @@ function setupProps( func, abstract_methods, class_id )
|
||||||
* @param {Object} prototype prototype to attach method to
|
* @param {Object} prototype prototype to attach method to
|
||||||
* @param {Object} properties properties to initialize
|
* @param {Object} properties properties to initialize
|
||||||
*
|
*
|
||||||
|
* @param {{public: Object, protected: Object, private: Object}} members
|
||||||
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function attachPropInit( prototype, properties )
|
function attachPropInit( prototype, properties, members )
|
||||||
{
|
{
|
||||||
var prop_pub = properties[ 'public' ];
|
var prop_pub = properties[ 'public' ],
|
||||||
|
prop_prot = properties[ 'protected' ]
|
||||||
|
;
|
||||||
|
|
||||||
util.defineSecureProp( prototype, '__initProps', function()
|
util.defineSecureProp( prototype, '__initProps', function()
|
||||||
{
|
{
|
||||||
|
var inst_props = class_instance[ this.__iid ];
|
||||||
|
|
||||||
// first initialize the parent's properties, so that ours will overwrite
|
// first initialize the parent's properties, so that ours will overwrite
|
||||||
// them
|
// them
|
||||||
var parent_init = prototype.parent.__initProps;
|
var parent_init = prototype.parent.__initProps;
|
||||||
|
@ -493,6 +538,25 @@ function attachPropInit( prototype, properties )
|
||||||
// not share references (and therefore, data)
|
// not share references (and therefore, data)
|
||||||
this[ prop ] = util.clone( prop_pub[ prop ] );
|
this[ prop ] = util.clone( prop_pub[ prop ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var methods_protected = members[ 'protected' ],
|
||||||
|
hasOwn = Array.prototype.hasOwnProperty
|
||||||
|
;
|
||||||
|
|
||||||
|
// copy over the methods
|
||||||
|
for ( method_name in methods_protected )
|
||||||
|
{
|
||||||
|
if ( hasOwn.call( methods_protected, method_name ) )
|
||||||
|
{
|
||||||
|
inst_props[ method_name ] = methods_protected[ method_name ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize protected properties and store in instance data
|
||||||
|
for ( prop in prop_prot )
|
||||||
|
{
|
||||||
|
inst_props[ prop ] = util.clone( prop_prot[ prop ] );
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,6 +650,20 @@ function attachId( func, id )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches an instance identifier to a class instance
|
||||||
|
*
|
||||||
|
* @param {Object} instance class instance
|
||||||
|
* @param {number} iid instance id
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function attachInstanceId( instance, iid )
|
||||||
|
{
|
||||||
|
util.defineSecureProp( instance, '__iid', iid );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches partially applied isInstanceOf() method to class instance
|
* Attaches partially applied isInstanceOf() method to class instance
|
||||||
*
|
*
|
||||||
|
@ -615,7 +693,7 @@ function attachInstanceOf( instance )
|
||||||
function createMeta( func, parent_id )
|
function createMeta( func, parent_id )
|
||||||
{
|
{
|
||||||
var id = func.__cid,
|
var id = func.__cid,
|
||||||
parent_meta = ( ( parent_id ) ? getMeta( parent_id) : undefined );
|
parent_meta = ( ( parent_id ) ? getMeta( parent_id ) : undefined );
|
||||||
|
|
||||||
// copy the parent prototype's metadata if it exists (inherit metadata)
|
// copy the parent prototype's metadata if it exists (inherit metadata)
|
||||||
if ( parent_meta )
|
if ( parent_meta )
|
||||||
|
@ -649,3 +727,26 @@ function getMeta( id )
|
||||||
return class_meta[ id ] || {};
|
return class_meta[ id ] || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instance object associated with the given method
|
||||||
|
*
|
||||||
|
* The instance object contains the protected and private members. This object
|
||||||
|
* can be passed as the context when calling a method in order to give that
|
||||||
|
* method access to those members.
|
||||||
|
*
|
||||||
|
* @param {function()} method method to look up instance object for
|
||||||
|
*
|
||||||
|
* @return {Object,null} instance object if found, otherwise null
|
||||||
|
*/
|
||||||
|
function getMethodInstance( method )
|
||||||
|
{
|
||||||
|
var iid = method.__iid,
|
||||||
|
data = class_instance[ method.__iid ];
|
||||||
|
|
||||||
|
return ( iid && data )
|
||||||
|
? data
|
||||||
|
: null
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,10 +59,15 @@ exports.initMembers = function( mpublic, mprotected, mprivate )
|
||||||
* @param {*} value property value
|
* @param {*} value property value
|
||||||
*
|
*
|
||||||
* @param {Object.<string,boolean>} keywords parsed keywords
|
* @param {Object.<string,boolean>} keywords parsed keywords
|
||||||
|
|
||||||
|
* @param {Object=} instCallback function to call in order to retrieve
|
||||||
|
* object to bind 'this' keyword to
|
||||||
*
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
exports.buildMethod = function( members, meta, name, value, keywords, cmp )
|
exports.buildMethod = function(
|
||||||
|
members, meta, name, value, keywords, instCallback
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var prev;
|
var prev;
|
||||||
|
|
||||||
|
@ -104,12 +109,18 @@ exports.buildMethod = function( members, meta, name, value, keywords, cmp )
|
||||||
if ( prev )
|
if ( prev )
|
||||||
{
|
{
|
||||||
// override the method
|
// override the method
|
||||||
dest[ name ] = overrideMethod( prev, value );
|
dest[ name ] = overrideMethod( prev, value, instCallback );
|
||||||
|
}
|
||||||
|
else if ( keywords[ 'abstract' ] )
|
||||||
|
{
|
||||||
|
// we do not want to wrap abstract methods, since they are not callable
|
||||||
|
dest[ name ] = value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// we are not overriding the method, so simply copy it over
|
// we are not overriding the method, so simply copy it over, wrapping it
|
||||||
dest[ name ] = value;
|
// to ensure privileged calls will work properly
|
||||||
|
dest[ name ] = overrideMethod( value, null, instCallback );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,24 +281,42 @@ function scanMembers( members, name, cmp )
|
||||||
* @param {function()} super_method method to override
|
* @param {function()} super_method method to override
|
||||||
* @param {function()} new_method method to override with
|
* @param {function()} new_method method to override with
|
||||||
*
|
*
|
||||||
|
* @param {Object=} instCallback function to call in order to retrieve
|
||||||
|
* object to bind 'this' keyword to
|
||||||
|
*
|
||||||
* @return {function()} override method
|
* @return {function()} override method
|
||||||
*/
|
*/
|
||||||
function overrideMethod( super_method, new_method )
|
function overrideMethod( super_method, new_method, instCallback )
|
||||||
{
|
{
|
||||||
|
instCallback = instCallback || function() {};
|
||||||
|
|
||||||
// return a function that permits referencing the super method via the
|
// return a function that permits referencing the super method via the
|
||||||
// __super property
|
// __super property
|
||||||
var override = function()
|
var override = null;
|
||||||
|
|
||||||
|
if ( new_method )
|
||||||
{
|
{
|
||||||
var tmp = this.__super;
|
override = function()
|
||||||
|
{
|
||||||
|
// the _super property will contain the parent method
|
||||||
|
this.__super = super_method;
|
||||||
|
|
||||||
// assign _super temporarily for the method invocation so
|
var retval = new_method.apply(
|
||||||
// that the method can call the parent method
|
( instCallback( this ) || this ), arguments
|
||||||
this.__super = super_method;
|
);
|
||||||
var retval = new_method.apply( this, arguments );
|
|
||||||
this.__super = tmp;
|
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
override = function()
|
||||||
|
{
|
||||||
|
return super_method.apply(
|
||||||
|
( instCallback( this ) || this ), arguments
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// This is a trick to work around the fact that we cannot set the length
|
// This is a trick to work around the fact that we cannot set the length
|
||||||
// property of a function. Instead, we define our own property - __length.
|
// property of a function. Instead, we define our own property - __length.
|
||||||
|
|
|
@ -35,7 +35,7 @@ exports.buildMember = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partially applied function to quickly build properties using common test data
|
* Quickly build properties using common test data
|
||||||
*/
|
*/
|
||||||
exports.buildMemberQuick = function( keywords, preserve_prior )
|
exports.buildMemberQuick = function( keywords, preserve_prior )
|
||||||
{
|
{
|
||||||
|
@ -66,16 +66,25 @@ exports.assertOnlyVisibility = function( vis, name, value, message )
|
||||||
var check = [ 'public', 'protected', 'private' ],
|
var check = [ 'public', 'protected', 'private' ],
|
||||||
i = check.length,
|
i = check.length,
|
||||||
visi = '',
|
visi = '',
|
||||||
|
value,
|
||||||
cmp;
|
cmp;
|
||||||
|
|
||||||
// forEach not used for pre-ES5 browser support
|
// forEach not used for pre-ES5 browser support
|
||||||
while ( i-- )
|
while ( i-- )
|
||||||
{
|
{
|
||||||
visi = check[ i ];
|
visi = check[ i ];
|
||||||
cmp = ( visi === vis ) ? value : undefined;
|
value = exports.members[ visi ][ name ];
|
||||||
|
cmp = ( visi === vis ) ? value : undefined;
|
||||||
|
|
||||||
|
// are we comparing functions?
|
||||||
|
if ( cmp && exports.funcVal )
|
||||||
|
{
|
||||||
|
cmp = exports.funcVal;
|
||||||
|
value = value();
|
||||||
|
}
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
exports.members[ visi ][ name ],
|
value,
|
||||||
cmp,
|
cmp,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,22 +29,21 @@ var common = require( './common' ),
|
||||||
// these two variables are declared outside of the class to ensure that they
|
// these two variables are declared outside of the class to ensure that they
|
||||||
// will still be set even if the context of the constructor is wrong
|
// will still be set even if the context of the constructor is wrong
|
||||||
var construct_count = 0,
|
var construct_count = 0,
|
||||||
construct_context = null;
|
construct_context = null,
|
||||||
|
construct_args = null
|
||||||
|
|
||||||
// create a basic test class
|
// create a basic test class
|
||||||
var Foo = Class.extend(
|
Foo = Class.extend(
|
||||||
{
|
|
||||||
args: null,
|
|
||||||
|
|
||||||
|
|
||||||
__construct: function()
|
|
||||||
{
|
{
|
||||||
construct_count++;
|
__construct: function()
|
||||||
construct_context = this;
|
{
|
||||||
|
construct_count++;
|
||||||
|
construct_context = this;
|
||||||
|
construct_args = arguments;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
this.args = arguments;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( Foo.prototype.__construct instanceof Function ),
|
( Foo.prototype.__construct instanceof Function ),
|
||||||
|
@ -67,19 +66,19 @@ assert.equal(
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
obj,
|
construct_context.__iid,
|
||||||
construct_context,
|
obj.__iid,
|
||||||
"Constructor should be invoked within the context of the class instance"
|
"Constructor should be invoked within the context of the class instance"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.notEqual(
|
assert.notEqual(
|
||||||
obj.args,
|
construct_args,
|
||||||
null,
|
null,
|
||||||
"Constructor arguments should be passed to the constructor"
|
"Constructor arguments should be passed to the constructor"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
obj.args.length,
|
construct_args.length,
|
||||||
args.length,
|
args.length,
|
||||||
"All arguments should be passed to the constructor"
|
"All arguments should be passed to the constructor"
|
||||||
);
|
);
|
||||||
|
@ -88,7 +87,7 @@ assert.equal(
|
||||||
for ( var i = 0, len = args.length; i < len; i++ )
|
for ( var i = 0, len = args.length; i < len; i++ )
|
||||||
{
|
{
|
||||||
assert.equal(
|
assert.equal(
|
||||||
obj.args[ i ],
|
construct_args[ i ],
|
||||||
args[ i ],
|
args[ i ],
|
||||||
"Arguments should be passed to the constructor: " + i
|
"Arguments should be passed to the constructor: " + i
|
||||||
);
|
);
|
||||||
|
@ -112,16 +111,16 @@ assert.equal(
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
construct_context,
|
construct_context.__iid,
|
||||||
subobj,
|
subobj.__iid,
|
||||||
"Parent constructor is run in context of the subtype"
|
"Parent constructor is run in context of the subtype"
|
||||||
);
|
);
|
||||||
|
|
||||||
// this should be implied by the previous test, but let's add it for some peace
|
// this should be implied by the previous test, but let's add it for some peace
|
||||||
// of mind
|
// of mind
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( ( subobj.args[ 0 ] === args2[ 0 ] )
|
( ( construct_args[ 0 ] === args2[ 0 ] )
|
||||||
&& ( subobj.args[ 1 ] == args2[ 1 ] )
|
&& ( construct_args[ 1 ] == args2[ 1 ] )
|
||||||
),
|
),
|
||||||
"Parent constructor sets values on subtype"
|
"Parent constructor sets values on subtype"
|
||||||
);
|
);
|
||||||
|
@ -135,14 +134,14 @@ assert.ok(
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
construct_context,
|
construct_context.__iid,
|
||||||
subobj2,
|
subobj2.__iid,
|
||||||
"Self-invoking constructor is run in the context of the new object"
|
"Self-invoking constructor is run in the context of the new object"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( ( subobj2.args[ 0 ] === args2[ 0 ] )
|
( ( construct_args[ 0 ] === args2[ 0 ] )
|
||||||
&& ( subobj2.args[ 1 ] == args2[ 1 ] )
|
&& ( construct_args[ 1 ] == args2[ 1 ] )
|
||||||
),
|
),
|
||||||
"Self-invoking constructor receives arguments"
|
"Self-invoking constructor receives arguments"
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,9 +30,9 @@ var common = require( './common' ),
|
||||||
prot = 'bar',
|
prot = 'bar',
|
||||||
priv = 'baz',
|
priv = 'baz',
|
||||||
|
|
||||||
pubf = function() {},
|
pubf = function() { return pub; },
|
||||||
protf = function() {},
|
protf = function() { return prot; },
|
||||||
privf = function() {},
|
privf = function() { return priv; },
|
||||||
|
|
||||||
// new anonymous class instance
|
// new anonymous class instance
|
||||||
foo = Class.extend( {
|
foo = Class.extend( {
|
||||||
|
@ -43,6 +43,13 @@ var common = require( './common' ),
|
||||||
'public pubf': pubf,
|
'public pubf': pubf,
|
||||||
'protected protf': protf,
|
'protected protf': protf,
|
||||||
'private privf': privf,
|
'private privf': privf,
|
||||||
|
|
||||||
|
'public getProp': function( name )
|
||||||
|
{
|
||||||
|
// return property, allowing us to break encapsulation for
|
||||||
|
// protected/private properties (for testing purposes)
|
||||||
|
return this[ name ];
|
||||||
|
},
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,8 +62,8 @@ var common = require( './common' ),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
foo.pubf,
|
foo.pubf(),
|
||||||
pubf,
|
pub,
|
||||||
"Public methods are accessible via public interface"
|
"Public methods are accessible via public interface"
|
||||||
);
|
);
|
||||||
} )();
|
} )();
|
||||||
|
@ -89,3 +96,24 @@ var common = require( './common' ),
|
||||||
);
|
);
|
||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected members should be accessible from within class methods
|
||||||
|
*/
|
||||||
|
( function testProtectedMembersAreAccessibleInternally()
|
||||||
|
{
|
||||||
|
assert.equal(
|
||||||
|
foo.getProp( 'peeps' ),
|
||||||
|
prot,
|
||||||
|
"Protected properties are available internally"
|
||||||
|
);
|
||||||
|
|
||||||
|
// invoke rather than checking for equality, because the method may be
|
||||||
|
// wrapped
|
||||||
|
assert.equal(
|
||||||
|
foo.getProp( 'protf' )(),
|
||||||
|
prot,
|
||||||
|
"Protected methods are available internally"
|
||||||
|
);
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ var common = require( './common' ),
|
||||||
mb_common = require( './inc-member_builder-common' )
|
mb_common = require( './inc-member_builder-common' )
|
||||||
;
|
;
|
||||||
|
|
||||||
mb_common.value = function() {};
|
mb_common.funcVal = 'foobar';
|
||||||
|
mb_common.value = function() { return mb_common.funcVal; };
|
||||||
mb_common.buildMember = common.require( 'member_builder' ).buildMethod;
|
mb_common.buildMember = common.require( 'member_builder' ).buildMethod;
|
||||||
|
|
||||||
// do assertions common to all member builders
|
// do assertions common to all member builders
|
||||||
|
@ -50,7 +51,8 @@ mb_common.assertCommon();
|
||||||
*/
|
*/
|
||||||
( function testCannotOverridePropertyWithMethod()
|
( function testCannotOverridePropertyWithMethod()
|
||||||
{
|
{
|
||||||
mb_common.value = 'moofoo';
|
mb_common.value = 'moofoo';
|
||||||
|
mb_common.funcVal = undefined;
|
||||||
mb_common.buildMemberQuick();
|
mb_common.buildMemberQuick();
|
||||||
|
|
||||||
assert.throws( function()
|
assert.throws( function()
|
||||||
|
@ -151,3 +153,74 @@ mb_common.assertCommon();
|
||||||
}, TypeError, "Cannot override concrete method with abstract method" );
|
}, TypeError, "Cannot override concrete method with abstract method" );
|
||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of the powerful features of the method builder is the ability to pass in
|
||||||
|
* an instance to be bound to 'this' when invoking a method. This has some
|
||||||
|
* important consequences, such as the ability to implement protected/private
|
||||||
|
* members.
|
||||||
|
*/
|
||||||
|
( function testMethodInvocationBindsThisToPassedInstance()
|
||||||
|
{
|
||||||
|
var instance = function() {},
|
||||||
|
val = 'fooboo',
|
||||||
|
val2 = 'fooboo2',
|
||||||
|
iid = 1,
|
||||||
|
|
||||||
|
func = function()
|
||||||
|
{
|
||||||
|
return this.foo;
|
||||||
|
},
|
||||||
|
|
||||||
|
func2 = function()
|
||||||
|
{
|
||||||
|
return this.foo2;
|
||||||
|
},
|
||||||
|
|
||||||
|
called = false,
|
||||||
|
instCallback = function()
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
return instance;
|
||||||
|
},
|
||||||
|
|
||||||
|
members = { 'public': {}, 'protected': {}, 'private': {} }
|
||||||
|
;
|
||||||
|
|
||||||
|
// set instance values
|
||||||
|
instance.foo = val;
|
||||||
|
instance.foo2 = val2;
|
||||||
|
|
||||||
|
// concrete method
|
||||||
|
mb_common.buildMember(
|
||||||
|
members,
|
||||||
|
exports.meta,
|
||||||
|
'func',
|
||||||
|
func,
|
||||||
|
[ 'public' ],
|
||||||
|
instCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
members[ 'public' ].func(),
|
||||||
|
val,
|
||||||
|
"Calling method will bind 'this' to passed instance"
|
||||||
|
);
|
||||||
|
|
||||||
|
// override method
|
||||||
|
mb_common.buildMember(
|
||||||
|
members,
|
||||||
|
exports.meta,
|
||||||
|
'func',
|
||||||
|
func2,
|
||||||
|
[ 'public' ],
|
||||||
|
instCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
members[ 'public' ].func(),
|
||||||
|
val2,
|
||||||
|
"Calling method override will bind 'this' to passed instance"
|
||||||
|
);
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue