Custom Type

All about Custom Types in Javascript


Before class was introduced to Javascript, programmers had to use Custom Type in order to achieve Object Oriented Programming (OPP).

This concept will allow us to go deeply on the fundamentals of Objects and understand how Objects are created and linked to each other.

Object Type

In JavaScript, functions are objects.

Do not confuse: many of the built-in constructors, like Object, Array, and Function, are actually functions. Thus Object is type of 'function'

function greet(name) {
  return `Hello, ${name}`;
}
 
typeof greet  // 'Object'
typeof Object // 'function'

The Object (even though it is a function) can have several properties:

Object.assign
Object.create
Object.hasOwn
Object.freeze
Object.seal
...more

And every Object has prototype properties:

Object.prototype.__defineGetter__;
Object.prototype.__defineSetter__;
Object.prototype.constructor();
Object.prototype.hasOwnProperty();
Object.prototype.isProtoTypeOf();
...more

How to create an Object

Let's analyze how the creation of a Object looks under the hood.

Object Constructor Function
  • The builder of all Objects (already created by default)
  • Creates the prototype object
  • Has a property called prototype that points to a Object.prototype
new Object

Allows to create a new Object based on the constructor function

let object1 = new Object();
let object2 = new Object();
  • The newobject1 is created with a property: __proto__
  • __proto__ object points to its parent Object.prototype
  • All new Objects created share the same Object.prototype
Object.prototype

It's the parent of all Objects. There is only one Object.prototype (already created).

All new Objects created should point to this. This has all the default properties of an object, which can be overriden by a new Object (allowed by Inheritance!!)

Note that also it has a property called __proto__ set to null

Useful Object's Methods

Assume the Prototype Chain (LinkedList):

cheesecakedessertObject.prototypecheesecake \rightarrow dessert \rightarrow Object.prototype

Object.create(obj)

Allows to create a child Object from a parent Object

let cheesecake = Object.create(dessert);

Object.getOwnPropertyNames(obj)

Returns all properties of the object passed, as string[]

Object.getOwnPropertyNames(cheesecake)
// [constructor, name, calories, minCalories, displayDessert, ...]
 
Object.getOwnPropertyNames(dessert)
// [constructor, minCalories, displayDessert, showDessert, ...]
 
Object.getOwnPropertyNames(Object.prototype)
// [constructor __defineGetter__, __defineSetter__, hasOwnProperty, ...]

Object.getPrototypeOf(obj)

Return the Prototype of the object passed

Object.getPrototypeOf(cheesecake) // returns dessert
Object.getPrototypeOf(dessert) // returns Object.prototype

Object.isPrototypeOf(obj)

dessert.isPrototypeOf(cheesecake) // True
Object.prototype.isPrototypeOf(dessert) // True
cheesecake.isPrototypeOf(Object.prototype) // False

Chain of Properties

Now, let's discuss how chain of properties works in Objects, which is kinda weird but the diagram and example will help.

Notice that all these new Objects created share the same __proto__. Thus, they all share the same properties from Object.prototype.

Example

Assume a new object3 points to another object1, which points to Object.prototype.

We want to access the property toString, which is not declared in any of the new objects created.

To search toString property, traversing is done from bottom (child) to up (parent). The Object.prototype is the parent of all objects, which is the end of the traverse (end of the chain)

Here is the code.

function findProperty(src, toString) {
  let currObj, answer;
 
  /* Traversing the prototype chain */
  currObj = src;
  while (currObj !== null) {
    let currObjProperties = Object.getOwnPropertyNames(currObj);
    let found = currObjProperties.find(elem => elem === toString);
      if (found) {
        answer = found
        return answer;
      }
      currObj = Object.getPrototypeOf(currObj); // go parent object
  }
  return answer; // if not found, return undefined
}

Function Properties

arguments.length

Returns the number of arguments passed to the function.

function generateResults(x,y) {
    console.log(arguments.length)
    console.log(arguments[0]);
    console.log(arguments[1]);
}
 
generateResults();        // Output: 0, undefined, undefined
generateResults("Hello"); // Output: 1, "Hello", undefined
generateResults(1,2);     // Output: 2, 1, 2

function.length

Returns the number of parameters that the function is expected to take.

function generateResults(x) {
    console.log(x);
}
generateResults.length // 1
 
function moreResults(x,y) {
    console.log(x, y);
}
moreResults.length // 2

function.valueOf()

Returns the function in plain text

generageResults.valueOf()
// -> 'function generateResults(x) { console.log(x); }'

this

In Java, this references the current Object we are operating. this is fixed (does not change)

In Javascript, this also references the current Object we are operating, but this changes where is pointing to depending on the scenerio.

Note:

By default this is referencing to window

Allows associating functions to an object at runtime

Can set this using apply(), call(), or bind()

window.singer = "Lionel"
 
