Did you know that private
, protected
and public
are reserved words in JavaScript?
Really.. I mean it!!
Take a look at this MDN page – Reserved Identifiers.
Oh Wow! It means I can create private, protected fields and methods in JavaScript? Sadly NO!
These have been reserved since ages but JavaScript hasn’t used them.
Oh! So how do I create private field in JavaScript?
The answer is – It Depends!
Depends on what?
Depends on which version of ECMAScript you are writing code in.
Let's first understand why everything is public in JavaScript
JavaScript does NOT have classes (yea, yea.. that class keyword is just sugar coating)
JavaScript is a “prototype” object oriented language, which means there are ONLY objects.
Watch this DigitalCV video to find out How JavaScript Dictionaries look like
And guess what these objects are? DICTIONARIES! That too with all members being visible, though you can change some properties to be non-enumerable, but it doesn’t really hide them.
So how do we hide properties of an object?
Even Object is made up of properties (keys in its dictionary)
Every property is again a dictionary (called Descriptors)
There are 4 descriptors
Descriptor | Description |
---|---|
value | value of the property |
writable | if false, the property is readonly |
enumerable | if false, the property does not show up in for..in or console.log |
configurable | if false, writable and enumerable can no longer be changed |
Let's try hiding by property descriptor
/*
* We are using old style for creating objects
* Actually the "true" style as there is no class in JS
*/
function Person(id, name) {
// Property descriptor for id property
const idPropertyDescriptor = {
value: id,
writable: true,
enumerable: false
};
Object.defineProperty(this,
"id",
idPropertyDescriptor
);
// Property descriptor for name property
const namePropertyDescriptor = {
value: name,
writable: false,
enumerable: true
};
Object.defineProperty(this,
"name",
namePropertyDescriptor
);
}
// Let's create an object of type Person
let eich = new Person(1, "Brendan");
// id is enumerable false, hence won't show up
console.log('Printing entire object');
console.table(eich);
// But id exists and CAN be changed and printed
eich.id = 7;
// However, name is read-only... wont change
console.log('Trying to change name to Eich')
eich.name = "Eich";
// So id is not really private, just hidden
console.log('Printing properties by name')
console.log(`id=${eich.id}, name=${eich.name}`);
And.. here is the output!
Printing entire object
┌─────────┬───────────┐
│ (index) │ Values │
├─────────┼───────────┤
│ name │ 'Brendan' │
└─────────┴───────────┘
Trying to change name to Eich
Printing properties by name
id=7, name=Brendan
Nope! Enumeration does not really make the property private, it merely “tries” to hide it, and that too is not very successfully because if you know the name, you can not only print it but even modify it.
We failed! 😔
Closures to the rescue
Huh? What is a closure?
When a function can “capture” (or close over) variables from outer functions, it creates a “closure” scope.
/*
* Function is a way of creating objects in JS
* Think of Account() as cosntructor
*/
function Account(id, name, bal) {
// properties of the object
this.id = id;
this.name = name;
// This is NOT a property
// Local variable to Account
let balance = bal;
this.deposit = function(amount) {
// deposit "closes over" balance
balance += amount;
}
this.withdraw = function(amount) {
// balance is NOT a property
// but local variable of outer function
balance -= amount;
}
this.print = function print() {
console.log(`ID=${this.id},
Name=${this.name},
Balance=${balance}`);
}
};
Now lets runs this code
var eich = new Account(1, "Brendan", 10000.00);
eich.print();
eich.deposit(1000);
eich.print();
eich.withdraw(500);
eich.print();
And here is the output
ID=1, Name=Brendan, Balance=10000 ID=1, Name=Brendan, Balance=11000 ID=1, Name=Brendan, Balance=10500
Wait a minute… balance is behaving just like a field. We can update it, print it and YET it’s NOT a property of the object. So where is it?
The answer is – “It’s NOT inside the object dictionary but has been put in a different dictionary called “closure”, which is available to deposit and withdraw functions when they are called.
Sweet! So it it hidden from us? Like a private field? YES!
console.log(eich);
Account {
id: 1,
name: 'Brendan',
deposit: [Function (anonymous)],
withdraw: [Function (anonymous)],
print: [Function: print]
}
YESS!! We have managed to “hide” balance and made it a “private” field of Account class.
But wait a minute.. what if we directly access it, like…
console.log(eich.id, eich.name, eich.balance);
Out of luck! It’s NOT inside the object, so we CANNOT access it at all. Only when the functions deposit and withdraw are called, they can “see” it in the closure dictionary
1 Brendan undefined
So, is this the way we create private methods too?
Absolutely! Just like local variables of outer function, even local functions are “captured” by closures.
Creating "private" functions
function Account(id, name, bal) {
// properties of the object
this.id = id;
this.name = name;
// Local variable to Account
let balance = bal;
// NOT part of the object
// Local function (like private)
// Cannot be called from outside
function sendSMS() {
console.log(`Sent SMS: Updated balance={balance}`);
}
this.deposit = function (amount) {
// deposit "closes over" balance
balance += amount;
// Can call local function
sendSMS();
}
this.withdraw = function(amount) {
// balance is NOT a property
// but local variable of outer function
balance -= amount;
// Can call local function
sendSMS();
}
this.print = function print() {
console.log(`ID=${this.id},
Name=${this.name},
Balance=${balance}`);
}
};
Let's call it from outside
let eich = new Account(1, "Brendan", 10000.00);
eich.print();
eich.deposit(1000);
eich.print();
eich.withdraw(500);
eich.print();
// Try to sendSMS from outside the object
eich.sendSMS();
Nope! Does not work
ID=1, Name=Brendan, Balance=10000
Sent SMS: Updated balance=11000
ID=1, Name=Brendan, Balance=11000
Sent SMS: Updated balance=10500
ID=1, Name=Brendan, Balance=10500
eich.sendSMS();
^
TypeError: eich.sendSMS is not a function
Wonderful! We have managed to create private fields and functions using ES5 syntax!
Wait… what about ES6? class syntax?
Wait for the next blog post 🙂