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);