# object.js _object.js_ is a set of tools and abstractions to create and manage constructors, objects and prototype chains in idiomatic JavaScript. This is an alternative to the ES6 `class` syntax in JavaScript and provides several advantages: - _Uniform and minimalistic_ definition "syntax" based on basic JavaScript object literals. No special cases, special syntax or _"the same but slightly different"_ ways to do things, trying to adhere to [POLS](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) as much as possible, - _Transparently_ based on JavaScript's prototypical inheritance model, - Produces fully introspectable constructors/instances, - Does not try to emulate constructs foreign to JavaScript (i.e. classes), - Granular 2-stage instance construction and initialization (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 JavaScript objects, - no artificial restrictions. Disadvantages compared to the `class` syntax: - No _syntactic sugar_, - Slightly more complicated calling of `parent` (_super_) methods. Note that the produced constructors and objects are functionally identical (almost) to the ones produced via ES6 classes and are interchangeable with them. Here is a basic comparison:
_object.js_ ```javascript var A = object.Constructor('A', { // prototype attribute (inherited)... attr: 'prototype', method: function(){ // ... }, }) var B = object.Constructor('B', A, { constructor_attr: 'constructor', constructor_method: function(){ return 'constructor' }, }, { get prop(){ return 42 }, __init__: function(){ this.instance_attr = 7 }, }) ``` - No _direct_ way to do "private" definitions, - Clear separation of constructor and `.prototype` For example, in `B`: - First block (optional) is merged with `B`, - Second block _is_ the `B.prototype`, - No special syntax, stands out less. _ES6_ ```javascript class A { // instance attribute (copied)... attr = 'instance' method(){ // ... } } class B extends A { static constructor_attr = 'class' static constructor_method(){ return 'class' } get prop(){ return 42 } constructor(){ super(...arguments) this.instance_attr = 7 } } ``` - Syntax pretty but _misleading_; calling a _constructor_ a class is not correct, - `static` and instance definitions are not separated, - lots of details done non-transparently under the hood.
## Contents - [object.js](#objectjs) - [Contents](#contents) - [Installation](#installation) - [Basic usage](#basic-usage) - [Inheritance](#inheritance) - [Callable instances](#callable-instances) - [Mix-ins](#mix-ins) - [Advanced usage](#advanced-usage) - [Low level constructor](#low-level-constructor) - [Extending the constructor](#extending-the-constructor) - [Inheriting from native constructor objects](#inheriting-from-native-constructor-objects) - [Extending native `.constructor(..)`](#extending-native-constructor) - [Components](#components) - [`sources(..)`](#sources) - [`parent(..)`](#parent) - [`parentProperty(..)`](#parentproperty) - [`parentCall(..)`](#parentcall) - [`mixin(..)`](#mixin) - [`mixins(..)`](#mixins) - [`hasMixin(..)`](#hasmixin) - [`mixout(..)`](#mixout) - [`mixinFlat(..)`](#mixinflat) - [`makeRawInstance(..)`](#makerawinstance) - [`Constructor(..)` / `C(..)`](#constructor--c) - [Utilities](#utilities) - [`normalizeIndent(..)`](#normalizeindent) - [`match(..)`](#match) - [Limitations](#limitations) - [Can not mix unrelated native types](#can-not-mix-unrelated-native-types) - [License](#license) ## 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') var B = object.Constructor('B', 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 <--- SubItem // var Base = object.Constructor('Base', { proto_attr: 'prototype attr value', get prop(){ return 'propery value' }, method: function(){ console.log('Base.method()') }, // initializer... __init__: function(){ this.instance_attr = 'instance' }, }) var Item = object.Constructor('Item', Base, { __init__: function(){ // call the "super" method... object.parentCall(this.prototype.__init__, this) this.item_attr = 'instance attribute value' }, }) 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 }) // a more flexible approach... // // 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 }, }) var action = Action() var action2 = new Action2() // the instances are now functions... action() action2() ``` 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 functionally identical but structurally different constructors/objects, the difference is in `.prototype` -- what is defined as the prototype _is_ the prototype (_POLS_), so we get: - _prototype function_ -> `.prototype` is that exact function object, - `.__call__(..)` -> `.prototype` is _the_ object with the `.__call__(..)` method. The instance in both cases is a function wrapper that will proxy the call to the corresponding implementation. (this may change in the future) ### Mix-ins Prototype-based mixin... ```javascript var utilityMixin = { utility: function(){ // ... }, } var Base = object.Constructor('Base') // normal instance prototype chain: // b -> Base.prototype -> .. // var b = Base() // mixin directly into the instance... // // now the prototype chain looks like this: // b -> mixinFlat({}, utilityMixin) -> Base.prototype -> .. // object.mixin(b, utilityMixin) ``` `.mixin(..)` will copy the contents of `utilityMixin` into the prototype chain between `b` and `b.__proto__`. We can also remove the mixin... ```javascript o.mixout(b, utilityMixin) ``` The mixed-in data is removed iff a [matching](#match) object is found in the chain with the same attributes as `utilityMixin` and with each attribute matching identity with the corresponding attribute in the mixin. Constructor-based mixin... ```javascript var UtilityMixin = function(parent){ return object.Constructor(parent.name + '+utils', parent, utilityMixin) } var Mixed = object.Constructor('Mixed', UtilityMixin(Base), { // ... }) var m = Mixed() ``` ## Advanced usage ### Low level constructor ```javascript var LowLevel = object.Constructor('LowLevel', { __new__: function(context, ...args){ return {} }, }) ``` Like [_function constructor_ and `.__call__(..)`](#callable-instances) 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. ### 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', C, { // ... }, { // ... }) ``` ### Inheriting from native constructor objects ```javascript var myArray = object.Constructor('myArray', 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', Array, { __new__: function(context, ...args){ var obj = Reflect.construct(myArray.__proto__, args, myArray) // ... return obj }, }) ``` ## Components Note that all of the following are generic and will work on any relevant JavaScript object. For example, this will happily create a normal native array object `['a', 'b', 'c']`: ```javascript var l = object.makeRawInstance(null, Array, 'a', 'b', 'c') ``` ### `sources(..)` Get sources for attribute ``` sources(, ) sources(, , ) -> ``` ``` callback() -> 'stop' | false -> undefined ``` ### `parent(..)` Get parent attribute value or method ``` parent(, ) -> -> undefined parent(, ) -> -> undefined ``` _Edge case: The `parent(, ..)` 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._ ### `parentProperty(..)` Get parent property descriptor ``` parentProperty(, ) -> -> undefined ``` ### `parentCall(..)` Get parent method and call it ``` parentCall(, , ) -> -> undefined parentCall(, ) -> -> undefined ``` ### `mixin(..)` _Mixin_ objects into a prototype chain ``` mixin(, , ..) -> ``` This will link the base `.__proto__` to the last _mixin_ in chain, keeping the prototype visibility the same. This will copy the content of each input object without touching the objects themselves, making them fully reusable. ### `mixins(..)` Get matching mixins ``` mixins(, ) mixins(, [, ..]) mixins(, , ) mixins(, [, ..], ) -> list ``` ``` callback(, , ) -> 'stop' | false -> undefined ``` ### `hasMixin(..)` Check if _base_ object has _mixin_ ``` hasMixin(, ) -> ``` ### `mixout(..)` Remove the _first_ match matching input _mixin_ from _base_ of _base_ ``` mixout(, , ..) mixout(, 'first', , ..) -> ``` Remove _all_ occurrences of each matching input _mixin_ from _base_ ``` mixout(, 'all', , ..) -> ``` This is the opposite of `mixin(..)` ### `mixinFlat(..)` Mixin contents of objects into one _base_ object ``` mixinFlat(, , ..) -> ``` This is like `Object.assign(..)` but copies property descriptors rather than property values. ### `makeRawInstance(..)` Make a raw (un-initialized) instance ``` makeRawInstance(, , ..) -> ``` `makeRawInstance(..)` will do the following: - Create an instance object - get result of `.__new__(..)` if defined, or - if prototype is a function or `.__call__(..)` is defined, create a wrapper function, or - if constructor's `.__proto__` is a function (constructor) use it to create an instance, or - use `{}`. - Link the object into the prototype chain _Un-initialized_ means this will not call `.__init__(..)` ### `Constructor(..)` / `C(..)` Define an object constructor ``` Constructor() Constructor(, ) Constructor(, , ) Constructor(, , , ) Constructor(, , ) -> ``` `Constructor(..)` essentially does the following: - Creates a _constructor_ function, - Sets constructor `.name` and `.toString(..)` for introspection, - Creates `.__rawinstance__(..)` wrapper to `makeRawInstance(..)` - Sets constructor `.__proto__`, `.prototype` and `.prototype.constructor`, - Mixes in _constructor-mixin_ if given. The resulting _constructor_ function when called will: - call constructor's `.__rawinstance__(..)` if defined or `makeRawInstance(..)` to create an instance, - call instance's `.__init__(..)` if present. Shorthand to `Constructor(..)` ``` C(, ..) -> ``` ## Utilities ### `normalizeIndent(..)` / `normalizeTextIndent(..)` Align _code_ to shortest leading white-space ``` normalizeIndent() normalizeIndent(, ) normalizeIndent(, , ) -> ``` This is used to format `.toString(..)` return values for nested functions to make source printing in console more pleasant to read. `tab_size` defaults to `object.TAB_SIZE` `keep_tabs` defaults to `object.KEEP_TABS` A shorthand to `normalizeIndent(..)` optimized for text rather than code ``` normalizeTextIndent(..) -> ``` This ignores `object.KEEP_TABS` and `keep_tabs` is 0 by default. ### `match(..)` Test if the two objects match in attributes and attribute values ``` match(base, obj) -> bool ``` This relies on first level object structure to match the input object, for a successful match one of the following must apply: - object are identical or: - `typeof` matches _and_, - attribute count matches _and_, - attribute names match _and_, - attribute values are identical. ## Limitations ### Can not mix unrelated native types At this point we can't mix native types, for example it is not possible to make a callable `Array` object... This is not possible in current _JavaScript_ implementations directly as most builtin objects rely on "hidden" mechanics and there is no way to combine or inherit them. To illustrate: ```javascript // produces an Array that looks like a function but does not act like one... var a = Reflect.construct(Array, [], Function) // creates a function that looks like an array... var b = Reflect.construct(Function, [], Array) ``` So these will produce partially broken instances: ```javascript var A = object.Constructor('A', Array, function(){ .. }) var B = object.Constructor('B', Array, { __call__: function(){ .. }, }) ``` Essentially this issue and the inability to implement it without emulation, shows the side-effects of two "features" in _JavaScript_: - lack of multiple inheritance - _hidden_ protocols/functionality (namely: calls, attribute access) Still, this is worth some thought. ## License [BSD 3-Clause License](./LICENSE) Copyright (c) 2019, Alex A. Naanou, All rights reserved.