major semantic change, should not break anything -- really odd...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-05-03 03:03:18 +03:00
parent 4822e39ed7
commit b57759d40f
3 changed files with 142 additions and 63 deletions

View File

@ -25,10 +25,13 @@ Disadvantages compared to the `class` syntax:
- Slightly more complicated calling of `parent` (_super_) methods - 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 ## Installation
```bash ```shell
$ npm install ig-object $ 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. `Object.create(..)` or other means.
```javascript ```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... Now we can test this...
@ -96,7 +99,7 @@ var Base = object.Constructor('Base', {
var Item = object.Constructor('Item', { var Item = object.Constructor('Item', {
// inherit from Base... // inherit from Base...
__proto__: Base.prototype, __extends__: Base,
__init__: function(){ __init__: function(){
// call the "super" method... // 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. 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 ## Advanced usage
@ -198,60 +212,50 @@ handling.
### Extending the constructor ### 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 ```javascript
var D = object.Constructor('D', var C = object.Constructor('C',
object.mixinFlat(function(){}, { // this will get mixed into the constructor C...
constructor_attr: 'some value', {
constructor_attr: 123,
constructorMethod: function(){ constructorMethod: function(){
// ... // ...
}, },
// ... // ...
}), }, {
{
instance_attr: 'some other value',
instanceMethod: function(){ instanceMethod: function(){
// get constructor data...
var x = this.constructor.constructor_attr var x = this.constructor.constructor_attr
// ... // ...
}, },
// ... // ...
}) })
``` ```
Keeping the class prototype a function is not necessary, but not doing And the same thing while extending...
so will break the `D instanceof Function` test.
Here is another less strict approach but here `D.__proto__` will not be
callable:
```javascript ```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 Note that `.__extends__` can be written in either block, this is done
will neither pass the `D instanceof Function` test nor be callable and for convenience and to keep it as close as possible to the definition top.
thus is not recommended.
### Inheriting from native constructor objects ### Inheriting from native constructor objects
```javascript ```javascript
var myArray = object.Constructor('myArray', Array, { var myArray = object.Constructor('myArray', {
__proto__: Array.prototype, __extends__: Array,
// ... // ...
}) })
@ -271,8 +275,8 @@ Extending `.constructor(..)` is not necessary in most cases as
replacement. replacement.
```javascript ```javascript
var myArray = object.Constructor('myArray', Array, { var myArray = object.Constructor('myArray', {
__proto__: Array.prototype, __extends__: Array,
__new__: function(context, ...args){ __new__: function(context, ...args){
var obj = Reflect.construct(myArray.__proto__, args, myArray) var obj = Reflect.construct(myArray.__proto__, args, myArray)
@ -363,7 +367,8 @@ Define an object constructor
``` ```
Constructor(<name>) Constructor(<name>)
Constructor(<name>, <prototype>) Constructor(<name>, <prototype>)
Constructor(<name>, <class-prototype>, <prototype>) Constructor(<name>, <parent-constructor>, <prototype>)
Constructor(<name>, <constructor-mixin>, <prototype>)
-> <constructor> -> <constructor>
``` ```
@ -375,6 +380,29 @@ C(<name>, ..)
``` ```
## 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 ## Utilities
Align text to shortest leading whitespace Align text to shortest leading whitespace

109
object.js
View File

@ -322,6 +322,14 @@ function(root, ...objects){
// unneccessary restrictions both on the "class" object and on the // unneccessary restrictions both on the "class" object and on the
// instance... // 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 // XXX Q: should the context (this) in .__new__(..) be _constructor or
// .prototype??? // .prototype???
// ... .prototype seems to be needed more often but through it we // ... .prototype seems to be needed more often but through it we
@ -373,12 +381,8 @@ function(context, constructor, ...args){
: Reflect.construct(Object, [], constructor) : Reflect.construct(Object, [], constructor)
// link to prototype chain, if not done already... // link to prototype chain, if not done already...
if(obj.__proto__ !== constructor.prototype){ obj.__proto__ !== constructor.prototype
obj.__proto__ = constructor.prototype && (obj.__proto__ = constructor.prototype)
Object.defineProperty(obj, 'constructor', {
value: constructor,
enumerable: false,
}) }
return obj } return obj }
@ -389,12 +393,13 @@ function(context, constructor, ...args){
// Constructor(name, proto) // Constructor(name, proto)
// -> constructor // -> constructor
// //
// Make a constructor with a prototype (object/function) and a class // Make a constructor with a prototype and a constructor prototype...
// prototype... // Constructor(name, constructor-mixin, proto)
// Constructor(name, class-proto, proto) // -> constructor
//
// Make a constructor with prototype extending parent-constructor...
// Constructor(name, parent-constructor, proto)
// -> constructor // -> constructor
// NOTE: the <class-proto> defines a set of class methods and
// attributes.
// //
// //
// The resulting constructor can produce objects in one of these ways: // 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): // Special methods (constructor):
// //
// Handle uninitialized instance construction // Handle uninitialized instance construction
@ -469,13 +484,9 @@ function(context, constructor, ...args){
// // NOTE: new is optional... // // NOTE: new is optional...
// var A = new Constructor('A') // var A = new Constructor('A')
// //
// // NOTE: in a prototype chain the prototypes are "inherited" // var B = Constructor('B', { __extends__: A })
// // 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 C = Constructor('C', Objec.create(B.prototype)) // var C = Constructor('C', B, {})
// //
// var c = C() // var c = C()
// //
@ -510,6 +521,9 @@ function(context, constructor, ...args){
// NOTE: to disable .__rawinstance__(..) handling set it to false in the // NOTE: to disable .__rawinstance__(..) handling set it to false in the
// class prototype... // class prototype...
// //
// XXX BUG:
// // this does not make a callable array...
// X = Constructor('X', Array, function(){})
// XXX revise .toString(..) definition test... // XXX revise .toString(..) definition test...
var Constructor = var Constructor =
module.Constructor = module.Constructor =
@ -518,9 +532,43 @@ module.C =
function Constructor(name, a, b){ function Constructor(name, a, b){
var proto = b == null ? a : b var proto = b == null ? a : b
proto = proto || {} 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(){ var _constructor = function Constructor(){
// create raw instance... // create raw instance...
var obj = _constructor.__rawinstance__ ? var obj = _constructor.__rawinstance__ ?
@ -543,8 +591,8 @@ function Constructor(name, a, b){
// set .toString(..)... // set .toString(..)...
// NOTE: do this only if .toString(..) is not defined by user... // NOTE: do this only if .toString(..) is not defined by user...
// XXX revise this test... // XXX revise this test...
;((cls_proto || {}).toString === Function.toString ;((constructor_mixin || {}).toString === Function.toString
|| (cls_proto || {}).toString === ({}).toString) || (constructor_mixin || {}).toString === ({}).toString)
&& Object.defineProperty(_constructor, 'toString', { && Object.defineProperty(_constructor, 'toString', {
value: function(){ value: function(){
var args = proto.__init__ ? var args = proto.__init__ ?
@ -561,21 +609,24 @@ function Constructor(name, a, b){
return `${this.name}(${args})${normalizeIndent(code)}` }, return `${this.name}(${args})${normalizeIndent(code)}` },
enumerable: false, enumerable: false,
}) })
_constructor.__proto__ = cls_proto === undefined ?
_constructor.__proto__
: cls_proto
_constructor.prototype = proto
// set generic raw instance constructor... // set generic raw instance constructor...
_constructor.__rawinstance__ instanceof Function _constructor.__rawinstance__ instanceof Function
|| (_constructor.__rawinstance__ = || (_constructor.__rawinstance__ =
function(context, ...args){ function(context, ...args){
return makeRawInstance(context, this, ...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 // set constructor.prototype.constructor
Object.defineProperty(_constructor.prototype, 'constructor', { _constructor.prototype.constructor = _constructor
value: _constructor,
enumerable: false,
})
return _constructor } return _constructor }

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-object", "name": "ig-object",
"version": "2.7.2", "version": "3.0.0",
"description": "", "description": "",
"main": "object.js", "main": "object.js",
"scripts": { "scripts": {