object.js/README.md

427 lines
9.8 KiB
Markdown
Raw Normal View History

# object.js
_object.js_ provides a set of tools for making and maintaining object
constructors and for managing their inheritance relations.
This is an alternative to the ES6 `class` syntax in JavaScript and provides
several advantages:
- Simple way to define instance and "class" methods, properties and attributes,
- Uniform and minimalistic definition syntax based on basic JavaScript
object syntax, no special cases, special syntax or _"the same but slightly
different"_ ways to do things,
- _Transparently_ based on JavaScript's prototypical inheritance model,
- Granular instance construction (a-la _Python's_ `.__new__(..)`
and `.__init__(..)` methods)
- Simple way to define callable instances (including a-la _Python's_
`.__call__(..)`)
- Less restrictive:
- `new` is optional
- all input components are reusable
- no artificial restrictions
Disadvantages compared to the `class` syntax:
- No _syntactic sugar_
- 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
```shell
$ npm install ig-object
```
Or just download and drop [object.js](object.js) into your code.
## Basic usage
Include the code, this is compatible with both [node's](https://nodejs.org/) and
[RequireJS'](https://requirejs.org/) `require(..)`
```javascript
var object = require('ig-object')
```
Create a basic constructor...
```javascript
// NOTE: new is optional here...
var A = new object.Constructor('A')
```
In _JavaScript_ constructor `B` inherits from constructor `A` iff
`A.prototype` is _prototype_ of `B.prototype`. So to implement inheritance
we simply need to _link_ the prototypes of two constructors via `.__proto__`,
`Object.create(..)` or other means.
```javascript
var B = object.Constructor('B', { __extends__: A })
var C = object.Constructor('C', B, {})
```
Now we can test this...
```javascript
var c = C() // or new C()
c instanceof C // -> true
c instanceof B // -> true
c instanceof A // -> true
```
### Inheritance
```javascript
//
// Base <--- Item
//
2019-07-17 00:26:39 +03:00
var Base = object.Constructor('Base', {
proto_attr: 'prototype attr value',
get prop(){
return 'propery value' },
method: function(){
console.log('Base.method()') },
2019-07-17 00:26:39 +03:00
// initializer...
__init__: function(){
this.instance_attr = 'instance'
},
2019-07-17 00:26:39 +03:00
})
var Item = object.Constructor('Item', {
// inherit from Base...
__extends__: Base,
2019-07-17 00:26:39 +03:00
__init__: function(){
// call the "super" method...
object.parentCall(this.prototype.__init__, this)
this.item_attr = 'instance attribute value'
},
2019-07-17 00:26:39 +03:00
})
var SubItem = object.Constructor('SubItem', Item, {
// ...
})
```
### Callable instances
```javascript
var Action = object.Constructor('Action',
// constructor as a function...
function(context, ...args){
// return the instance...
return this
})
var action = new Action()
// the instance now is a function...
action()
// a different way to do the above...
//
// This is the same as the above but a bit more convenient as we do
// not need to use Object.assign(..) or object.mixinFlat(..) to define
// attributes and props.
var Action2 = object.Constructor('Action2', {
__call__: function(context, ...args){
return this
},
})
```
In the above cases both the _function constructor_ and the `.__call__(..)`
method receive a `context` argument in addition to `this` context, those
represent the two contexts relevant to the callable instance:
- Internal context (`this`)
This always references the instance being called
- External context (`context`)
This is the object the instance is called from, i.e. the call _context_
(`window` or `global` by default)
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
### Low level constructor
```javascript
var LowLevel = object.Constructor('LowLevel', {
__new__: function(context, ...args){
return {}
},
})
```
Like _function constructor_ and `.__call__(..)` this also has two contexts,
but the internal context is different -- as it is the job of `.__new__(..)`
to create an instance, at time of call the instance does not exist and `this`
references the `.prototype` object.
The external context is the same as above.
Contexts:
- Internal context (`this`)
References the `.prototype` of the constructor.
- External context (`context`)
This is the object the instance is called from, i.e. the call _context_
(`window` or `global` by default), the same as for function constructor
and `.__call__(..)`.
The value `.__new__(..)`returns is used as the instance and gets linked
in the prototype chain.
This has priority over the callable protocols above, thus the user must
take care of both the _function constructor_ and `prototype.__call__(..)`
handling.
**Notes:**
- `.__new__(..)` is an instance method, contrary to _Python_ (the
inspiration for this protocol). This is done intentionally as in
JavaScript there is no distinction between an instance and a class and
defining `.__new__(..)` in the class would both add complexity as well
as restrict the use-cases for the constructor.
### Extending the constructor
```javascript
var C = object.Constructor('C',
// this will get mixed into the constructor C...
{
constructor_attr: 123,
constructorMethod: function(){
// ...
},
// ...
}, {
instanceMethod: function(){
// get constructor data...
var x = this.constructor.constructor_attr
// ...
},
// ...
})
```
And the same thing while extending...
```javascript
var D = object.Constructor('D',
// this will get mixed into C(..)...
{
__extends__: C,
// ...
}, {
// ...
})
```
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', {
__extends__: Array,
// ...
})
```
All special methods and protocols defined by _object.js_ except for
`.__new__(..)` will work here without change.
For details on `.__new__(..)` and native `.constructor(..)` interaction
see: [Extending native `.constructor(..)`](#extending-native-constructor)
### Extending native `.constructor(..)`
Extending `.constructor(..)` is not necessary in most cases as
`.__init__(..)` will do everything generally needed, except for instance
replacement.
```javascript
var myArray = object.Constructor('myArray', {
__extends__: Array,
__new__: function(context, ...args){
var obj = Reflect.construct(myArray.__proto__, args, myArray)
// ...
return obj
},
})
```
## Components
Get sources for attribute
```
sources(<object>, <name>)
sources(<object>, <name>, <callback>)
-> <list>
```
Get parent attribute value or method
```
parent(<prototype>, <name>)
-> <parent-value>
-> undefined
parent(<method>, <this>)
-> <parent-method>
-> undefined
```
_Edge case: The `parent(<method>, ..)` has one potential pitfall -- in
the rare case where a prototype chain contains two or more references
to the same method under the same name, `parent(..)` can't distinguish
between these references and will always return the second one._
Get parent property descriptor
```
parentProperty(<prototype>, <name>)
-> <prop-descriptor>
-> undefined
```
Get parent method and call it
```
parentCall(<prototype>, <name>, <this>)
-> <result>
-> undefined
parentCall(<method>, <this>)
-> <result>
-> undefined
```
Mixin objects into a prototype chain
```
mixin(<root>, <object>, ..)
-> <object>
```
Mixin contents of objects into one
```
mixinFlat(<root>, <object>, ..)
-> <object>
```
This is like `Object.assign(..)` but copies property objects rather than
property values.
Make a raw (un-initialized) instance
```
makeRawInstance(<context>, <constructor>, ..)
-> <object>
```
A shorthand to this is `Constructor.__rawinstance__(context, ..)`.
Define an object constructor
```
Constructor(<name>)
Constructor(<name>, <prototype>)
Constructor(<name>, <parent-constructor>, <prototype>)
Constructor(<name>, <constructor-mixin>, <prototype>)
-> <constructor>
```
Shorthand to `Constructor(..)`
```
C(<name>, ..)
-> <constructor>
```
## 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
```
normalizeIndent(<text>)
normalizeIndent(<text>, <tab-size>)
-> <text>
```
This is used to format `.toString(..)` return values for nested functions
to make source printing in console more pleasant to read.
## License
[BSD 3-Clause License](./LICENSE)
2019-07-17 00:26:39 +03:00
Copyright (c) 2019, Alex A. Naanou,
All rights reserved.
<!-- vim:set ts=4 sw=4 spell : -->