This guide explains the object instantiation lifecycle in Doh - the step-by-step process by which Doh creates and initializes object instances using the New()
function. Understanding this construction flow and the role of lifecycle phases is key to effectively using and extending Doh objects.
This guide covers:
New()
function and the object creation process.machine
function for phase execution control.New()
and AsyncNew()
)In Doh, object instances are created using either the New()
function for synchronous patterns or the AsyncNew()
function for patterns with asynchronous phase handlers. These functions orchestrate the transformation of a Pattern definition and an optional input idea
object into a fully initialized Doh object instance, ready for use. The process involves resolving inheritance, melding properties, and executing lifecycle phases in a specific order.
// Creating a new object instance (synchronous)
let myObject = New('pattern_name', idea_object, phase);
// Creating a new object instance (asynchronous - for patterns with async phase handlers)
let myAsyncObject = await AsyncNew('pattern_name', idea_object, phase);
New()
and AsyncNew()
FunctionsNew()
FunctionThe New()
function is the entry point for synchronous object creation in Doh. It can be called in several ways:
New(pattern, idea, phase)
- Create a new object with the specified pattern and idea, machined to the specified phaseNew(pattern, idea)
- Create a new object, defaulting to 'final' phaseNew(idea)
- Create a new object using idea.pattern as the patternNew(existingObject, phase)
- Machine an existing object to the specified phaseAsyncNew()
FunctionThe AsyncNew()
function is designed for patterns that contain asynchronous phase handlers:
await AsyncNew(pattern, idea, phase)
- Create a new object asynchronously, enabling async phase executionawait AsyncNew(pattern, idea)
- Create a new object asynchronously, defaulting to 'final' phaseawait AsyncNew(pattern)
- Create a new object using the pattern, with empty ideaKey differences from New()
:
async
can safely use await
New()
will generate warnings// Synchronous pattern - use New()
Pattern('SyncPattern', {
object_phase() {
this.data = 'initialized';
}
});
const syncObj = New('SyncPattern');
// Asynchronous pattern - use AsyncNew()
Pattern('AsyncPattern', {
async object_phase() {
this.data = await fetch('/api/data');
this.processedData = await this.data.json();
}
});
const asyncObj = await AsyncNew('AsyncPattern');
When New()
or AsyncNew()
is called, it initiates the following construction flow:
First, New()
determines the primary pattern and initial properties (idea
):
The function then resolves all the patterns that the object should inherit from:
// Collect all patterns from both the pattern's inherits and the idea's inherits
var patterns = Doh.meld_objects(
Doh.extend_inherits(Patterns[idea.pattern].inherits),
Doh.extend_inherits(idea.inherits)
);
// Add the main pattern last
patterns[idea.pattern] = true;
Before creating the object, constructor arguments are prepared:
// Process constructor arguments through pre_constructor hooks
for (let i = 0; i < pre_melded_constructors.length; i++) {
test_args = pre_melded_constructors[i](constructor_args);
if (test_args && IsIterable(test_args)) constructor_args = test_args;
}
The actual object is created using either the default prototype constructor or a custom one:
// Default PrototypeConstructor creates a proper prototype chain
object = prototype_constructor(...constructor_args);
object.inherited = object.inherited || {};
After object creation, constructors are called in inheritance order:
// Run all constructor methods in pattern order
for (let i = 0; i < melded_constructors.length; i++) {
melded_constructors[i].call(object, ...constructor_args);
}
if (Object.hasOwn(idea, 'constructor')) {
idea.constructor.call(object, ...constructor_args);
}
Each resolved pattern is then mixed into the object instance using Doh.mixin_pattern
. This function applies the MOC rules during the process. By default, properties are replaced (override-first). When a property declares a MOC melder (e.g., 'Method'
, 'Array'
, 'Object'
, deep {}
), the specified melding behavior is applied instead. Validation then occurs ('IsString'
, 'IsArray'
, 'IsObject'
, etc, or the default: 'IsAny'
):
// Mix each pattern into the object
// Doh.mixin_pattern applies MOC rules during this process
for (i in patterns) {
// Only mix in hard inherits (true)
if (patterns[i] === true) Doh.mixin_pattern(object, i);
else delete patterns[i]; // Clean up soft inherits
}
The inherits
property is updated to be an ordered list of inherited patterns:
object.inherits = [];
for (i in patterns) {
object.inherits.push(i);
}
Finally, the object's machine
function is attached. This function controls the execution of lifecycle phases.
object.machine = function (phase) {
// Cycle through defined phases (from MOC definition)
for (let phase_name of object.moc.__.phase_order()) {
if (!object.machine.completed[phase_name]) { // Run each phase only once
object.machine.phase = phase_name;
object.machine.completed[phase_name] = false; // Mark as running
// Execute the melded methods for this phase
// Note: 'Phase' is a MOC type, see docs/moc
object[phase_name].apply(object);
object.machine.completed[phase_name] = true; // Mark as complete
}
// Stop if the target phase is reached
if (phase_name == phase) return object;
}
return object;
};
// Initialize completion state
object.machine.completed = {};
// Execute phases up to the requested phase (or 'final' by default)
object.machine(phase || 'final');
This machine
ensures phases run in the correct order (defined by the combined MOC definitions of the inherited patterns) and only once per instance.
object
PatternThe base object
pattern defines two primary lifecycle phases that run for all Doh object instances:
object_phase
)The object_phase
is the primary initialization phase. Its responsibilities include setting up internal properties like melded
and handling static property connections.
// Simplified view of object_phase from the base 'object' pattern
object_phase: function () {
// Ensure this.melded refers to the final MOC definition
this.melded = this.moc || {};
Doh.mimic(this, 'melded', this, 'moc');
}
Asynchronous Object Phase:
When using AsyncNew()
, the object_phase
can be declared as async
and use await
:
Pattern('AsyncInitPattern', {
async object_phase() {
// Can safely use await in AsyncNew() context
this.config = await fetch('/api/config').then(r => r.json());
this.melded = this.moc || {};
Doh.mimic(this, 'melded', this, 'moc');
return { initialized: true };
}
});
// Must use AsyncNew() for patterns with async phases
const obj = await AsyncNew('AsyncInitPattern');
builder_phase
)The builder_phase
handles the automatic construction of child objects defined within the instance, integrating with the Sub-Object-Builder system.
// Simplified view of builder_phase from the base 'object' pattern
builder_phase: function () {
// 1. Collect properties marked for auto-building into this.built
Doh.collect_buildable_ideas(this, this.moc, this, '');
// 2. Set up parent-child linkage helpers (this.builder_method, etc.)
// ... (builder helper setup) ...
// 3. Build child objects if any were found
if (this.built) {
// Define the internal function that builds each child
this.machine_built = function (targetPhase) {
for (let prop_name in this.built) {
if (IsUndefined(this.built[prop_name])) { // Check if already built
let idea = this[prop_name];
// Instantiate child, machining it ONLY up to targetPhase
this[prop_name] = this.built[prop_name] = New(idea, targetPhase);
this[prop_name].builder = this; // Set parent reference
}
}
};
// Execute the build, machining children to the phase specified
// in this.machine_built_to, or 'final' if not specified.
this.machine_built(this.machine_built_to || 'final');
}
}
This phase uses an internal machine_built(phase)
function to iterate through properties marked for building (collected in this.built
) and instantiates them using New()
, passing down a target phase.
object
pattern)The base object
pattern also provides several utility methods available on all instances:
Understanding Phase Order:
object_phase
runs before builder_phase
object_phase
Choosing New() vs AsyncNew():
New()
for patterns with only synchronous phase handlersAsyncNew()
for patterns that have async phase handlersAsyncNew()
is fully compatible with synchronous patternsNew()
will generate warningsPattern Inheritance:
Machine Usage:
Custom Phases:
'phase'
type.AsyncNew()
pre_
and post_
hooks (see the MOC documentation for details).Asynchronous Phase Design:
async
when they need to use await
AsyncNew()
when patterns have async phasesthis
for async phasesWhen extending or creating patterns, focus on the phase methods (object_phase
, builder_phase
, and custom phases) to properly initialize your objects and build any child components. Use AsyncNew()
when any phase in your pattern hierarchy needs asynchronous operations.