-
Notifications
You must be signed in to change notification settings - Fork 9
Javascript Objects
Object Oriented Programming (OOP) refers to using self-contained pieces of code to develop applications. We call these self-contained pieces of code objects, better known as Classes in most OOP programming languages and Functions in JavaScript. We use objects as building blocks for our applications. Building applications with objects allows us to adopt some valuable techniques, namely, Inheritance (objects can inherit features from other objects), Polymorphism (objects can share the same interface—-how they are accessed and used—-while their underlying implementation of the interface may differ), and Encapsulation (each object is responsible for specific tasks).
Up until now, we've been working mostly with Primitive Data Types: strings, numbers, booleans, undefined, null etc.
If you type: typeof "hello";
into the console, it will return "string"
if you type: typeof [1, 2, 3];
into the console, you might expect it to return "array"
. However, it returns "object"
.
An object is a data structure in javascript that lets you store any type of data value about a particular thing, and helps you keep track of that data by using a "key".
To wrap our heads around how to think about objects in OOP, you might start with the question how do you define a person? What is a person? A value containing just a name wouldn't be enough.
var sisterName = "Sarah";
They also have other characteristics
var sisterName = "Sarah";
var sisterAge = 23;
var sisterParents = ["Alice", "Susan"];
var sisterSiblings = ["Ron", "Jim Bob"];
var sisterFavoriteColor = "purple";
var hasPets = true;
Instead of defining all of the information about a person into isolated variables, objects allow you to group this information into a meaningful structure.
Here is an object that is stored into a variable called sister using key value pairs:
var sister = {
name: "Sarah",
age: 23,
parents: ["Alice", "Susan"],
siblings: ["Ron", "Jim Bob"],
favoriteColor: "purple",
hasPets: true
};
This syntax is called Object-literal notation. Some important things to remember when structuring an object literal:
- The "key" (representing a property or method name) and it's "value" are separated from each other by a colon
- The key: value paris are separated from each other by commas
- The entire objects is wrapped inside curly braces {}
- Don't use numbers as the first character in your property names
- Avoid using spaces or hyphens in property names, camel casing is the preferred multi-word naming convention
Just like how you can look up a word in the dictionary to find its definition, the key in key:value pairs allows you to look up a piece of information about an object.
Here are two equivalent ways to use the key to return its value
sister["parents"]
//returns ["Alice", "Susan"]
sister.parents
// also returns ["Alice", "Susan"]
The first instance is called bracket notation, and the second is called dot notation.
You can also include methods. Right now we have a bunch of properties in our data object, but they don't really say what our object does. If we wanted to include paintPicture()
method that returns "Sarah paints a picture!"
whenever you call it, you can use pretty much the exact same syntax as when you were defining properties, the only difference being the value in the key:value pair will be a function.
var sister = {
name: "Sarah",
age: 23,
parents: ["Alice", "Susan"],
siblings: ["Ron", "Jim Bob"],
favoriteColor: "purple",
hasPets: true,
paintPicture: function() { return "Sarah Paints!"; }
};
sister.paintPicture(); //returns Sarah Paints!
Methods are simply properties that hold function values
Ex. 1
var rabbit = {}; // creates an object called rabbit
rabbit.speak = function(line) {
console.log("The rabbit says '" + line + "'");
}; // uses dot notation to add a method called speak to rabbit object
//Call method from object
rabbit.speak("I'm alive.");
// --> the rabbit says "I'm alive."
Ex. 2
/* Add a method called printAccountSummary that returns the following message:
Welcome!
Your balance is currently $1000 and your interest rate is 1%.
*/
var savingsAccount = {
balance: 1000,
interestRatePercent: 1,
deposit: function addMoney(amount) {
if (amount > 0) {
savingsAccount.balance += amount;
}
},
withdraw: function removeMoney(amount) {
var verifyBalance = savingsAccount.balance - amount;
if (amount > 0 && verifyBalance >= 0) {
savingsAccount.balance -= amount;
}
},
// your code goes here
printAccountSummary: function() {
return ("Welcome!\nYour balance is currently $" + savingsAccount.balance +
" and your interest rate is " + savingsAccount.interestRatePercent + "%.");
}
};
console.log(savingsAccount.printAccountSummary());
savingsAccount.withdraw(50.00);
In this example, the code uses the "this" keyword to output the type of rabbit that is speaking:
function speak(line) {
console.log("The " + this.type + " rabbit says '" + line + "'");
}
//Create objects
var whiteRabbit = {type: "white", speak: speak};
var fluffyRabbit = {type: "fluffy", speak: speak};
whiteRabbit.speak("Oh my ears and whiskers, how late it's getting!");
// --> logs: The white rabbit says 'Oh my ears and whiskers, how late it's getting!'
fluffyRabbit.speak("Don't hate me cuz you aint me!");
// --> logs: The fluffy rabbit says 'Don't hate me cuz you aint me'
The this keyword is a self-referencing variable that can be used inside of functions. It refers to the object that the function is being acted upon. In the example above, when the whiteRabbit or fluffyRabbit objects (with type "white" or "fluffy" respectively) use the function speak()
, the function speak looks up the value for the key type in the object it is being applied to using this.type
. this
lets the computer know speak()
function wants to access the properties of the object to which it is being applied.
Almost all objects have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, it's prototype will be searched for the property. The ancestral prototype behind almost all objects is Object.prototype
. This prototype provides a few methods that show up in all objects, such as toString
, which converts an object to a string representation.
Many objects don't directly have Object.prototype
as their prototype, but instead have another object, which provides its own default properties. For example, functions derive from Function.prototype
, and arrays derive from Array.prototype
You can use Object.create
to create an object with a specific prototype
var protoRabbit = {
speak: function(line) {
console.log("The " + this.type + " rabbit says '" + line + "'");
}
};
//create an individual rabbit object
var killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEEE!");
// --> The killer rabbit says 'SKREEEE!'
The "proto" rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the killer rabbit, contains properties that apply only to itself--in this case its type--and derives shared properties from its prototype.
A more convenient way to create objects that derive from some shared prototype is to use a constructor. In Javascript, calling a function with the "new"
keyword in front of it causes it to be treated as a constructor. The constructor will have its "this" variable bound to a fresh object, and unless it explicitly returns another object value, this new object will be returned from the call.
An object created with "new" is said to be an "instance" of its contructor.
// It is convention to capitalize the names of constructors so that they are easily distinguished from other functions.
function Rabbit(type) {
this.type = type;
}
var crazyRabbit = new Rabbit("crazy");
var blackRabbit = new Rabbit("black");
console.log(blackRabbit.type);
// --> black
//Constructors automatically get a property named prototype, which be default holds a plain, empty object that drives from Object.prototype.
Rabbit.prototype.speak = function(line) {
console.log("The " + this.type + " rabbit says '" + line + "'");
};
blackRabbit.speak("Hello...");
// --> The black rabbit says 'Hello...'
The two important principles with OOP in JavaScript are Object Creation patterns (Encapsulation) and Code Reuse patterns (Inheritance).
Objects can be thought of as the main actors in an application, or simply the main “things” or building blocks that do all the work. As you know by now, objects are everywhere in JavaScript since every component in JavaScript is an Object, including Functions, Strings, and Numbers. We normally use object literals or constructor functions to create objects.
Encapsulation refers to enclosing all the functionalities of an object within that object so that the object’s internal workings (its methods and properties) are hidden from the rest of the application. This allows us to abstract or localize specific set of functionalities on objects.
Inheritance refers to an object being able to inherit methods and properties from a parent object (a Class in other OOP languages, or a Function in JavaScript).
Both of these concepts, encapsulation and inheritance, are important because they allow us to build applications with reusable code, scalable architecture, and abstracted functionalities. Maintainable, scalable, efficient.
When building applications, you create many objects, and there exist many ways for creating these objects: you can use the ubiquitous object literal pattern: var myObj = {name: "Richard", profession: "Developer"};
You can use the prototype pattern, adding each method and property directly on the object’s prototype. For example:
function Employee () {}
Employee.prototype.firstName = "Abhijit";
Employee.prototype.lastName = "Patel";
Employee.prototype.startDate = new Date();
Employee.prototype.signedNDA = true;
Employee.prototype.fullName = function () {
console.log (this.firstName + " " + this.lastName);
};
var abhijit = new Employee () //
console.log(abhijit.fullName()); // Abhijit Patel
console.log(abhijit.signedNDA); // true
You can also use the constructor pattern, a constructor function (Classes in other languages, but Functions in JavaScript). For example:
function Employee (name, profession) {
this.name = name;
this.profession = profession;
} // Employee () is the constructor function because we use the <em>new</em> keyword below to invoke it.
var richard = new Employee (“Richard”, “Developer”) // richard is a new object we create from the Employee () constructor function.
console.log(richard.name); //richard
console.log(richard.profession); // Developer
In the latter example, we use a custom constructor function to create an object. This is how we create objects when we want to add methods and properties on our objects, and when we want to encapsulate functionality on our objects. JavaScript developers have invented many patterns (or ways) for creating objects with constructor functions. And when we say Object Creation Patterns, we are concerned principally with the many ways of creating objects from constructor functions, as in the preceding example.
In addition to the patterns for creating objects, you want to reuse code efficiently. When you create your objects, you will likely want some of them to inherit (have similar functionality) methods and properties from a parent object, yet they should also have their own methods and properties. Code reuse patterns facilitate ways in which we can implement inheritance.
These two universal principles—creating objects (especially from constructor Functions) and allowing objects to inherit properties and methods—are the main concepts with OOP in JavaScript.
When you simply want to create an object just to store some data, and it is the only object of its kind, you can use an object literal and create your object. This is quite common and you will use this simple pattern often.
However, whenever you want to create objects with similar functionalities (to use the same methods and properties), you encapsulate the main functionalities in a Function and you use that Function’s constructor to create the objects. This is the essence of encapsulation. And it is this need for encapsulation that we are concerned with and why we are using the Combination Constructor/Prototype Pattern.
To make practical use of OOP in JavaScript, we will build an object-oriented quiz application that uses all the principles and techniques we learn in this article. First up, our quiz application will have users (a Users Function) who take the quiz. There will be some common properties for every user who takes the quiz: each user will have a name, a score, an email, and the quiz scores (all the scores). These are the properties of the User object. In addition, each User object should be able to show the name and score, save scores, and change the email. These are the methods of the object.
Because we want ALL the user objects to have these same properties and methods, we cannot use the object literal way of creating objects. We have to use a constructor Function to encapsulate these properties and methods.
Since we know all users will have the same set of properties, it makes sense to create a Function (Class in OOP languages) that encapsulates these properties and methods. Thus, we will use the Combination Constructor/Prototype Pattern for this.
//The User Function
function User (theName, theEmail) {
this.name = theName;
this.email = theEmail;
this.quizScores = [];
this.currentScore = 0;
}
User.prototype = {
constructor: User,
saveScore:function (theScoreToAdd) {
this.quizScores.push(theScoreToAdd)
},
showNameAndScores:function () {
var scores = this.quizScores.length > 0 ? this.quizScores.join(",") : "No Scores Yet";
return this.name + " Scores: " + scores;
},
changeEmail:function (newEmail) {
this.email = newEmail;
return "New Email Saved: " + this.email;
}
}
// Make Instances of the User function
// A User
firstUser = new User("Richard", "[email protected]");
firstUser.changeEmail("[email protected]");
firstUser.saveScore(15);
firstUser.saveScore(10);
firstUser.showNameAndScores(); //Richard Scores: 15,10
// Another User
secondUser = new User("Peter", "[email protected]");
secondUser.saveScore(18);
secondUser.showNameAndScores(); //Peter Scores: 18
We have encapsulated all the functionality for a User inside the User Function, so that each instance of User can make use of the prototype methods (like changeEmail) and define their own instance properties (like name and email).
Inheritance is a way to create a class as a specialized version of one or more classes (JavaScript only supports single inheritance). The specialized class is commonly called the child, and the other class is commonly called the parent. In JavaScript you do this by assigning an instance of the parent class to the child class, and then specializing it.
Implementing inheritance in our quiz application will permit us to inherit functionality from parent Functions so that we can easily reuse code in our application and extend the functionality of objects. Objects can make use of their inherited functionalities and still have their own specialized functionalities. To inherit both properties and methods, we will need to utilize two functions.
To create an object that inherits the properties of another object, we can use the Object.create method, you pass into it an object that you want to inherit from, and it returns a new object that inherits from the object you passed into it. For example:
// We have a simple cars object
var cars = {
type:"sedan",
wheels:4
};
We want to inherit from the cars object, so we do:
var toyota = Object.create (cars); // now toyota inherits the properties from cars
console.log(toyota.type); // sedan
Of course we can now add more properties to the toyota object.
Javascript does not detect the child class prototype.constructor, so we must state that manually.
In the example below, we define the class Student
as a child class of Person
. Then we redefine the sayHello()
method and add the sayGoodBye()
method.
// Define the Person constructor
var Person = function(firstName) {
this.firstName = firstName;
};
// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
console.log("I am walking!");
};
Person.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName);
};
// Define the Student constructor
function Student(firstName, subject) {
// Call the parent constructor, making sure (using Function#call)
// that "this" is set correctly during the call
Person.call(this, firstName);
// Initialize our Student-specific properties
this.subject = subject;
};
/* Create a Student.prototype object that inherits from Person.prototype.
NOTE: A common error here is to use "new Person()" to create the
Student.prototype. That's incorrect for several reasons, not least
that we don't have anything to give Person for the "firstName"
argument. The correct place to call Person is above, where we call
it from Student. */
Student.prototype = Object.create(Person.prototype); // See note below
// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;
// Replace the "sayHello" method
Student.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName + ". I'm studying "
+ this.subject + ".");
};
// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
console.log("Goodbye!");
};
// Example usage:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
console.log(student1 instanceof Person); // true
console.log(student1 instanceof Student); // true
http://eloquentjavascript.net/06_object.html
http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Basics