_prototype
and __parent__, inheritance and scoping in ECMAScript
A brief description of inheritance and
scoping through the _prototype and __parent__ chains.
Our recent Thanksgiving Holiday led me to think about
family. When I think about family I think about parents, property,
and inheritance. More specifically, I think about ECMAScript inheritance
of properties through the __parent__ and _prototype chains. These
two types of property chains are often confused, sometimes misused,
and too often neglected (like family).
_prototype
The _prototype chain gives ECMAScript class-like behavior,
similar to other object-oriented languages. Multiple objects can
inherit the same property from a base class object, so that any
instance of that class object will inherit the property from the
base class. If any instance changes the value of an inherited
property the property changes only for that instance, not for
the base class or for other objects that inherit from that base
class. This is similar to the way basic inheritance acts in any
OO language.
__parent__
The __parent__ chains gives an ECMAScript object a
way to resolve the location of variable, letting a variable filter
down through a hierarchy of sub-objects. Multiple objects can
scope to the same property from a base parent object; when any
of these objects changes the value of this scoped variable it
is changed in the base parent object. This __parent__ chain is
unlike inheritance found in most OO languages.
_prototype vs __parent__
These two chains, which are independent of each other,
provide a combination that is very useful. Many object in your
application may have both types of chains. Because both methods
allow an object to reflect data in a base object, implementors
sometimes choose one over the other with insufficient thought.
_prototype
The _prototype chain should be selected when you want
OO inheritance. That is, when you want to have multiple instances
of a base class object inherit the behavior and default values
of that base object, and for each instance of that base class
object to maintain its own overrides of those inherited defaults.
You would use the _prototype chain whenever you define an object
that you want other objects to "act like". Additionally,
by using the _prototype chain you can have many instance objects
inherit lots of properties and methods without using extra memory
to duplicate those properties and methods in every instance (important
in small-memory situations).
Although a _protoype chain may be created explicitly,
it is usually automatically created when the "new" operator
is used. With "new", an object's ._prototype is initialized
to the .prototype object of the base class.
__parent__
The __parent__ chain should instead be used when you
want to represent a hierarchy of objects, where the script in
any one object automatically recognizes a variable in an enclosing
object. In browsers, for example, the __parent__ chain is how
script in a button event may automatically reference a variable
in the enclosing form object, or the enclosing frame or document
object.
A __parent__ chain is usually created explicitly when
designing the hierarchy of your objects. The __parent__ chain
is usually ignored unless a function is being called where that
function's object has the jseImplicitParents attribute. But you
can also tell the interpreter to use the __parent__ chain in a
jseInterpret call with the JSE_INTERPRET_IMPLICIT_PARENTS flag,
or for all functions by compiling with JSE_ALWAYS_IMPLICIT_PARENTS 1.
Example
The following example demonstrates the order and effect of the
different scope chains on objects. For this demo a println()
function is assumed, along with the setAttributes()
function from the lang library (usually these attributes would
be set from your compiled code).
function demo_func()
{
println("init demo_func x = " + x);
println("init demo_func y = " + y);
println("init demo_func z = " + z);
x = "demo_func_x";
y = "demo_func_y";
z = "demo_func_z";
println("term demo_func x = " + x);
println("term demo_func y = " + y);
println("term demo_func z = " + z);
}
setAttributes(demo_func,IMPLICIT_PARENTS|IMPLICIT_THIS);
var prototype_obj, parent_obj, instance_obj;
prototype_obj = new Object();
prototype_obj.x = "prototype_x";
prototype_obj.z = "prototype_z";
parent_obj = new Object();
parent_obj.x = "parent_x";
parent_obj.y = "parent_y";
parent_obj.z = "parent_z";
instance_obj = new Object();
instance_obj.z = "instance_z";
instance_obj._prototype = prototype_obj;
instance_obj.__parent__ = parent_obj;
instance_obj.demo_func = demo_func;
println("instance_obj.x = " + instance_obj.x);
println("instance_obj.y = " + instance_obj.y);
println("instance_obj.z = " + instance_obj.z);
println("parent_obj.x = " + parent_obj.x);
println("parent_obj.y = " + parent_obj.y);
println("parent_obj.z = " + parent_obj.z);
println("prototype_obj.x = " + prototype_obj.x);
println("prototype_obj.y = " + prototype_obj.y);
println("prototype_obj.z = " + prototype_obj.z);
println("");
instance_obj.demo_func();
println("");
println("instance_obj.x = " + instance_obj.x);
println("instance_obj.y = " + instance_obj.y);
println("instance_obj.z = " + instance_obj.z);
println("parent_obj.x = " + parent_obj.x);
println("parent_obj.y = " + parent_obj.y);
println("parent_obj.z = " + parent_obj.z);
println("prototype_obj.x = " + prototype_obj.x);
println("prototype_obj.y = " + prototype_obj.y);
println("prototype_obj.z = " + prototype_obj.z);
Executing the above code produces this output:
instance_obj.x = prototype_x
instance_obj.y = undefined
instance_obj.z = instance_z
parent_obj.x = parent_x
parent_obj.y = parent_y
parent_obj.z = parent_z
prototype_obj.x = prototype_x
prototype_obj.y = undefined
prototype_obj.z = prototype_z
init demo_func x = prototype_x
init demo_func y = parent_y
init demo_func z = instance_z
term demo_func x = demo_func_x
term demo_func y = demo_func_y
term demo_func z = demo_func_z
instance_obj.x = demo_func_x
instance_obj.y = undefined
instance_obj.z = demo_func_z
parent_obj.x = parent_x
parent_obj.y = demo_func_y
parent_obj.z = parent_z
prototype_obj.x = prototype_x
prototype_obj.y = undefined
prototype_obj.z = prototype_z
This code has demonstrated these facts about scoping variables
in this implicit-parents / implicit-this function.