Austen's LJSTHW Journal

JavaScript’s Prototypal Inheritance: Level Zero

This is not meant to be authoritative in any way. These are simply my notes distilled and organized. I’m looking for feedback and corrections.

These are the five questions that help me understand the prototypal inheritance of JavaScript.

  1. What is an Object?
  2. What is a Function?
  3. What happens when a constructor is invoked?
  4. What happens when a constructor is defined?
  5. What is the prototype chain?

  1. An instance of Object is the base type for all non-primitive data types in JavaScript. The instance of an object type is like a lump of clay. Properties and methods can be attached to it at any point during its lifetime (except when Object.freeze() is applied). It can be instantiated in multiple ways.

    • A pair of curly braces creates an “object literal” which can be directly assigned or returned via factory function.
    • The Object.create() method returns an object instance.
    • The new operator tells a function to return a “constructed’” (instantiated and modified) object.

    For these notes, objects are instantiated via constructors. When referring to object properties in general, it is assumed that methods are also included.

  2. An instance of Function is a type that inherits directly from the base object. It is different from other objects in that it can be called to execute a subroutine of JavaScript code. When called with the new operator, the given function becomes an object “constructor”. All Function objects have a .prototype property for this purpose.

  3. A constructor instantiates and modifies an object. The constructed object inherits properties from the object referenced by .prototype. An executed return statement within the constructor’s body will override the “construction” of a new object in which case the constructor executes only the return statement.

  4. When the constructor is defined, it creates an empty instance of the base object and references it with the .prototype property. Though this prototype object begins life without any unique properties, its properties can be modified at any point after the constructor is defined. This particular object instance is meant to serve as a set of properties to be shared by all object instances constructed by the function.

  5. An object referenced by .prototype becomes the prototype for objects instantiated by the constructor, also known as “descendants”.The prototype chain is like a singly-linked list of prototypes and descendants. The properties of the prototype object are inherited by the descendants via a prototype-chain look-up. If the descendant is assigned a property with a name shared by the prototype object, the descendant’s version of the property shadows the prototype’s version and the look-up is completed. If the prototype is a descendant of another prototype, the look-ups can continue until either the sought property is finally found, or the lookup reaches the base Object type. If the constructor’s .prototype is reassigned to a different object, previously constructed objects are unaffected, but subsequently constructed objects perform property look-ups on the prototype chain from the substituted prototype.

Examples in Node REPL

Can base object properties be added and modified after creation?

> clay = new Object()
{}
> clay.arms = 2
2
> clay.legs = 2
2
> clay.head = 2
2
> clay
{ arms: 2, legs: 2, head: 2 }
> clay.head = 1
1
> clay
{ arms: 2, legs: 2, head: 1 }

Is a function object really constructed from the base object?

> riddle_obj = new Object()
{}
> riddle_func = new Function()
[Function: anonymous]
// Before we start modifying, let's be certain
// that neither object has the "question" property

> riddle_obj.question
undefined
> riddle_func.question
undefined

// The base object's prototype object is like an ancestor from which
// all base object instances inherit their properties. We'll use the
// Object.getPrototypeOf() method to access the prototype object.
// We'll also attach a question property to the base object's prototype
// to see if it propagates to both descendant base
// objects descendant functions.

> Object.getPrototypeOf(riddle_obj).question = "Why was 6 afraid of 7?"
'Why was 6 afraid of 7?'
> riddle_obj.question
'Why was 6 afraid of 7?'
> riddle_func.question
'Why was 6 afraid of 7?'

// That worked. Both types reflect the change to the prototype of the base
// object. Now we'll see if changing the prototype of function propagates to
// both the descendant function object and the descendant base object.

> Object.getPrototypeOf(riddle_func).answer = "Because 7 8 9!"
'Because 7 8 9!'

// The change propagates to the function,
> riddle_func.answer
'Because 7 8 9!'

// but the change does not propagate to the base object.
> riddle_obj.answer
undefined

// This is one way to understand how the base object type is the primary
// type, or prototype, from which all other non-primitive JavaScript
// datatypes inherit their properties.

