diff --git a/README.md b/README.md index 358259d..d82324c 100755 --- a/README.md +++ b/README.md @@ -25,10 +25,13 @@ Disadvantages compared to the `class` syntax: - Slightly more complicated calling of `parent` (_super_) methods +There are some other limitations to this currently, for more info see +the [relevant section](#limitations). + ## Installation -```bash +```shell $ npm install ig-object ``` @@ -59,9 +62,9 @@ we simply need to _link_ the prototypes of two constructors via `.__proto__`, `Object.create(..)` or other means. ```javascript -var B = object.Constructor('B', {__proto__: A.prototype}) +var B = object.Constructor('B', { __extends__: A }) -var C = object.Constructor('C', Object.create(B.prototype)) +var C = object.Constructor('C', B, {}) ``` Now we can test this... @@ -96,7 +99,7 @@ var Base = object.Constructor('Base', { var Item = object.Constructor('Item', { // inherit from Base... - __proto__: Base.prototype, + __extends__: Base, __init__: function(){ // call the "super" method... @@ -106,6 +109,9 @@ var Item = object.Constructor('Item', { }, }) +var SubItem = object.Constructor('SubItem', Item, { + // ... +}) ``` @@ -152,6 +158,14 @@ If the prototype is explicitly defined as a function then it is the user's responsibility to call `.__call__(..)` method. +**Notes:** +- the two approaches (_function_ vs. `.__call__(..)`) will produce + slightly different results, the difference is in `.prototype`, in the + first case it is a _function_ while in the second an object with a + `.__call__(..)` method. + (this may change in the future) + + ## Advanced usage @@ -198,60 +212,50 @@ handling. ### Extending the constructor -The `constructor.__proto__` should be callable, _object.js_ will by design -make no effort to either maintain nor test for this. - ```javascript -var D = object.Constructor('D', - object.mixinFlat(function(){}, { - constructor_attr: 'some value', - +var C = object.Constructor('C', + // this will get mixed into the constructor C... + { + constructor_attr: 123, + constructorMethod: function(){ // ... }, // ... - }), - { - instance_attr: 'some other value', - + }, { instanceMethod: function(){ + // get constructor data... var x = this.constructor.constructor_attr // ... }, - // ... }) ``` -Keeping the class prototype a function is not necessary, but not doing -so will break the `D instanceof Function` test. - -Here is another less strict approach but here `D.__proto__` will not be -callable: +And the same thing while extending... ```javascript -var D = object.Constructor('D', +var D = object.Constructor('D', + // this will get mixed into C(..)... { - __proto__: Function, + __extends__: C, // ... - }, - { + }, { // ... }) ``` -Passing a simple object as a constructor prototype will work too, but -will neither pass the `D instanceof Function` test nor be callable and -thus is not recommended. +Note that `.__extends__` can be written in either block, this is done +for convenience and to keep it as close as possible to the definition top. ### Inheriting from native constructor objects ```javascript -var myArray = object.Constructor('myArray', Array, { - __proto__: Array.prototype, +var myArray = object.Constructor('myArray', { + __extends__: Array, // ... }) @@ -271,8 +275,8 @@ Extending `.constructor(..)` is not necessary in most cases as replacement. ```javascript -var myArray = object.Constructor('myArray', Array, { - __proto__: Array.prototype, +var myArray = object.Constructor('myArray', { + __extends__: Array, __new__: function(context, ...args){ var obj = Reflect.construct(myArray.__proto__, args, myArray) @@ -363,7 +367,8 @@ Define an object constructor ``` Constructor() Constructor(, ) -Constructor(, , ) +Constructor(, , ) +Constructor(, , ) -> ``` @@ -375,6 +380,29 @@ C(, ..) ``` +## Limitations + +### Can not mix unrelated native types directly + +At this point we can't mix native types, i.e. it is not possible to make +a callable `Array`... + +For example this will produce a broken instance: +```javascript +var CallablaArray = object.Constructor('CallablaArray', Array, function(){ .. }) +``` + +This will produce a broken instance in a different way: +```javascript +var CallablaArray = object.Constructor('CallablaArray', Array, { + __call__: function(){ .. }, +}) +``` + +Some of this is due to how _object.js_ is currently implemented, this +needs further investigation... + + ## Utilities Align text to shortest leading whitespace diff --git a/object.js b/object.js index d400f26..6c9e278 100755 --- a/object.js +++ b/object.js @@ -322,6 +322,14 @@ function(root, ...objects){ // unneccessary restrictions both on the "class" object and on the // instance... // +// XXX the following are not the same: +// var C = Constructor('C', function(){ .. }) +// and +// var C2 = Constructor('C2', { __call__: function(){ .. } }) +// the difference is in C.prototype vs. C2.prototype, the first +// being a function while the second is an object with a call +// method... +// Q: should the two cases produce the same result??? // XXX Q: should the context (this) in .__new__(..) be _constructor or // .prototype??? // ... .prototype seems to be needed more often but through it we @@ -373,12 +381,8 @@ function(context, constructor, ...args){ : Reflect.construct(Object, [], constructor) // link to prototype chain, if not done already... - if(obj.__proto__ !== constructor.prototype){ - obj.__proto__ = constructor.prototype - Object.defineProperty(obj, 'constructor', { - value: constructor, - enumerable: false, - }) } + obj.__proto__ !== constructor.prototype + && (obj.__proto__ = constructor.prototype) return obj } @@ -389,12 +393,13 @@ function(context, constructor, ...args){ // Constructor(name, proto) // -> constructor // -// Make a constructor with a prototype (object/function) and a class -// prototype... -// Constructor(name, class-proto, proto) +// Make a constructor with a prototype and a constructor prototype... +// Constructor(name, constructor-mixin, proto) +// -> constructor +// +// Make a constructor with prototype extending parent-constructor... +// Constructor(name, parent-constructor, proto) // -> constructor -// NOTE: the defines a set of class methods and -// attributes. // // // The resulting constructor can produce objects in one of these ways: @@ -432,6 +437,16 @@ function(context, constructor, ...args){ // // // +// Special attributes: +// .__extends__ +// Shorthand to define define the prototype constructor. +// Constructor('X', {__extends__: Y}) +// is the same as: +// Constructor('X', Y, {}) +// This can be defined on either the prototype or the constructor +// mixin but not on both. +// +// // Special methods (constructor): // // Handle uninitialized instance construction @@ -469,13 +484,9 @@ function(context, constructor, ...args){ // // NOTE: new is optional... // var A = new Constructor('A') // -// // NOTE: in a prototype chain the prototypes are "inherited" -// // NOTE: JS has no classes and the prototype is just another -// // object, the only difference is that it's used by the -// // constructor to link other objects i.e. "instances" to... -// var B = Constructor('B', {__proto__: A.prototype}) +// var B = Constructor('B', { __extends__: A }) // -// var C = Constructor('C', Objec.create(B.prototype)) +// var C = Constructor('C', B, {}) // // var c = C() // @@ -510,6 +521,9 @@ function(context, constructor, ...args){ // NOTE: to disable .__rawinstance__(..) handling set it to false in the // class prototype... // +// XXX BUG: +// // this does not make a callable array... +// X = Constructor('X', Array, function(){}) // XXX revise .toString(..) definition test... var Constructor = module.Constructor = @@ -518,9 +532,43 @@ module.C = function Constructor(name, a, b){ var proto = b == null ? a : b proto = proto || {} - var cls_proto = b == null ? b : a + var constructor_mixin = b == null ? b : a + var constructor_proto - // the actual constructor... + // handle: + // Constructor(name, constructor, { .. }) + if(constructor_mixin instanceof Function){ + constructor_proto = constructor_mixin + constructor_mixin = null + proto.__proto__ === ({}).__proto__ + && (proto.__proto__ = constructor_proto.prototype) + + // handle: + // Constructor(name, { .. }) + // Constructor(name, { .. }, { .. }) + } else { + // handle .__extends__ + a = Object.hasOwnProperty.call(proto, '__extends__') + && proto.__extends__ + b = constructor_mixin != null + && Object.hasOwnProperty.call(constructor_mixin, '__extends__') + && constructor_mixin.__extends__ + // sanity check... + if(!!a && !!b){ + throw new Error('Constructor(..): ' + +'only one of prototype.__extends__ or constructor.__extends__ ' + +'can exist.') } + constructor_proto = !!a ? a : b + // cleanup... + if(!!b){ + constructor_mixin = mixinFlat({}, constructor_mixin) + delete constructor_mixin.__extends__ } + !!constructor_proto + && (proto.__proto__ = constructor_proto.prototype) + } + + + // the constructor base... var _constructor = function Constructor(){ // create raw instance... var obj = _constructor.__rawinstance__ ? @@ -543,8 +591,8 @@ function Constructor(name, a, b){ // set .toString(..)... // NOTE: do this only if .toString(..) is not defined by user... // XXX revise this test... - ;((cls_proto || {}).toString === Function.toString - || (cls_proto || {}).toString === ({}).toString) + ;((constructor_mixin || {}).toString === Function.toString + || (constructor_mixin || {}).toString === ({}).toString) && Object.defineProperty(_constructor, 'toString', { value: function(){ var args = proto.__init__ ? @@ -561,21 +609,24 @@ function Constructor(name, a, b){ return `${this.name}(${args})${normalizeIndent(code)}` }, enumerable: false, }) - _constructor.__proto__ = cls_proto === undefined ? - _constructor.__proto__ - : cls_proto - _constructor.prototype = proto // set generic raw instance constructor... _constructor.__rawinstance__ instanceof Function || (_constructor.__rawinstance__ = function(context, ...args){ return makeRawInstance(context, this, ...args) }) + !!constructor_proto + && (_constructor.__proto__ = constructor_proto) + _constructor.prototype = proto + // NOTE: this is intentionally last, this enables the user to override + // any of the system methods... + // NOTE: place the non-overridable definitions after this... + !!constructor_mixin + && mixinFlat( + _constructor, + constructor_mixin) // set constructor.prototype.constructor - Object.defineProperty(_constructor.prototype, 'constructor', { - value: _constructor, - enumerable: false, - }) + _constructor.prototype.constructor = _constructor return _constructor } diff --git a/package.json b/package.json index 541ec95..af9d7d9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ig-object", - "version": "2.7.2", + "version": "3.0.0", "description": "", "main": "object.js", "scripts": {