function printInfo() {
    console.log(this.singer); 
    // this -> the context where this function will work
}
 
printInfo() // "Lionel"
// this -> references to window
 
let obj = new Object();
obj.singer = "Elvis"
obj.singerInfo = printInfo()
 
obj.singerInfo() // "Elvis"
// this -> references to object
 

apply()

Allows to specify to the function what this is refering to

function.apply(obj , args)

Example

let obj = new Object()
obj.terpConstant = 20
window.terpConstant = 10
function product(x,y){ return x * y * this.terpConstant }
 
product(2,5) // `this` referes to `window` // 2*5*10 = 100
product.apply(this, 3, 3) // `this` refers to `window` // 3*3*10 = 90
product.apply(obj, 3, 3) // `this` refers to obj // 3*3*20 = 180

call()

Similar as .apply(), useful for Inheritance to populate superclass/parent-class

function.call(object, args)

Example

product.call(this, 3, 3) // `this` refers to `window` // 3*3*10 = 90
product.call(obj, 3, 3) // `this` refers to obj // 3*3*20 = 180

bind()

The bind() method creates a new function that, when called, has its this keyword set to the first arugment. The args allows partially apply arguments, similar to currying functionality.

function.bind(object, args)

Example

const obj = {name: 'Jose'};
 
function greet(greeting, punctuation) {
  console.log(greeting + this.name + punctuation);
}
 
const boundGreet = greet.bind(obj, "Hello, ");
boundGreet("!");  // -> "Hello, Jose!"

Function Class without class keyword 🔨

A function can also work as a Class. By convention, a function class name starts with Capital Letter.

Warning:

Doing in this way, if we were creating 50 instances of Computer, then we were creating 50 instances of getMemory and setMemory functions.

So this method is inefficient. Can we do better? yes, Sharing Prototypes

// constructor function / Custom Type for Computer
function Computer(model, memory) {
    this.model=model;
    this.memory=memory;
    // DO NOT USE LAMBDA WITH 'THIS'
    this.getMemory = function(){ return this.memory }
    this.setMemory = function(newMemory){ this.memory=newMemory }
}
 
Computer("mac", 100); // properties are set to 'window' object
 
// Create an object from constructor function
let pc = new Computer("PC", 30); // properties are set to 'pc' object
pc.setMemory(100); // property set to 'pc' object

Sharing Prototypes 🤝

We can set these instance functions to the parent Object.prototype, so that all new objects created shares the same common function, without repeately creating one each time a new object is created.

Note:

Only set to .prototypes properties that share in common between all objects. If want to set a property that is unique between all objects, set it into function constructor

/* function constructo / Custom Type for Car */
function Car(model, year) {
    this.model=model;
    this.year=year;
}
// Car.prototype.model = "ford" // DO NOT DO THIS
// Car.prototype.year = "2020" // DO NOT DO THIS
Car.prototype.getYear = function(){ return this.year }
Car.prototype.setYear = function(newYear){ this.year=newYear }

How atually defining a Function Class

A long story just to get here. Defining a function class, the right way!, without class keyword

// Constructor function / Custom Type for Student
function Student(name, credits, courses){
    this.name=name;
    this.credits=credits;
    this.courses=[...courses];
}
 
// Set Common Properties for all Objects / Static members / Class variables
Student.prototype = {
    constructor: Student, // it says: I belong to this constructor
    college: "UMCP",
    info: function() {
      document.writeln(`
        ${this.name}, ${this.credits}, ${this.courses}, ${this.college} `)
    }
};
 
// Create Unique Students
let student1 = new Student("Kelly", 15, [414,420]);
let student2 = new Student("Jose", 42, [414,420]);
let student3 = new Student("Kevin", 1, [414,420]);
let student4 = new Student("Martin", 60, [414,420]);

Function Inheritance

To understand Inheritance, imagine two custom type, one is a subset of another. All common properties of a Student are shared with GradStudents, such as:

  • ID
  • Name
  • Credits

But GradStudents may have properties that doesn't have all Students

  • Office Room
  • Office Hours
  • Bachelor's degree
  • Thus, GradStudent is a subset of Student set.
/*------ Grad Class / Custom Type for Grad ------*/
function GradStudent(name, credits, courses, advisor) {
    // Calls super class constructor
    Student.call(this, name, credits, courses);
	// This says: call Student Class, this = GradStudent, pass arguments
 
    this.advisor = advisor;
}
 
// Set Static members for all instances GradStudent
GradStudent.prototype = new Student();
GradStudent.prototype.constructor = GradStudent;
GradStudent.prototype.getAdvisor = function() { return this.advisor; }
 
// Create Unique Grads
let graduateStudent1 = new GradStudent("Kelly", 15, [414, 420], "Dr. Smith");
let graduateStudent2 = new GradStudent("Wiley", 15, [631, 632], "Dr. Will");