Is a new operator ignored with return statement in the constructor?

// Which gets returned from a constructor, the Chicken or the Egg?

// Make Egg and Chicken type constructors.
> let Egg = function () { this.greeting = "The Egg has landed!"; }
undefined
> let Chicken = function () { this.greeting = "The Chicken has landed!"; }
undefined

// Create Chicken and Egg instances.
> let eggietype = new Egg()
Egg { greeting: 'The Egg has landed!' }
> let chickietype = new Chicken()
Chicken { greeting: 'The Chicken has landed!' }


// What does a constructor return if there is no return statement?
> let ChickenEgg_noreturn = function () {}
undefined
// set the prototype to Chicken object
> ChickenEgg_noreturn.prototype = chickietype
Chicken { greeting: 'The Chicken has landed!' }

// constructed object inherits from prototype,
// (it does not inherit from the constructor itself)
> new ChickenEgg_noreturn()
Chicken {}
> Object.getPrototypeOf( new ChickenEgg_noreturn() )
Chicken { greeting: 'The Chicken has landed!' }
// As expected, plain function call with no return statement
> ChickenEgg_noreturn()
undefined

// What results from a function containing a return statement if it is
// invoked with the "new" operator? (this is a "factory function" which
// uses Object.create() to return a descendant of the eggietype object)
> let ChickenEgg_yesreturn = function () { return Object.create(eggietype) }
undefined
// set the prototype to Chicken object
> ChickenEgg_yesreturn.prototype = chickietype;
Chicken { greeting: 'The Chicken has landed!' }

// "return" statement ignores "new" operator
> new ChickenEgg_yesreturn()
Egg {}
// As expected, the function returns the object created from the "return" statement
> ChickenEgg_yesreturn()
Egg {}


// This demonstrates that a constructor cannot execute a return statement
// because the return statement overrides "construction" from a prototype.

Is the default constructor prototype an empty base object?

> let Shoes = function (fastener) {
... this.fastener = fastener;
... }
undefined

// Here's our empty prototype.
> Shoes.prototype
Shoes {}

// These shoes have their own unique fasteners
> ruby_slippers = new Shoes("buckle");
Shoes { fastener: 'buckle' }
> clown_shoes = new Shoes("buttons")
Shoes { fastener: 'buttons' }

// buttoned clown shoes are out of style.
// take off those buttons!
> delete clown_shoes.fastener
true

// Hey, Bozo has no way to fasten his shoes!
> clown_shoes.fastener
undefined

// When in doubt lace your shoes.
> Shoes.prototype.fastener = "laces"
'laces'
> Shoes.prototype
Shoes { fastener: 'laces' }

// Now Bozo can tie his shoes.
> clown_shoes.fastener
'laces'

// Dorothy wants laces too. Just click your heels and...
> delete ruby_slippers.fastener
true
// ...voila!
> ruby_slippers.fastener
'laces'

// This is a silly demonstration that the constructor's default
// prototype is an empty base object. The changes to the base
// object are visible to descendants lacking properties to shadow
// the base object's properties

Example in Script

What happens to an object’s prototype chain when prototypes are reassigned?

#!/usr/bin/env node
// This is our prototype chain printer
const protoChain = (obj, objname) => {
    console.log(`>>>protoChain: ${objname}`);
    while (obj) {
        console.log(obj);
        let spaces = "  ";
        for(let i = 0; i < 4; i++) {
            if(i === 3) {
                console.log(spaces +"V");
            } else if(i === 2) {
                console.log(" \\ /");
            } else {
                console.log(spaces + "|");
            }
        }
        obj = Object.getPrototypeOf(obj);
    }
    console.log(obj);
}

// Here's the inheritance hierarchy for our life forms
// Animalia -> Cnidaria -> Anthozoa -> SeaAnemone
// Chromista -> BrownAlgae -> Kelp -> seaBamboo
// Chromista -> BrownAlgae -> Kelp -> Coral
// reassign Coral prototype
// Animalia -> Cnidaria -> Anthozoa -> Coral

