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.

  • 🧱 Objects: An object bundles related properties and methods so one variable can model a student, an account, or a home.
  • 🏗️ Constructors: A constructor function with this creates many instances from a single reusable blueprint.
  • 🔁 Enumeration: The for/in loop walks every enumerable property, printing keys and values without hardcoding names.
  • 📦 ES6 classes: The class keyword is syntactic sugar; methods still live on the shared prototype object.
  • 🧬 Inheritance: extends links prototypes automatically, and super calls the parent constructor or an overridden method.
  • 🔒 Encapsulation: Fields prefixed with # stay private, hidden from Object.keys and JSON.stringify.
  • ⚙️ Practice: Every example on this page shows verified output, so you can compare results as you learn.

Object Oriented JavaScript (OOJS) concepts with objects, constructors, and classes

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.

FAQs

It creates an empty object, links it to the constructor prototype, runs the constructor with “this” bound to that object, and returns it. If the constructor returns an object explicitly, that value is returned instead.

Use a static method when the logic belongs to the type rather than to any single instance, such as a factory that builds an object from JSON or a helper that compares two instances. Static members are called on the class itself.

Because “this” is decided by how a method is called, not where it is written. Passing a method as a callback loses the receiver. Fix it by binding the method in the constructor or by declaring it as a class field with an arrow function.

No. Declaring two methods with the same name keeps only the last one. Simulate overloading by inspecting arguments.length, by using default parameter values, or by accepting a single options object with named properties.

Yes. AI coding assistants handle that refactor well because the mapping is mechanical. Always review the result, since assistants can miss methods attached to the prototype outside the constructor or drop a call to the parent constructor.

Yes. Generated code still has to be read, reviewed, and debugged. Knowing how prototypes, “this”, and private fields behave is what lets you spot an incorrect suggestion before it reaches production.

Summarize this post with: