OOJS: Object Oriented JavaScript Tutorial with Example
⚡ Smart Summary
Object Oriented JavaScript models real world entities as objects that carry properties and methods. This page explains object literals, constructor functions, prototypes, ES6 classes, inheritance, and private fields with runnable examples you can execute and verify.

What is OOPS Concept in JavaScript?
Many times, variables or arrays are not sufficient to simulate real-life situations. JavaScript allows you to create objects that act like real life objects. A student or a home can be an object that has many unique characteristics of its own. You can create properties and methods on your objects to make programming easier. If your object is a student, it will have properties like first name, last name, and id, plus methods like calculateRank or changeAddress. If your object is a home, it will have properties like number of rooms, paint colour, and location, plus methods like calculateArea or changeOwner.
Object oriented programming in JavaScript therefore rests on four ideas that you will meet on this page: grouping state and behaviour into an object, reusing a blueprint through a constructor, sharing methods through the prototype, and restricting direct access to internal data. If you are new to the language, start with this introduction to JavaScript and then return here.
How to Create an Object
You can create an object like this:
var objName = new Object(); objName.property1 = value1; objName.property2 = value2; objName.method1 = function() { // line of code }
OR, using the shorter object literal form:
var objName = { property1: value1, property2: value2, method1: function() { // lines of code } };
Access Object Properties and Methods
You can access a property of an object with dot notation, and you can call a method by adding parentheses:
objectname.propertyname; // read a property objectname.methodname(); // call a method objectname["propertyname"]; // bracket notation, useful for dynamic keys
Try this Example yourself:
<html>
<head>
<title>Objects!!!</title>
<script type="text/javascript">
var student = new Object();
student.fName = "John";
student.lName = "Smith";
student.id = 5;
student.markE = 76;
student.markM = 99;
student.markS = 87;
student.calculateAverage = function()
{
return (student.markE + student.markM + student.markS)/3;
};
student.displayDetails = function()
{
document.write("Student Id: " + student.id + "<br />");
document.write("Name: " + student.fName + " " + student.lName + "<br />");
var avg = student.calculateAverage();
document.write("Average Marks: " + avg);
};
student.displayDetails();
</script>
</head>
<body>
</body>
</html>
Output:
Student Id: 5 Name: John Smith Average Marks: 87.33333333333333
💡 Tip: The example above uses document.write because it runs inside the editor on this page. In real projects, prefer console.log for debugging or DOM methods such as textContent, because document.write wipes the page when it is called after loading has finished.
OOPS Constructor
Creating objects one at a time is not that useful, because you would have to repeat the same code for every student. This is where the object constructor comes into the picture. A constructor is an ordinary function that is called with the new keyword; it defines an object type that can be reused for every individual instance.
Inside a constructor, this refers to the new object being built, so each instance receives its own copy of the values passed in. Related values such as marks are often stored in an array so that they can be totalled in a single pass.
Try this Example yourself:
<html>
<head>
<script type="text/javascript">
function Student(first, last, id, english, maths, science)
{
this.fName = first;
this.lName = last;
this.id = id;
this.markE = english;
this.markM = maths;
this.markS = science;
this.calculateAverage = function()
{
return (this.markE + this.markM + this.markS)/3;
}
this.displayDetails = function()
{
document.write("Student Id: " + this.id + "<br />");
document.write("Name: " + this.fName + " " + this.lName + "<br />");
var avg = this.calculateAverage();
document.write("Average Marks: " + avg + "<br /><br />");
}
}
var st1 = new Student("John", "Smith", 15, 85, 79, 90);
var st2 = new Student("Hannah", "Turner", 23, 75, 80, 82);
var st3 = new Student("Kevin", "White", 4, 93, 89, 90);
var st4 = new Student("Rose", "Taylor", 11, 55, 63, 45);
st1.displayDetails();
st2.displayDetails();
st3.displayDetails();
st4.displayDetails();
</script>
</head>
<body>
</body>
</html>
Output:
Student Id: 15 Name: John Smith Average Marks: 84.66666666666667 Student Id: 23 Name: Hannah Turner Average Marks: 79 Student Id: 4 Name: Kevin White Average Marks: 90.66666666666667 Student Id: 11 Name: Rose Taylor Average Marks: 54.333333333333336
Loop Through the Properties of an Object
Syntax:
for (variablename in objectname) { // lines of code to be executed }
The for/in loop is usually used to walk through the properties of an object. You can give any name to the variable, but the object name must match an object that already exists. On each pass, the variable holds the property key as a string, so the value is read with bracket notation such as employee[x].
Try this Example yourself:
<html>
<head>
<script type="text/javascript">
var employee={first:"John", last:"Doe", department:"Accounts"};
var details = "";
document.write("<b>Using for/in loops </b><br />");
for (var x in employee)
{
details = x + ": " + employee[x];
document.write(details + "<br />");
}
</script>
</head>
<body>
</body>
</html>
Output:
Using for/in loops first: John last: Doe department: Accounts
Two points are worth remembering. A for/in loop also visits enumerable properties inherited through the prototype chain, so a guard such as Object.prototype.hasOwnProperty.call(employee, x) is often added. For plain data objects, Object.keys(employee) or Object.entries(employee) is usually clearer, because both return only the object’s own properties.
What Is the ES6 class Syntax in JavaScript?
ECMAScript 2015, commonly called ES6, added the class keyword. A class is syntactic sugar over the prototype system that the constructor function above already uses: no new object model was introduced. Running typeof Student on a class still returns the string “function”, and every method written in the class body is installed on Student.prototype, which means all instances share one copy of that method instead of each object carrying its own.
The syntax also removes some rough edges. Class bodies always execute in strict mode, calling a class without new throws a TypeError instead of silently polluting the global object, and getters, setters, and static members have a dedicated place to live. The result is shorter code that expresses the same intent.
class Student { constructor(first, last, id, english, maths, science) { this.fName = first; this.lName = last; this.id = id; this.marks = [english, maths, science]; } calculateAverage() { const total = this.marks.reduce((sum, m) => sum + m, 0); return total / this.marks.length; } displayDetails() { console.log("Student Id: " + this.id); console.log("Name: " + this.fName + " " + this.lName); console.log("Average Marks: " + this.calculateAverage().toFixed(2)); } } const st1 = new Student("John", "Smith", 15, 85, 79, 90); st1.displayDetails(); console.log(typeof Student); console.log(st1.calculateAverage === Student.prototype.calculateAverage);
Output:
Student Id: 15 Name: John Smith Average Marks: 84.67 function true
The last two lines prove the point: the class is still a function, and the method on the instance is the very same function object stored on the prototype.
Constructor Function vs ES6 Class
| Aspect | Constructor function | ES6 class |
|---|---|---|
| Declaration | function Student(…) { } | class Student { constructor(…) { } } |
| Method placement | Assigned inside the function, so each instance gets its own copy unless you write to Student.prototype | Written in the class body and placed on the prototype automatically |
| Calling without new | Runs silently and this is undefined or the global object | Throws a TypeError immediately |
| Hoisting | Function declarations are hoisted and usable before the definition | In the temporal dead zone until evaluated |
| Strict mode | Follows the surrounding script | Always strict |
| Inheritance | Manual: Object.create plus a call to the parent | extends and super |
| Private data | Closures or a naming convention such as _balance | True private fields with # |
| Underlying model | Prototypes | Prototypes (identical at runtime) |
How to Implement Inheritance Using extends and super
Inheritance lets a specialised type reuse the properties and methods of a more general type. Before ES6, this meant wiring the prototype chain by hand in three separate steps, and it was easy to forget one of them.
function Person(fName, lName) { this.fName = fName; this.lName = lName; } Person.prototype.introduce = function () { return "I am " + this.fName + " " + this.lName; }; function Student(fName, lName, id) { Person.call(this, fName, lName); // 1. borrow the parent constructor this.id = id; } Student.prototype = Object.create(Person.prototype); // 2. link the chain Student.prototype.constructor = Student; // 3. repair constructor var st = new Student("Kevin", "White", 4); console.log(st.introduce()); console.log(st instanceof Person);
Output:
I am Kevin White true
The extends keyword performs all three steps for you, and super gives a clean way to reach the parent. There are two distinct uses of super:
- super(…) as a call runs the parent constructor. It is only valid inside a derived constructor, and it must run before you touch this, otherwise a ReferenceError is thrown.
- super.method() as a property lookup invokes the parent version of a method that the child has overridden, which is how you extend behaviour instead of replacing it.
class Person { constructor(fName, lName) { this.fName = fName; this.lName = lName; } introduce() { return "I am " + this.fName + " " + this.lName; } } class Student extends Person { constructor(fName, lName, id) { super(fName, lName); // must run before "this" is used this.id = id; } introduce() { return super.introduce() + ", roll number " + this.id; } } const st = new Student("Hannah", "Turner", 23); console.log(st.introduce()); console.log(st instanceof Student, st instanceof Person); console.log(Object.getPrototypeOf(Student.prototype) === Person.prototype);
Output:
I am Hannah Turner, roll number 23 true true true
The third line is the important one. It confirms that extends produced exactly the prototype link the manual version built by hand, so classes did not replace prototype chaining. They simply hid the boilerplate. When a property is read from st, the engine looks at the instance, then Student.prototype, then Person.prototype, then Object.prototype, and stops at the first match; that search path is the prototype chain.
⚠️ Warning: Never assign Student.prototype = Person.prototype. That makes both types share one object, so any method you add to the child is also added to the parent. Use Object.create(Person.prototype), or simply use extends.
How to Achieve Encapsulation with Private Class Fields
Encapsulation means keeping internal state out of reach so that it can only change through methods you control. For years JavaScript developers faked it with a leading underscore, but a name such as _balance is only a convention and any code can still overwrite it. Modern JavaScript offers real privacy: a field whose name begins with # is accessible only inside the class body that declares it.
Private fields bring three concrete benefits:
- They must be declared in the class body before use, which documents the internal state of the type in one place.
- They are invisible to Object.keys, JSON.stringify, and for/in, so internal state does not leak into serialised output.
- Touching a private field from outside the class is a syntax error, caught before the code ever runs, rather than a silent bug.
class BankAccount { #balance = 0; constructor(owner, opening) { this.owner = owner; this.#balance = opening; } deposit(amount) { if (amount <= 0) { return "Deposit must be positive"; } this.#balance += amount; return this.owner + " balance: " + this.#balance; } get balance() { return this.#balance; } } const acc = new BankAccount("John", 500); console.log(acc.deposit(250)); console.log(acc.deposit(-40)); console.log("Read via getter: " + acc.balance); console.log("Own keys: " + Object.keys(acc)); console.log("JSON: " + JSON.stringify(acc));
Output:
John balance: 750
Deposit must be positive
Read via getter: 750
Own keys: owner
JSON: {"owner":"John"}
The balance never appears in Object.keys or in the JSON string, and the negative deposit is rejected by the method instead of corrupting the field. The get balance() accessor exposes a read-only view, which is the usual way to publish private state safely.
Now that objects, prototypes, classes, inheritance, and encapsulation make sense, keep building. Practise the fundamentals with these practical JavaScript code examples, work through interactive JavaScript exercises, apply classes to algorithms such as quicksort in JavaScript, tidy up output using JavaScript string formatting, and see how static typing changes object design in TypeScript.