/*--------Animalia--------*/

function Animalia () {
    this.kingdom = "animalia";
}

function Cnidaria () {
    this.phylum = "cnidaria";
}
Cnidaria.prototype = new Animalia();

function Anthozoa () {
    this.class = "anthozoa";
}
Anthozoa.prototype = new Cnidaria();

function SeaAnemone () {
    this.order = "sea anemone";
}
SeaAnemone.prototype = new Anthozoa();

/*-------Chromista---------*/

function Chromista () {
    this.kingdom = "chromista";
}

function BrownAlgae () {
    this.division = "brown algae";
}
BrownAlgae.prototype = new Chromista();

function Kelp () {
    this.family = "kelp";
}
Kelp.prototype = new BrownAlgae();

function SeaBamboo () {
    this.species = "sea bamboo";
}
SeaBamboo.prototype = new Kelp();

/*---------What is Coral?---------*/
function BlueCoral () {
    this.species = "blue coral";
}
BlueCoral.prototype = new Kelp();


console.log("\n>>> Here's the prototype reassignment demo");
console.log("Our first Coral leads back to the Chromista type");
console.log("> let coral = new BlueCoral():\n");
let coral = new BlueCoral();
protoChain(coral, "coral");

console.log("\n>>> Oops... coral should descend from Animal, not Chromista!");
console.log("Let's change that prototype of the constructor and check our " +
    " first coral's prototype chain");
console.log("> BlueCoral.prototype = new Anthozoa()\n");
BlueCoral.prototype = new Anthozoa();
protoChain(coral, "coral with .constructor.prototype reassigned");

console.log("\nThat BlueCoral constructor's reassigned prototype had no effect" +
    " on our existing coral. The reassignment will only apply to newly" +
    "instantiated corals. Let's go back and fix our existing coral with...");
console.log("> Object.setPrototypeOf(coral, new Anthozoa):\n");
Object.setPrototypeOf(coral, new Anthozoa());
protoChain(coral, "coral with Object.setPrototypeOf() reassignment");

And here’s the output…


>>> Here's the prototype reassignment demo
Our first Coral leads back to the Chromista type
> let coral = new BlueCoral():

>>>protoChain: coral
Chromista { species: 'blue coral' }
  |
  |
 \ /
  V
Chromista { family: 'kelp' }
  |
  |
 \ /
  V
Chromista { division: 'brown algae' }
  |
  |
 \ /
  V
Chromista { kingdom: 'chromista' }
  |
  |
 \ /
  V
Chromista {}
  |
  |
 \ /
  V
{}
  |
  |
 \ /
  V
null

>>> Oops... coral should descend from Animal, not Chromista!
Let's change that prototype of the constructor and check our  first coral's prototype chain
> BlueCoral.prototype = new Anthozoa()

>>>protoChain: coral with .constructor.prototype reassigned
Chromista { species: 'blue coral' }
  |
  |
 \ /
  V
Chromista { family: 'kelp' }
  |
  |
 \ /
  V
Chromista { division: 'brown algae' }
  |
  |
 \ /
  V
Chromista { kingdom: 'chromista' }
  |
  |
 \ /
  V
Chromista {}
  |
  |
 \ /
  V
{}
  |
  |
 \ /
  V
null

That BlueCoral constructor's reassigned prototype had no effect on our existing coral. The reassignment will only apply to newlyinstantiated corals. Let's go back and fix our existing coral with...
> Object.setPrototypeOf(coral, new Anthozoa):

>>>protoChain: coral with Object.setPrototypeOf() reassignment
Animalia { species: 'blue coral' }
  |
  |
 \ /
  V
Animalia { class: 'anthozoa' }
  |
  |
 \ /
  V
Animalia { phylum: 'cnidaria' }
  |
  |
 \ /
  V
Animalia { kingdom: 'animalia' }
  |
  |
 \ /
  V
Animalia {}
  |
  |
 \ /
  V
{}
  |
  |
 \ /
  V
null

I think I might also draw cartoons to go with the code examples! But again, I need to make sure I’m understanding the concepts and vocabulary correctly.

