JavaScript applications create and delete many objects as they run. Sometimes these objects are quite different, and sometimes they refer to the same concept, but different data. When it comes to concepts from a given subject area (or, as they say, entities), it's important to have an abstraction that hides the structure of that object from us.
Take the concept of a “company” and build an abstraction around it without using encapsulation:
// The real thing will be much more complex
// file: company.js
// Constructor (in the general sense of the word)
const make = (name, website) => {
return { name, website };
};
// Selectors
const getName = (company) => company.name;
const getWebsite = (company) => company.website;
Now the usage:
import { make, getName } from './company.js';
const company = make('Hexlet', 'https://hexlet.io');
console.log(getName(company)); // Hexlet
This abstraction makes it easier to work with companies (especially when the structure changes), hides implementation details, and makes the code more “human”. Let's try to do the same using encapsulation:
// The real thing will be much more complex
// file: company.js
const make = (name, website) => {
return {
name,
website,
getName() {
return this.name;
},
getWebsite() {
return this.website;
},
};
};
And usage:
import { make } from './company.js';
const company = make('Hexlet', 'https://hexlet.io');
console.log(company.getName()); // Hexlet
Here we can see a number of convenient points compared to the version on the functions:
- We no longer need to import
getName
, it's already contained within the company - You can use auto-complete code
But the pros always come with the cons. Take a closer look at the constructor code. It is expected that every call to the constructor would result in the creation of a new object, but we surely don't want to add new methods each time an object is formed. Methods, unlike normal data, don't change. There's no point in creating them anew for each call, it wastes memory and CPU time.
Let's rewrite our example in a way that means that methods aren't constantly created:
// Don't forget that we need regular functions, not arrow functions!
function getName() {
return this.name;
};
function getWebsite() {
return this.website;
};
// In terms of usage, nothing has changed, but functions are no longer copied
const make = (name, website) => {
return {
name,
website,
getName,
getWebsite,
};
};
New operator
All of the above methods of creating objects have a right to exist and are used in real life, but JavaScript has built-in support for generating objects. Let's rewrite our example using a constructor function.
// This function is usually called a constructor (although technically, it's a normal function with a context)
// Constructors are written with a capital letter
function Company(name, website) {
this.name = name;
this.website = website;
// Methods are still defined externally as normal functions
this.getName = getName;
this.getWebsite = getWebsite;
};
Now the usage:
const company = new Company('Hexlet', 'https://hexlet.io');
console.log(company.getName()); // Hexlet
The most interesting thing in this example is the new
operator (like many things in JS, it doesn't work like new
in other languages). It actually creates an object, sets it as a context during a constructor call (in this case: Company
), and returns the created object. This is why the constructor itself doesn't return anything (it can, but that's another discussion), but inside the company
constant we have the object we need.
// A simplified illustration of how new works inside the interpreter when called like this:
// new Company();
const obj = {};
Company.bind(obj)(name, website); // this call simply fills this (equal to obj) with the desired data
return obj;
This method doesn't appear any better than the prior manual creation, but it makes use of another key JavaScript mechanism: prototypes (more about them in the next lesson).
In JavaScript, constructors are available for all data types that may be represented by objects or are objects in and of themselves, such as functions. Sometimes, they replace a specific data creation syntax (as in the case of arrays), and sometimes, they're the only way to create data of that type (e.g., dates):
// Special syntax for creating arrays
// Arrays are objects, remember the length property
const numbers = [10, 3, -3, 0]; // literal
// Object method of creation via constructor
// The result below is equivalent to the one above
const numbers = new Array(10, 3, -3, 0);
// Dates have no literals, they're created as objects
const date = new Date('December 17, 1995 03:24:00');
// Dates have a lot of methods
date.getMonth(); // 11, in JS months are numbered from zero
// Even functions can be created this way
// the last argument is the body, all the previous ones are arguments
const sum = new Function('a', 'b', 'return a + b');
sum(2, 6); // 8
But not all functions can be constructors. The lack of its own context makes it impossible to use the operator new
together with arrow functions:
const f = () => {};
// TypeError: function is not a constructor
const obj = new f();
Recommended materials
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.