JavaScript objects

Table of Contents

Object

  • An object is a collection of properties
  • A property has a name and a value
  • An object can be seen as associative array (map) where the keys in the array are the names of the object’s properties
// Create object as literal
const bob = {
  name: "Bob",
  age: 25
};

// Access properties
console.log(bob.name);               // >> Bob
bob.age = bob.age+1;
bob.age++;
console.log(bob.age);                // >> 27

Object Properties

  • Properties can dynamically be added to an object
  • Properties can be accessed using the dot or index operator
  • Property names that are not valid variable names have to be put in quotes
const bob = {name: "Bob", age: 25};

// Add new properties
bob.hairColor = "black";
console.log(bob.hairColor);          // >> black
console.log(bob.lastname);           // >> undefined

// Access properties using the dot or index operator
console.log(bob.name);               // >> Bob
console.log(bob["name"]);            // >> Bob

// Property with quoted name
const alice = {
  name: "Alice",
  "year of birth": 1991
};
console.log(alice["year of birth"]); // >> 1991

Object Methods

  • An object property that points to a function value is called method
  • Within the method body, the this reference points to the object the method is called on
const bob = {name: "Bob"};

// Add method to object
bob.speak = function(phrase) {
  console.log(this.name+" says: "+phrase);
};
bob.speak("What's up?");        // >> Bob says: What's up?
console.log(bob.speak);         // >> [Function]

// Object literal with method
const alice = {
  name: "Alice",
  speak: function(phrase) {
    console.log(this.name+" says: "+phrase);
  }
};
alice.speak("Great!");          // >> Alice says: Great!

Object Constructor

  • A constructor function is a normal function, but treated as constructor when called with the new operator
  • The this reference points to the new object
// Constructor function
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.speak = function(phrase) {
    console.log(this.name+" says: "+phrase);
  }
}

// Create objects
const alice = new Person("Alice", 19);
const bob = new Person("Bob", 25);

console.log(alice.name);    // Alice
bob.speak("I'm Bob");       // >> Bob says: I'm Bob

Altering Objects

Objects can always be altered even if they were created using a constructor function:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.speak = function (phrase) {
        console.log(this.name + " says: " + phrase);
    }
    this.sleep = function () {
        console.log("CHRRRZ Z Z z z...");
    };
}

const bob = new Person("Bob", 25);

// Alter bob
bob.speak = function () {
    console.log("Sorry, I prefer writing");
}
bob.write = function (phrase) {
    console.log(this.name + " writes: " + phrase);
}
delete bob.sleep;

bob.speak("Bla bla");    // >> Sorry, I prefer writing
bob.write("Bla bla");   // >> Bob writes: Bla bla
bob.sleep();             // ! TypeError: bob.sleep is not a function !

// bob is still a Person
console.log(bob instanceof Person);  // >> true

The this-reference

  • The this reference within a function points to the bound object
  • Functions are implicitly bound to the object the function is called on (methods)
  • Functions can be explicitly bound using bind or call
// speak is not bound ('this' points to the global object or is undefined)
function speak(phrase) {
  console.log(this.name+" says: "+phrase);
}
speak("Hello");           // >> undefined says: Hello
                          // OR
                          // TypeError: Cannot read property name of undefined

const alice = {name: "Alice", speak: speak};
// speak is implicitly bound to alice when called as method
alice.speak("Hello");     // >> Alice says: Hello

// bind creates a new function which is explicitly bound to the passed object
const aliceSpeak = speak.bind(alice);
aliceSpeak("Hello");      // >> Alice says: Hello

// call explicitly binds the function to the passed object and calls it
speak.call(alice, "Hi");  // >> Alice says: Hi

Prototype

  • Most objects have a prototype, which is another object used as fallback source for properties
  • The prototype is often referenced by the __proto__ property (not standardized)
  • Each function has a prototype property which points to an object.
  • This object becomes the prototype of the objects created with the new operator from that function
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// Add functionality to Person's prototype
Person.prototype.speak = function(phrase) {
  console.log(this.name+" says: "+phrase);
};

const alice = new Person("Alice", 19);
const bob = new Person("Bob", 25);

// alice and bob have the same prototype
console.log(alice.__proto__ === bob.__proto__); // >> true

// New functionality can always be added to a prototype
Person.prototype.sleep = function() {
  console.log("CHRRRZ Z Z z z ...");
};

bob.sleep();    // >> CHRRR Z Z Z z z ...
  • Using prototype functions has the advantage that only one function instance is needed for all objects
  • But prototype functions have no access to local variables of the constructor function
function Person(name, age) {
  // Public property
  this.name = name;
  // Local variable (private property)
  const yearOfBirth = new Date().getFullYear()-age;
  // Only functions defined in the constructor function can access local variables
  this.getYearOfBirth = function() {
    return yearOfBirth;
  }
}
Person.prototype.toString = function() {
  // Prototype functions have only access to the public properties of the object
  return this.name+" was bron in "+this.getYearOfBirth();
}
const bob = new Person("Bob", 25);
console.log(bob+"!");   // >> Bob was born in 1996!

Inheritance

  • Prototype properties are inherited over several levels
  • Inheritance is set up by
    • calling the base constructor function to initialize the new object :

function Student(name, age, university) {
Person.call(this, name, age);
// ...
}

    • setting the prototype object of the base constructor function as the prototype of the prototype object of the sub constructor function:

// Student’s prototype object must be replaced by a new object which
// has Person’s prototype object as prototype
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Example1:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.describe = function() {
  console.log(this.name+" is "+this.age);
}
Person.prototype.speak = function(phrase) {
  console.log(this.name+" says: "+phrase);
}

// Student extends Person
function Student(name, age, university) {
  Person.call(this, name, age);
  this.university = university;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sleep = function() {
  console.log("CHRRRZ Z Z z z...");
}
Student.prototype.describe = function(){
  console.log(this.name+" is a student at "+this.university);
};

Example2:

const bob = new Student("Bob", 25, "BFH");
console.log(bob instanceof Student); // >> true
console.log(bob instanceof Person); // >> true
bob.speak("Hi");
bob.describe();
bob.sleep();
// >> Bob says: Hi
// >> Bob is a student at BFH
// >> CHRRRZ Z Z z z...

Inheritance Helper Function

Using a helper function simplifies inheritance and makes it less error prone:

// Helper to correctly set up the prototype chain
function extend(base, sub) {
  const origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (let key in origProto) {
    sub.prototype[key] = origProto[key];
  }
  Object.defineProperty(sub.prototype, 'constructor', {
    enumerable: false,
    value: sub
  });
}
//----------------------------------------

function Prof(name, age) {
  Person.call(this, name, age);
}
Prof.prototype.describe = function() {
  console.log("Prof. "+this.name+" is "+this.age+"years old");
}
extend(Person, Prof);