I know it looks like I haven’t made any journal entries in a while, but I’ve actually been steadily updating my post on prototypal inheritance. There have been several edits already.

I’m not going to make cartoons after all, but I am adding some example code.

Right now I’m working on the example code for illustrating the prototype chain. I’ve already got some content worked out to demo the inheritance relationships, but I’m struggling a little bit with finding an intuitive way to put it into code. I will figure it out tomorrow and post it.

For now, I’m done with my Level 0 prototypal inheritance notes. If you happen to read them, please let me know when you find glaring errors.

On to LJSTHW ex30!

Super good writeup! And, you should investigate the problems people had with the this keyword and how a lot of this prototype style worked. Basically, this was inconsistent in the prototype style so people had weird workarounds like defining classes inside functions just so they can grab this at the top in a closure. The new style classes get rid of all those problems and make this consistent.

Great! Thanks for the feedback. I will research this now.

I posted the prototypal inheritance write-up on my blog.


I might add a very basic demo of the this keyword to clarify my explanation, but I’ve been learning about callbacks and that’s keeping me busy.

1 Like

Heavy stuff :space_invader: :grinning:
Cool write up!

1 Like

In my write-up on prototypal inheritance, I demonstrated this weird JavaScript overriding feature I noticed between new and return when using functions that can be both factory functions and constructors. My understanding was that the return keyword automatically overrides a new operator. Today I discovered that it’s only true if the return value is an object. If the value is a primitive (string, boolean, number, null), the new operator overrides the return statement and the function becomes a constructor!

This script I wrote helped me figure it out on my own, but I just found the documentation on MDN right near the top of the page for the new keyword. It’s so weird. Why didn’t the language designers just make it so return automatically overrides new, regardless of whether or not the return value is primitive? What kind of sadist would build a language feature this way? Here’s the MDN excerpt:

When the code new Foo(...) is executed, the following things happen:

  1. A new object is created, inheriting from Foo.prototype .
  2. The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo () , i.e. if no argument list is specified, Foo is called without arguments.
  3. The object (not null, false, 3.1415 or other primitive types) returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn’t explicitly return an object, the object created in step 1 is used instead. (Normally constructors don’t return a value, but they can choose to do so if they want to override the normal object creation process.)

Here’s my demonstration script. It’s also in my gitlab repo for LJSTHW. I was going to write about this on my blog, but then I found in the MDN docs so it’s probably not worth writing about anymore.

#!/usr/bin/env node
// This is a test of how the "return" and "new" keywords can override one
// another in object instantiation.
// "return non-primitive object" overrides "new object"
// "new object" overrides "return primitive value"

function FactoryEgg () {
    this.eggType = '\"I was instantiated in a factory function!\"'
    this.sayType = function() {console.log(this.eggType)};
}

function ConstructorEgg (){
    this.eggType = '\"I was instantiated in a constructor function!\"'
    this.sayType = function() {console.log(this.eggType)};
}

// Chicken can be a factory function if supplied a "factory" argumnet,
//  but it can also be a constructor.
function Chicken (whichway) {
    if (whichway === "factory") {
        // This will override "new Chicken()" constructor
        return new FactoryEgg();
    } else {
        // this gets overridden with "new Chicken()" constructor
        return '\"I am a primitive return value!\"';
    }
}

// set the function's prototype for when it is invoked as a constructor
console.log("> Chicken.prototype = new ConstructorEgg();");
Chicken.prototype = new ConstructorEgg();

console.log("\nThe following chicken eggs come from the same " +
    "\nChicken function to demonstrate how keywords " +
    "\n'new' and 'return' can override one another.");
console.log("\n>>> RETURN OBJECT overrides NEW OBJECT");

console.log("> chelsea = Chicken('factory');");
chelsea = Chicken('factory');
console.log("> console.log(chelsea);");
console.log(chelsea);
console.log("> chelsea.sayType();");
chelsea.sayType();

console.log("\n>>> RETURN PRIMITIVE VALUE");

