-
Notifications
You must be signed in to change notification settings - Fork 5
Javascript inheritance identification
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.
- 1. Identifying JavaScript inheritance for ORC Models
- 2. Four definitions of prototypical inheritance
- 3. Syntax constructs
- 4. Constructor functions
-
5.
Object.create
assignment -
6.
class
keyword expressions
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.
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
.
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 a ∈ A,
- The function
C
must include an assignmentthis.a = Exp
or -
P must include an assignment
C.prototype.a = Exp
.
- The function
-
For each m ∈ M,
- function
C
must include an assignmentthis.m = function {Exp}
or -
P must include an assignment
C.prototype.m = function {Exp}
.
- function
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]
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
.
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.
Property inheritance | Notes |
---|---|
Function.prototype.call() |
|
Function.prototype.apply() |
|
this.propertyName assignment |
|
Function.prototype.propertyName assignment |
Method inheritance | Notes |
---|---|
this.methodName = FunctionExpression |
|
Function.prototype.methodName = FunctionExpression |
Method inheritance | Notes |
---|---|
Fxn2.prototype = new Fxn1() |
|
Fxn2.prototype = Object.create(Fxn1.prototype) |
|
Fxn2.prototype.constructor = Fxn2 |
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
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
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
Inheritance is simple with espree
: look for the superClass<Identifier>
property of a ClassDeclaration
:
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
}
}
Note that the second class
-- Square
-- has the property superClass<Identifier>
:
This work is licensed under a Creative Commons Attribution 4.0 International License.