Skip to content

Javascript inheritance identification

Greg Swindle edited this page Nov 22, 2017 · 3 revisions

Engineers and developers must first understand a software product before they can extend, repair, or improve such products. Class-Responsibilities-Collaborator (CRC) models combat complexity with simplicity: their very structure encourages designers to focus on product behavior instead of programming mechanics.

Table of contents

1. Identifying JavaScript inheritance for ORC Models

JavaScript is an object-based language based on prototypes, rather than being class-based. Because of this different basis, it can be less apparent how JavaScript allows you to create hierarchies of objects and to have inheritance of properties and their values.

Details of the object model. (n.d.). Retrieved August 01, 2017, from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model

Engineers and developers must first understand a software product before they can extend, repair, or improve such products. Class-Responsibilities-Collaborator (CRC) models combat complexity with simplicity: their very structure encourages designers to focus on

  • Classes
  • Responsibilities
  • Collaborators

Despite ES2105's implementation of class, extends, super, static keywords, JavaScript did not become a class-based language. On the contrary, JavaScript remains a object-based language. Programmers who hope to corral complex JavaScript source code must first understand that JavaScript implements two essential OOP features -- inheritance and polymorphism -- with prototype chains instead of classes. Indeed, it is vital to identify and define all possible OOP techniques in JavaScript in order to generate Object-Responsibilities-Collaborator model reports.

2. Four definitions of prototypical inheritance

In JavaScript runtime contexts, we can discover an object's prototype using either Object.getPrototypeOf or Reflect.getPrototypeOf. These methods are not available for execution during static code analysis, however, so we need to define strategies for identifying an object's prototype based on a program's abstract syntax tree.

There are four ways to create Objects with prototype chains in JavaScript; therefore, there are four ways to identify Objects for CRC model representation.

The article "JSClassFinder: A Tool to Detect Class-like Structures in JavaScript" prescribes two definitions for identifying what the authors call "classes":[2]

An object is a tuple (C, A, M), where

  • C is the object name,
  • A = {a1, a2, . . . , ap} are the attributes defined by the object, and
  • M = {m1, m2, . . . , mq} are the methods.

Note: In the following definitions, the variable A (object attributes) is synonymous with the official ECMAScript term property.

2.1. Definition 1

An object (C, A, M), defined in a program P, must respect the following conditions:

  • P must have a function with name C.

  • P must include at least one expression of type

    • new C() or
    • Object.create(C.prototype).
  • For each aA,

    • The function C must include an assignment this.a = Exp or
    • P must include an assignment C.prototype.a = Exp.
  • For each mM,

    • function C must include an assignment this.m = function {Exp} or
    • P must include an assignment C.prototype.m = function {Exp}.

2.2. Definition 2: constructors

Assuming that (C1, A1, M1) and (C2, A2, M2) are objects in a program P, we define that C2 is a prototype of C1 if one of the following conditions holds:

  • P includes an assignment C2.prototype = new C1().
  • P includes an assignment C2.prototype = Object.create(C1.prototype).
  • P includes an assignment C2.prototype.constructor = C2. [3]

2.3. Definition 3 - extends

ECMAScript 2015 (aka ECMAScript 6) introduced the class syntax, which is a form of syntactic-sugar around Object prototypes. When using classes, we express prototypal inheritance with the extends keyword [4], e.g.,

class CrcModelMarkdownFormatter extends CrcModelFormatter {
    // stuff here
}

Assuming that (C1, A1, M1) and (C2, A2, M2) are objects in a program P, we define that C2 is a prototype of C1 if one of the following conditions holds:

  • P includes a declaration class C2 extends C1.

2.4. Definition 4 - Object.setPrototypeOf

ECMAScript 6 also introduced the static method Object.setPrototypeOf to enable prototypal inheritance.

Assuming that (C1, A1, M1) and (C2, A2, M2) are objects in a program P, we define that C2 is a prototype of C1 if P includes an expression Object.setPrototypeOf(C2.prototype, C1).

🔗 Inheritance and the prototype chain goes into more detail.

2.5. Object property inheritance matrix

Property inheritance Notes
Function.prototype.call()
Function.prototype.apply()
this.propertyName assignment
Function.prototype.propertyName assignment

2.6. Object method inheritance matrix

Method inheritance Notes
this.methodName = FunctionExpression
Function.prototype.methodName = FunctionExpression

2.7. constructor inheritance matrix

Method inheritance Notes
Fxn2.prototype = new Fxn1()
Fxn2.prototype = Object.create(Fxn1.prototype)
Fxn2.prototype.constructor = Fxn2

3. Syntax constructs

3.1. Source code

const o = {a: 1}

// The newly created object o has Object.prototype as its [[Prototype]]
// o has no own property named 'hasOwnProperty'
// hasOwnProperty is an own property of Object.prototype.
// So o inherits hasOwnProperty from Object.prototype
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null

const b = ['yo', 'whadup', '?']

// Arrays inherit from Array.prototype
// (which has methods indexOf, forEach, etc.)
// The prototype chain looks like:
// b ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2
}

// Functions inherit from Function.prototype
// (which has methods call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null

3.2. espree@3.4.3 AST

3.3. Node identification

4. Constructor functions

4.1. Source code

function Polygon(height, width) {
  this.height = height
  this.width = width
}

function Square(sideLength) {
  // Inherit Polygon's properties.
  // Without Polygon.call (or Polygon.apply),
  // s.hasOwnProperty('height') => false
  Polygon.call(this, sideLength, sideLength)
}

// Inherit Polygon's constructor
// Omitting this results in
// s instanceof Polygon => false
Square.prototype = Object.create(Polygon.prototype)

Square.prototype.area = function() {
  return this.height * this.width
}

let p = new Polygon(2, 2)
// p is an object with own properties 'height' and 'width'
// p.[[Prototype]] is the value of Polygon.prototype when new Polygon is executed

let s = new Square(2)
// s is an object with own properties 'height' and 'width'
// s.[[Prototype]] is the value of Polygon.prototype when new Square is executed

s instanceof Square
// => true
s instanceof Polygon
// => true
p instanceof Square
// => false
Polygon.prototype.isPrototypeOf(s)
// => true

4.2. espree@3.4.3 AST

4.3. Node identification

5. Object.create assignment

5.1. Source code

const a = {a: 1}
// a ---> Object.prototype ---> null

const b = Object.create(a)
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)

const c = Object.create(b)
// c ---> b ---> a ---> Object.prototype ---> null

const d = Object.create(null)
// d ---> null
console.log(d.hasOwnProperty)
// undefined, because d doesn't inherit from Object.prototype

5.2. espree@3.4.3 AST

5.3. Node identification

6. class keyword expressions

Inheritance is simple with espree: look for the superClass<Identifier> property of a ClassDeclaration:

6.1. Source code

class Polygon {
  constructor(height, width) {
    this.height = height
    this.width = width
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength)
  }
  get area() {
    return this.height * this.width
  }
  set sideLength(newLength) {
    this.height = newLength
    this.width = newLength
  }
}

6.2. espree@3.4.3 AST

🔗 View in AST Explorer

Note that the second class -- Square -- has the property superClass<Identifier>:

AST of a class with a prototypical chain

6.3. Node identification


Clone this wiki locally