console.log("> charlotte = Chicken('return primitive without new operator!');");
charlotte = Chicken('return primitive without new operator!');
console.log("> console.log(charlotte)");
console.log(charlotte)

console.log("\n>>> NEW OBJECT overrides RETURN PRIMITIVE VALUE");
console.log("> charlene = new Chicken('new overrides return primitive!');")
charlene = new Chicken('new overrides return primitive!');
console.log("> console.log(charlene);");
console.log(charlene);
console.log("> charlene.sayType();");
charlene.sayType();

and the output:

> Chicken.prototype = new ConstructorEgg();

The following chicken eggs come from the same 
Chicken function to demonstrate how keywords 
'new' and 'return' can override one another.

>>> RETURN OBJECT overrides NEW OBJECT
> chelsea = Chicken('factory');
> console.log(chelsea);
FactoryEgg {
  eggType: '"I was instantiated in a factory function!"',
  sayType: [Function] }
> chelsea.sayType();
"I was instantiated in a factory function!"

>>> RETURN PRIMITIVE VALUE
> charlotte = Chicken('return primitive without new operator!');
> console.log(charlotte)
"I am a primitive return value!"

>>> NEW OBJECT overrides RETURN PRIMITIVE VALUE
> charlene = new Chicken('new overrides return primitive!');
> console.log(charlene);
ConstructorEgg {}
> charlene.sayType();
"I was instantiated in a constructor function!"

If you’re going with ES6 then don’t do this:

let FactoryEgg = function () {
}

That’s like a bizarre hybrid old-JS+ES6 monster and you’ll have weird issues with this. Do this:

let FactoryEgg = () => {
}

Use the fat-arrow () => everywhere you can as it solves a ton of problems for you. In fact I don’t even know how you could do let and not use ES6 so just use fat-arrows. The word function will cause you problems except in a few places like async function and the new style classes.

1 Like

Right! Thank you for pointing out the inconsistency. I guess I could’ve stuck with named function definitions instead using of let with anonymous functions for pre-ES6 style JS. I might go back and fix that on my blog once I get through a couple of tutorials, but I will definitely use arrow functions IRL.

In any case, I believe I couldn’t do the constructor-override demo with arrow functions because their scope is bound to the global context by default (e.g., this === window); they can’t be constructors, but they can be factory functions. Also, based on what I learned from another demo I wrote, they don’t play nicely as object methods either. Maybe there’s some way to use bind, apply or call to reassign their context, but I haven’t taken time to experiment with those.

I think I’ll post a short write up of that function-context demo here soon.

I just changed the function definitions to pre-ES6 style in thenew vs return override demo script. Thanks again for pointing that out!

How Invocation Patterns Affect “this” Context

I’m a bit confused by some behaviour with the this keyword when functions are invoked as callbacks. I’ll update this post into a writeup as I start to figure it out. Once I’ve ironed out the wrinkles, I’ll post it on my blog.

Here is a demo script, which can also be found on my GitLab page for Learn JavaScript the Hard Way. The confusing part is near the end of the script.

#!/usr/bin/env node
// I'm testing "this" in different contexts
// In this particular test, I want to see what happens when 
// a function is invoked with function invocation versus
// local invocation. I am also contrasting named function definition
// with the "function" keyword versus arrow functions.
//
// Let's start with a global arrow function
let a = () => {
    // what is the context?
    if (this === g || this === h || this === j || this === k) {
        console.log("a local arrow function");
    } else {
        console.log("a global arrow function");
    }
}

// And now a global function
function f () {
    // what is the context?
    if (this === g || this === h || this === j || this === k) {
        console.log("f local function");
    } else {
        console.log("f global function");
    }
}

// What happens when global functions are referenced from an object literal?
let g = {
    a : a,
    f : f,
    // Let's also try defining similar functions within the literal
    // arrow function within an object literal still has global "this"
    p : ()=>{ this===g ? console.log("p local arrow") : console.log("p global arrow");},
    // However function def within object literal has local "this"!
    q : function(){ this===g ? console.log("q local") : console.log("q global");}
}

