JavaScript Prototypes and Classes

Dr. Greg Bernstein

Updated September 29th, 2019

Prototypes

Readings

Get Hands on

  • Try all the examples as you read the following slides

  • It will really help things sink in

The __proto__ property

Note __proto__ is in the ES6 standard but it has been a de facto standard in many browsers for quite a while.

var myObjA = {stuff: "Think about stuff", number: 42};
var myObjB = {course: "CS3520", students: 34};
myObjB.__proto__ = myObjA;
console.log(myObjB); // Doesn't look different
console.log(myObjB.stuff); // How?

More __proto__

var myObjA = {stuff: "Think about stuff", number: 42};
var myObjB = {course: "CS3520", students: 34};
var myObjC = {music: "Metal Baroque", guitarist: "JS Bach"};
myObjB.__proto__ = myObjA;
myObjC.__proto__ = myObjB;
console.log(myObjC); // Doesn't look different
console.log(myObjC.number); // How?

The Prototype Chain

JavaScript looks for properties (including functions) on every object on the __proto__ chain until null is encountered.

console.log(myObjC)
console.log(myObjC.__proto__)
console.log(myObjC.__proto__.__proto__)
console.log(myObjC.__proto__.__proto__.__proto__)
console.log(myObjC.__proto__.__proto__.__proto__.__proto__)

The Prototype Chain

for (var prop in myObjC) {
    console.log(`${prop} has value ${myObjC[prop]}`);
}
console.log("Keys for myObjC:");
console.log(Object.keys(myObjC));

Enumerate Object Properties

From MDN

  • for...in loops: traverses all enumerable properties and its prototype chain

  • Object.keys(o): returns an array with all the own enumerable properties’ names (“keys”) of an object o.

Enumerate Object Properties

From MDN

  • Object.getOwnPropertyNames(o): returns an array containing all own properties’ names (enumerable or not) of an object o.

New and Updated Properties

From plain english

New / updated properties are assigned to the object, not to the prototype

Try it!

Let’s add a property to myObjC already in myObjB or myObjA

myObjC.number = 3;
myObjC.course = "Web Dev";
console.log(myObjC)
console.log(myObjC.__proto__)
console.log(myObjC.__proto__.__proto__)

Is this behavior useful?

  • Yes, allows sharing of functions and properties
  • Prevents accidental overwrites
  • Kind of hints at inheritance

With Function Contructors

function WindsurfSail(size) {
    this.size = size;
    this.battens = 5;
    this.cambers = 4;
}
// What does this do?
WindsurfSail.prototype.style = "race";
WindsurfSail.prototype.description = function() {
    return `a ${this.size} ${this.style} sail`;
}

With Function Constructors

Usage and investigation

var s1 = new WindsurfSail(6.6);
var s2 = new WindsurfSail(7.7);
s1.description();
s2.description();
console.log(s1.__proto__)
console.log(WindsurfSail.prototype)

Function Constructor prototype

Given a Function constructor Board()

  • Board.prototype is by default an Object

  • Board.prototype will be set to the __proto__ field of the created instance when new is used

Function Constructor prototype

Given a Function constructor Board()

  • The Board.prototype Object has a constructor property that points back to the constructor function itself. This allows instanceof to work.

Prototypical Inheritance

  • The mechanisms we’ve just seen:
    • __proto__
    • Constructor Functions with prototype and constructor properties
  • Provide the basis for fairly rich inheritance modeling
    • All inheritance in JavaScript is based on these mechanisms

Prototypical Inheritance

  • The raw use of these mechanisms for inheritance hierarchies is somewhat error prone so ES6 introduced classes to help us out.

  • JavaScript classes do not add any new functionality, just much easier and more readable syntax to set up prototypical inheritance hierarchies and provide access to parent functions.

Control over Object Property Attributes

JavaScript now gives us some advanced control over the properties of objects. See Object.defineProperty() for details. We will not use these but they provide nice capabilities especially for libraries and such.

  • enumerable: true if and only if this property shows up during enumeration of the properties on the corresponding object.

  • writable: true if and only if the value associated with the property may be changed with an assignment operator.

Control over Object Property Attributes

  • configurable: true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.

JavaScript Classes

References

Basic Syntax

See MDN class

class Board {
    constructor() { // all properties here
    this.year = 2010;
    this.make = "Mikes Lab";
    this.weight = "10lbs";
    this.style = "Formula";
    }

    about() { // nice simple method definition
        return `${this.year} ${this.make} ${this.style} board`;
    }

    sailRecommendation() { // nice simple method definition
        return `${this.style} sails`;
    }
}

Try the simple class

Much nicer syntax, same implementation

var b1 = new Board();
b1.about();
b1.sailRecommendation();
console.log(b1.__proto__)
console.log(b1.__proto__.constructor)
b1 instanceof Board

Key points

From Deep Dive into Classes

  • Classes can only contain method definitions, not data properties;

  • When defining methods, you use shorthand method definitions;

  • Unlike when creating object literals, you do not separate method definitions in class bodies with commas;

Extending (inheritance)

Start with a base class:

class Mammal {
    constructor(commonName) {
        this.backbone = true;
        this.neocortex = true;
        this.name = commonName;
    }

    locomotion() {
        return "usually walking, but not always";
    }

    speak() {
        return "Some kind of mammal sound";
    }
}

Extending (inheritance)

Extend from a base class using extend keyword:

class Marsupial extends Mammal {
    constructor(commonName) {
        super(commonName); // parent constructor
        this.pouch = true;
        this.aussie = "likely";
    }

    speak() {
        return "Some kind of Marsupial sound";
    }
}

Try using these classes

mam1 = new Mammal("Kitty");
mam1.locomotion();
mam1.speak();
mars1 = new Marsupial("Taz Devil");
mars1.speak();
mars1.locomotion();
Object.keys(mars1)
Array [ "backbone", "neocortex", "name", "pouch", "aussie" ]
Object.keys(mam1)

Look at Prototype Chain

Check if the prototype chain is setup:

console.log(mam1)
console.log(mam1.__proto__)
console.log(mam1.__proto__.constructor)
console.log(mars1)
console.log(mars1.__proto__)
console.log(mars1.__proto__.constructor)
console.log(mars1.__proto__.__proto__)
console.log(mars1.__proto__.__proto__.constructor)

Try InstanceOf

mam1 instanceof Mammal
mars1 instanceof Mammal
mars1 instanceof Marsupial
mam1 instanceof Marsupial

Key Points for Extend

Summarized from Deep Dive into Classes

  • Subclasses are declared with the class keyword, followed by an identifier, and then the extends keyword, followed by an identifier.

  • If your derived class needs to refer to the class it extends, it can do so with the super keyword.

Key Points for Extend

Summarized from Deep Dive into Classes

  • A derived class can’t contain an empty constructor. You must call super in the constructor of a derived class before you use this.

Super duper stuff

Summarized from Deep Dive into Classes

In JavaScript, there are precisely two use cases for the super keyword.

  • Within subclass constructor calls.

  • To refer to methods in the superclass. Within normal method definitions, derived classes can refer to methods on the parent class with dot notation: super.methodName.