JavaScript - from Scripting to Object-oriented Programming Language.
Em Ha Tuan
What is scripting programming language?
A scripting language is a type of programming language that is often interpreted and executed at runtime rather than being compiled into machine code before runtime. Scripting languages are generally designed to be lightweight, flexible, and easy to use. They are often used for automating tasks, creating dynamic web pages, and other scenarios where rapid development and flexibility are important.
Is Javascript the scripting programming language?
Yes, JavaScript is indeed a scripting language. It was originally created to enable client-side scripting in web browsers, allowing developers to enhance the interactivity and dynamism of web pages. JavaScript is primarily known for its use in web development, where it runs in web browsers and interacts with the Document Object Model (DOM) to manipulate the content and behavior of web pages dynamically.
Let's examine a simple code snippet written in JavaScript, which is often used as a scripting language:
This HTML file includes a simple JavaScript script that does the following:
- Gets the current hour using
new Date().getHours()
. - Uses a function
getGreeting
to determine the appropriate greeting based on the time of day. - Displays the greeting message in an HTML element with the id "greeting" using
document.getElementById()
.
You can save this code in an HTML file and open it in a web browser to see the greeting message change based on the time of day.
What is Object-oriented Programming?
Object-oriented is a programming paradigm based on the concept of objects.** This concept can contain data and code which data is form of field (attributes or properties) and code is the form of procedures ( methods or functions).
A common feature of objects is that methods are attached to them and can access and modify the object’s data fields (code can access or modify data).
In OOP, we usually see the specific name or syntax that is this
or self
used to refer to the current object and usually following the concepts: Encapsulation, Abstraction, Inheritance and Polymorphism.
Now, let's explore how JavaScript works as a Object oriented programming language.
Javascript is loosely Typed Language.
Loosely-typed the data type from the data that is assigned to it. That mean when we use the let keyword and create a new variable like let name = 'John Wick';
The name variable is become the string
data type. If we change the name value like this name = 200
the data type of name variable is become the number
. Javascript is very to do that if we can do the unit testing with the code like that we are not find the bug until they hit to production.
Furthermore, Javascript does not do any sort of parameter type checking. It allow you pass any value what you want until you get the weird bug on production.
Data Type, actually in Javascript you don’t create a data type. All the data in Javascript is object
data type. But you see you are really create the Person classes. In Javascript, even thought the class
exist on JS but actually it is object
data type.
JavaScript is Prototype-based Language.
JavaScript is often described as a prototype-based programming language. This means that objects can be serve as prototypes for each objects.
What is prototype?
In Javascript, each object has an associated prototype, which is accessed through the internal property [[Prototype]]
. This prototype is the object from which the current object inherits properties - inheritance is achieved through a mechanism called prototype chaining. You can access the prototype of an object using the Object.getPrototypeOf()
method or the __proto__
property (deprecated).
So, what is prototype chaining?
Prototype chaining in JavaScript refers to the mechanism by which objects are linked to other objects through their prototypes, forming a chain-like structure. This chaining allows objects to inherit properties and methods from their prototypes, creating a hierarchical structure known as the "prototype chain” and how prototype chaining works?
- Object Creation: Each object in JavaScript, when created, is linked to a prototype object -
Object.prototype
. This prototype object is referred to by the internal property[[Prototype]]
. - Property and Method Lookup:
- When you access a property or method on an object, JavaScript first looks for that property in the object itself.
- If the property is not found in the object, JavaScript looks in the object's prototype.
john ---> Person.prototype ---> Object.prototype ---> underfined
- This process continues up the prototype chain until the property is found or until the end of the chain is reached.
- Inheritance: Objects inherit properties and methods from their prototypes. If an object doesn't have a particular property, JavaScript looks in its prototype, and so on, until it finds the property or reaches the end of the prototype chain.
In this example, john
doesn't have its own sayHello
method, so it looks in its prototype (Person
) and finds the sayHello
method there.
Now, let's delve into the main context of this post.
Instance Properties and Prototype Properties
When we work with constructor functions and create instance of objects, there are two types of properties associated with these instances: Instance Properties and Prototype Properties. First, let take a look about Instance Properties:
What is Instance Properties?
There are unique to each instance of an object created using a constructor functions. These properties are assigned directly to the object when it is instantiated.
The instance properties are typically set inside the constructor function using this
keyword. Each instance has its own copy of instance properties which consume more memory when there are many instances.
Example:
So now, what is Prototype Properties?
The Prototype Properties are shared among all of constructor function, typically after the constructor is defined. Prototype properties are added outside the constructor function. Since prototype properties are shared, they are more memory efficient when dealing with many instances.
Example:
Now, let summary all the things about Instance Property and Prototype Properties with the example below:
In this example, year
is a prototype property shared among all instance of the Car
constructor. The make
and model
properties are instance properties unique to each Car
instance.
So now, the question is what is the constructor functions? Actually, I can create an object or inherit or do a lot things without it. But, why do we need it and follow as a standard when developing an application in Javascript. Here is the answer.
Constructor functions
Why do we have the constructor, we thinking about the application contains a lot of object. And we have a few problems if we do not use the constructor function.
-
Every time, we create or inherit an object. We need to use or remember the syntax
Object.setPrototypeOf
. This is easy to forget this syntax and it’s make the object which inheritance worry about the detail of its object. -
If we use this approach doesn’t allow us to execute any consistent logic each time a new object is created.
-
Sometime an object is readable objects and it is potentially be used or modified and making issues for all objects that inherit from them.
Now, with the constructor
functions it is going to be fixed all the issues and problem.
- It looks like a regular function in Javascript. We can forget about this.
- With constructor function, it makes the consistent logic and syntax for all the object in large project.
- We can easy to use OO concepts with constructor function and prevent the exception bug.
Firstly, understanding the new Keywords.
So now, what is happening with the new
keywords? Take a look at the code below:
- We define the
Student
object with constructor function. - We use
this
keywords to point some object in memory. - We create a new object -
em
withnew
keywords. - The
new
keywords will bindthis
to the new objectem
. - The
new
keywords will call the function that follow thenew
keyword. - The
new
keyword will return a newly created object.
We can see the key make all this code work is the new
keyword.
As I said when we use the constructor functions. It is make the Object Oriented concept is easy to process in Javascript. Now, we are going to the set and get with setter and getter on constructor functions.
Getter and setter
We have a few ways to make setter and getter work on Javascript object.
- Use
Object.defineProperty
for define a property on object function. This is complex way for creating a property with set and get on Javascript object. We will figure out the easy way later.
- Use Function
Prototype
this is easy way for define a get and set on Javascript with less syntax. Take a look at the example below:
Static properties and Static method
Static properties are properties that belong to the class itself rather than to instances of the class. These properties are shared among all instances of the class and can be accessed without creating an instance. Unlike instance properties, which are specific to each object created from a class, static properties are associated with the class definition.
In Javascript we can create a static property by assign new variable to Object, like this:
Fruit.maxPrice = 100000; Fruit.getDate = () => new Date(); console.log({ maxPrice: Fruit.maxPrice }); // Outputs: { maxPrice: 100000 } console.log(Fruit.getDate()); // Outputs: 2023-12-13T14:19:46.931Z
But, with this way. There are some limited. The problem refer to instance property. Please take look at the code below:
Fruit.getTitle = () => { return this.name + ' - ' + this.color; };
I define the static method - getTitle
and we can access this property by Fruit.getTitle()
but also, we get the result is undefined - undefined
.
Why we have the result like that? Because, when we define a static property in a class or a constructor function, it is related with constructor function itself, not with instances of that constructor. Therefore, the this
does not make sense within the context of static properties or methods. Take a look the example bellow:
As we can see the result, this
is {}
- empty object. That mean we can not access to instance properties of constructor function.
Private properties and methods with Closures
Actually, Javascript does not have native support for private properties and methods in the same way that some other programming languages do. However, there are conventions and patterns in Javascript that can be used to archive a form of encapsulation and create the illusion of privacy. One of them that is Closure and ES6 Classes with Symbols (we will figure out on later). Let talk about Closure.
What is Closure? A closure in Javascript is a feature that allows a function to retain access to variables from its outer (enclosing) scope, even after the outer function has finished executing. In simpler terms, a closure “closes over” the variables from its surrounding environment, preserving them event when the enclosing function is no longer in scope.
Let’s make the private properties and method to understand the closure:
Now, we try to access the tax
and isFree
properties. We will get the value like this:
console.log(watermelon.tax); // Outputs: undefined console.log(watermelon.isFree()); // Outputs: TypeError: watermelon.isFree is not a function.
Inheritances
On Javascript, we don’t have a easy way to make the object inherit from other object. It maybe complex on the way of coding. Let take’s example for more detail. Actually, we have three steps for create inheritance object on Javascript.
First, create a new constructor function which inherit.
In this example below, I create a new Object named Device, it is constructor function and have a isTablet
function prototype for sharing all the object inherit with. Also, I create a Object Iphone
inherit from Device
with the features
is extension of instance property and setFeature
for adding new feature to the features
property on Iphone
object.
Second, copy the
Device
’s prototype toIphone
‘s prototype.
Iphone.prototype = Object.create(Device.prototype);
But, why don’t we do this thing like this:
Iphone.prototype = Device.prototype;
The answer is memory point. If we assign the based constructor functions prototype to the inherit object. They will use the same memory and make based object is incorrect as original if the object inherit do some modified. So, for preventing that we will copy the based function prototype to inherit object by Object.create
.
Now, let create an object Iphone
:
const iphone15 = new Iphone('Iphone 15', 360, 'ip15'); iphone15.setFeature('faceId'); console.log(iphone15); // Outputs: // Device { features: [ 'faceId' ] }
We can see the console is only features
created and what is happening with instance properties. Let’s print out more things about iphone5
object:
console.log(Iphone.prototype); // Outputs: // Device { setFeature: [Function (anonymous)] } console.log(Iphone.prototype.setFeature.toString()); // Outputs: // function(name){ // return this.features.push(name); // } console.log(iphone15.isTablet()); // Outputs: // false
As we can see the Iphone
prototype doesn’t exist the constructors prototype and also the instance properties. We only see the setFeature function on the Iphone object. But we can access to isTablet
prototype. So, what does these mean? The answer is, at the at moment, our code is only inheriting the based object prototype with no constructor make the iphoner15
is incorrect. To fix that, we will inherit the constructor prototype from based object - Device object. We do the code like this:
function Iphone(name, size, model) { Device.call(this, name, size, model); this.features = []; } // ... Iphone.prototype.constructor = Iphone;
With the call
method we can assign any value as this
when calling an existing function. Please refer to: **Function.prototype.call()
** of MDN.
Now, let’s try again:
console.log(iphone15); // Outputs: // Iphone { // name: 'Iphone 15', // size: 360, // model: 'ip15', // features: [ 'faceId' ] // } console.log(iphone15.constructor.toString()); // Outputs: // function Iphone(name, size, model) { // Device.call(this, name, size, model); // this.features = []; // }
As the result, the iphone15
was have the instances property and also have constructor of itself. Maybe, the Iphone
object inherited from Device
object.
But how can we verify that an object inherited from based object? As we know, Javascript is loosely-type programming language, so that mean we doesn’t have create an specific data type as other programming language, all the data type of instance which create from function or class is object
on Javascript. See the example below:
function Fruit() {} const apple = new Fruit(); console.log(typeof apple); // Outputs: // object class Animal {} const duck = new Animal(); console.log(typeof duck); // Outputs: // object
So, to verify the data type of object, we can use instanceof
operator. When we use typeof iphone15
, we only get the result object
. Now let try with instanceof
:
console.log(iphone15 instanceof Iphone); // Outputs: true console.log(iphone15 instanceof Object); // Outputs: true
Why iphone15
is instance of Object
. Please refer to the Prototype chain above the post.
For more information: instanceof
of MDN.
Polymorphism
Polymorphism is the use of a single symbol to represent multiple different types.
Refer to: Wikipedia
Polymorphism allows us to use any object from a particular class or function as if it was that class or data types. And we can do that with Javascript, because it is a loosely-typed programming language. So that mean we can easy do the concept polymorphism on Javascript because it doesn’t have the compiler for type checker. I was creating a function generateTitle
method to return the format name of the Device and Iphone. Look at the code below:
Device.prototype.generateTitle = function () { return `${this.name} - ${this.isTablet() ? 'tablet' : 'mobile'} - ${ this.model }`; }; Iphone.prototype.generateTitle = function () { return `Apple device: ${this.name} - ${this.model}`; }; const samsung = new Device('Samsung Galaxy S7', 360, 'ssgalaxys7'); console.log(samsung.generateTitle()); // Outputs: // Samsung Galaxy S7 - mobile - ssgalaxys7 const iPadGen7 = new Iphone('iPad Gen 7'); console.log(iPadGen7.generateTitle()); // Outputs: // iPad Gen 7 - mobile - undefined
The Device generateTitle
method was overriding by Iphone generateTitle
method and we can see the result of two instances samsung
and iPadGen7
are different. By JavaScript does not have compiler and loosely-typed programming language. So we will not get the error when we miss or given wrong data type.
The most concept on of Polymorphism, that is interface
and abstract
. Unlucky, Javascript does not support these operator for handle Object-Oriented. But we have a lot of common feature to do that.
Abstraction
class AbstractAninmal { constructor() { if (new.target === AbstractAninmal) { throw new Error('Abstract class cannot be instantiated.'); } } isEatMeal() { throw new Error('Method not implemented.'); } }
Interface
As we discussed, Javascript is loosely-typed language, so that means there is not a native “interface” like in some other languages. However, there are different way to achieve interface-like behavior or contracts in Javascript. Here is the one:
class IAnimal { name; color; size; constructor() { if (new.target === IAnimal) { throw new Error('Interface class cannot be instantiated.'); } } }
Javascript Classes
On ES6, we can define an object with a class
on Javascript. With class
, it makes an object defined more clearly in syntax and readable.
/** * Represents a device. * * @constructor * @param {string} name - The name of the device. * @param {string} size - The size of the device. * @param {string} model - The model name of the device. */ class Device { name = ''; size = 0; model = ''; constructor(name, size, model) { this.name = name; this.size = size; this.model = model; } getTitle = function () { return this.name + ' - ' + this.model; }; } /** * Checks if the device is a tablet based on its size. * * @function * @returns {boolean} True if the size is greater than 360 pixels, indicating a tablet. */ Device.prototype.isTablet = function () { return this.size > 360; }; /** * Gets the title of the device. * * @function * @returns {string} The title of the device, combining name, size, and model. */ Device.prototype.generateTitle = function () { return `${this.name} - ${this.isTablet() ? 'tablet' : 'mobile'} - ${ this.model }`; }; /** * Represents an iPhone device, extending the Device class. * * @constructor * @extends Device * @param {string} name - The name of the iPhone. * @param {string} model - The model name of the iPhone. */ class Iphone extends Device { features = []; constructor(name, size, model) { super(name, size, model); this.features = []; } } /** * Sets a feature for the iPhone. * * @function * @param {string} name - The name of the feature to set. */ Iphone.prototype.setFeature = function (name) { this.features.push(name); }; /** * Gets the title of the device. * * @function * @override Override the {Device.generateTitle} function. * @returns {string} The title of the device, combining name, size, and model. */ Iphone.prototype.generateTitle = function () { return `Apple device: ${this.name} - ${this.model}`; }; // Example. const samsung = new Device('Samsung Galaxy S7', 360, 'ssgalaxys7'); console.log(Device.prototype.toString()); const iPadGen7 = new Iphone('iPad Gen 7'); console.log(iPadGen7.generateTitle()); const obj = {}; console.log(Object.getPrototypeOf(obj));
On ES6, we can define an object with a class on Javascript. With class, it makes an object defined more clearly in syntax and readable.
Summary
JavaScript, initially designed as a scripting language for enhancing web page interactivity, has undergone a remarkable evolution, expanding its role from a client-side tool to a versatile, object-oriented programming language. Originally executed in web browsers, JavaScript is now employed both on the client and server sides, thanks to environments like Node.js.
This transition is marked by JavaScript's embrace of object-oriented programming (OOP) principles. While maintaining its scripting language roots, JavaScript introduced features like objects, prototypes, and functions, enabling developers to organize code in a more modular and reusable manner.
In the OOP paradigm, JavaScript supports concepts such as encapsulation, inheritance, and polymorphism. Developers can create classes, instantiate objects, and utilize prototype-based inheritance to build complex systems with clear hierarchies and relationships.
The integration of OOP concepts in JavaScript has significantly enhanced its capabilities, making it a powerful language for building scalable and maintainable applications. Whether creating dynamic web interfaces or implementing server-side logic, JavaScript's journey from a scripting language to an object-oriented programming language underscores its adaptability and widespread adoption in modern software development.
Thank you for reading this post.
See you later.