H = function () {
    // creating references to the global functions in a constructor
    this.a = a;
    this.f = f;
    // defining similar functions within the constructor
    // arrow function in constructor has a local "this"
    this.r = ()=>{ this===h ? console.log("r local arrow") : console.log("r global")};
    // nested function def also has local "this"
    this.u = function() {this===h ? console.log("u local"):console.log("u global")};
}
h = new H();

// let's also see what happens if the function objects are passed as parameters
// to a constructor
J = function (func_a, func_f) {
    this.a = func_a;
    this.f = func_f;
}
j = new J(a, f);

// Finally, a and f are passed as callback functions to a method before invocation.
K = function () {
    this.n = function (func_a, func_f) {
        func_a();
        func_f();
    };
}
k = new K()

console.log("\nFunctions a and f are defined from global.");
console.log(">>> invoked as global function");
a(); // a global arrow function
f(); // f global function

console.log("\nFunctions p and q are defined within object literal.");
console.log(">>> invoked via object literal 'g'");
g.a(); // a global arrow function
g.f(); // f local function
g.p(); // p global arrow
g.q(); // q local

console.log("\nFunctions r and u are defined within constructor.");
console.log(">>> invoked via constructed object 'h'");
h.a(); // a global arrow function
h.f(); // f local function
h.r(); // r local arrow
h.u(); // u local

console.log("\n>>> a and f invoked via parameter to constructed object 'j'.");
j.a(); // a global arrow function
j.f(); // f local function

console.log("\n>>> a and f invoked as callbacks in method 'k.n()'.");
k.n(a, f);

// From these tests, it can be seen that arrow functions obey rules
// of lexical scope. That is, their scope is determined at the point of 
// function definition. "this" refers to the context where the function
// is defined.
// 
// I'm confused about the following:
// Regular global function definitions should obey dynamic scope rules 
// if invoked as a callback function from a method. "this" refers to the context where the function is called.
// But in my tests, the regular function obeys static scope.
// What am I misunderstanding?

Yes, that’s why I told you to never type the word function again. I’m serious, it’s just broken when you’re trying to do OOP. The problem is fat-arrow functions have a good this, but function is old and doesn’t handle this right. The caveat is that in the new OOP they fixed function when used inside class so that this works as expected.

2 Likes

I just started my first React tutorial. Lots of head scratching, but at least I’m getting an idea of the structure and what I need to learn next.

I’m near the end of the tutorial where there’s some “extra practice”. It’ll be fun to try solving these problems, but I’m not sure how far hacking will get me without actually understanding how this all fits together.

In any case, there’s some important vocabulary I need to investigate:

  • components (pure, class)
  • render (incl. return)
  • props (incl. super)
  • state (need this to learn Redux)
  • constructor (only for a component managing state)

And some tools to learn:

  • babel
  • webpack
  • npm

This is my second React tutorial.

I’m trying to make sense of props, components and elements. Here’s what I’m thinking so far.
Props are generic objects which are passed from upper components down to lower components. Because JavaScript doesn’t use named parameters (unlike Python), the “tag attributes” of the component’s markup representation are put in as properties of the generic object. It’s kind of like passing a dict() of key-value pairs as arguments to a function.

Elements are like units composed of markup used to structure the app content.

Components are object/function representations of the functionality of an app. In JavaScript, they are defined as functions or classes, and in JSX they are invoked like XML tags. They can manage internal state as well as receive parameters/arguments (props) from the higher components which invoke them. A function component returns a representation of its state in the form of elements. An object component is similar, but the elements are returned from the component’s render method. It turns out each component class and function is basically a kind of constructor for component instances (duh). The component instance can be seen in the virtual DOM. The “elements” contained within are all components as well, even the ones that look like standard HTML.

The main difference between class-built Components and function Components is that function components only have a render() method along with a few other built-in methods, whereas the former can have custom methods.

When using components, it might help to think of them as custom HTML tags with through which necessary functionality must be implemented. Ideally, a complex component is composed of simpler components which can be reused throughout the app/page.

And here’s a reminder to myself to read this article about promises.

I also need to read about super.