# 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 // 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', { // inherit from Base... __extends__: 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 }) 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(, ) sources(, , ) -> ``` 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._ Get parent property descriptor ``` parentProperty(, ) -> -> undefined ``` Get parent method and call it ``` parentCall(, , ) -> -> undefined parentCall(, ) -> -> undefined ``` Mixin objects into a prototype chain ``` mixin(, , ..) -> ``` Mixin contents of objects into one ``` mixinFlat(, , ..) -> ``` This is like `Object.assign(..)` but copies property objects rather than property values. Make a raw (un-initialized) instance ``` makeRawInstance(, , ..) -> ``` A shorthand to this is `Constructor.__rawinstance__(context, ..)`. Define an object constructor ``` Constructor() Constructor(, ) Constructor(, , ) Constructor(, , ) -> ``` Shorthand to `Constructor(..)` ``` C(, ..) -> ``` ## Utilities Align text to shortest leading whitespace ``` normalizeIndent() normalizeIndent(, ) -> ``` This is used to format `.toString(..)` return values for nested functions to make source printing in console more pleasant to read. ## Limitations ### Can not mix unrelated native types directly At this point we can't mix native types, for example it is not possible to make a callable `Array` object... For example this will produce a broken instance: ```javascript var CallablaArray = object.Constructor('CallablaArray', Array, function(){ .. }) ``` This will produce an instance broken 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... ## License [BSD 3-Clause License](./LICENSE) Copyright (c) 2019, Alex A. Naanou, All rights reserved.