Hiding data, creating encapsulation, in JavaScript ES-2015 classes

By: (plus.google.com) +David Herron; Date: February 27, 2018

Tags: Node.JS » JavaScript

ES2015 added a ton of useful new features, and with ES2016/2017/2018 we're getting a whole new language with a list of exciting new capabilities. One of them is the new Class defintion syntax that brings Class definitions closer to what's done in other languages. Unfortunately the new Class syntax does not provide a robust implementation hiding mechanism. Hiding the implementation is important in object oriented programming, if only to have the freedom to change the implementation at any time.

Important attributes of object-oriented programming is data encapsulation and implementation hiding. The ES2015 JavaScript class definition offers excellent data encapsulation - but implementation hiding just isn't there. Okay, this is JavaScript, a Functional Programming lanaguage, where object-oriented programming is kind of shunned, but there are techniques regarding Class definitions and practices in other languages which are very useful.

With the default syntax for defining a JavaScript class, there is no implementation hiding. The fields are simply there, and any code anywhere in the program can manipulate the fields. Okay, this is JavaScript, a language where anything can be manipulated at will. Let me ask you if that is safe? For small scripts on a web page it's okay to manipulate all kinds of things. For huge applications meant to have great security, it's not a good idea.

The best recommendation for implementation hiding, TODAY, in JavaScript ES2015 Classes is to use Symbol instances to define the fields. We have an example of this later. Unfortunately while this approach adds a measure of implementation hiding, there is no actual protection. It's relatively easy to for 3rd party code to access the fields protected by that technique. The details for this are shown below.

Introduction

In traditional JavaScript we would tend to create anonymous objects like so:

{
    key: 'foo',
    title: 'The Rain in Spain',
    body: 'Falls mainly on the plain'
}

These objects are quick and easy to create and pass around. But there are at least two problems:

  • You can't robustly tell one anonymous object from another
    • That "if it walks like a duck, talks like a duck, it's a duck" paradigm doesn't work for software. What if you honestly need to give the same name to fields in different objects, but the fields have different semantics because of domain-specific attributes. In such a case one object is a duck, and the other a quark, and they're completely different from each other, even if they have a field or two with the same name.
  • You have no form of implementation hiding

A simple Class

class Note {
    constructor(key, title, body) {
        this.key = key;
        this.title = title;
        this.body = body;
    }
}

The new Class syntax in JavaScript is this. It's similar to Class definitions in other languages, but with a JavaScript flavor to it. We've also written

Rewrite it a little, and save it to note1.js:

module.exports = class Note {
    constructor(key, title, body) {
        this.key = key;
        this.title = title;
        this.body = body;
    }
}

Then run these commands:

$ vi note1.js
david@nuc2:~/t$ node
> const Note = require('./note1');
undefined
> typeof Note
'function'
> const aNote = new Note('foo', 'The Rain In Spain', 'Falls mainly on the plain');
undefined
> var notNote = {}
undefined
> notNote instanceof Note
false
> aNote instanceof Note
true
> typeof aNote
'object'
> 

This shows we can robustly identify the object type using the instanceof operator.

> aNote.title
'The Rain In Spain'
> aNote.title = 'gibberish';
'gibberish'
> aNote.title
'gibberish'
> 

But we have no protection against 3rd party code manipulating object instances. We have no form of implementation hiding.

What if we needed a Note class that retrieved information from a server? That's not possible here.

The best implementation hiding method for JavaScript classes

Create note2.js containing this:

const _note_key = Symbol('key');
const _note_title = Symbol('title');
const _note_body = Symbol('body');

module.exports = class Note {
    constructor(key, title, body) {
        this[_note_key] = key;
        this[_note_title] = title;
        this[_note_body] = body;
    }
    get key() { return this[_note_key]; }
    get title() { return this[_note_title]; }
    set title(newTitle) { this[_note_title] = newTitle; }
    get body() { return this[_note_body]; }
    set body(newBody) { this[_note_body] = newBody; }   
};

This Note class is meant to be equivalent to the first, but with some improvements.

We've used the new getter and setter syntax, and even have one field that's read-only. A get function is a getter, and as implied by the code returns a value corresponding to a field in the object. A set function is a setter, and it sets the corresponding field in the object.

The method we chose for implementation-hiding uses the Symbol class, which is also new with ES-2015. A Symbol is an opaque object with two main use cases:

  • Generating unique keys to use as property fields - as in the Note class above
  • Symbolic identifiers that you can use for concepts like COLOR_RED

You define a Symbol through a factory method that generates Symbol instances:

> let symfoo = Symbol('foo')

Each time you invoke the Symbol factory method a new, and unique, instance is created. For example, Symbol('foo') === Symbol('foo') is false, as is symfoo === Symbol('foo'), because a new instance is created on each side of the equality operator. However, symfoo === symfoo is true, because they are the same instance.

This means the Symbol('key') stored in _note_key is used as the index into Note instances where the key field is stored. The same is true for the other fields.

That particular Symbol instance is only accessible inside the module where this Class is defined. Therefore, 3rd party code outside the module would have a very hard time getting access to that specific Symbol instance and would therefore find it incredibly difficult to access the field in the object.

This means for most intents and purposes the only access to the field implementation is through those getter/setter functions. Therefore, we have a measure of implementation hiding.

Or, do we?

Accessing the hidden fields through the side door

$ node
> const Note = require('./note2');
undefined
> const aNote = new Note('foo', 'The Rain In Spain', 'Falls mainly on the plain');
undefined
> console.log(aNote);
Note {
  [Symbol(key)]: 'foo',
  [Symbol(title)]: 'The Rain In Spain',
  [Symbol(body)]: 'Falls mainly on the plain' }
undefined

This is what we talked about. The fields in the Note instance are somewhat visible, but the index is this Symbol instance whose value is known only inside the note2.js module.

> console.log(aNote[Symbol('key')])
undefined

The straight-forward method to access this value does not work, because the Symbol instance here is not the same as the Symbol instance inside the note2.js module.

But...

> Object.keys(aNote)
[]
> Reflect.ownKeys(aNote)
[ Symbol(key), Symbol(title), Symbol(body) ]

With Object.keys the JavaScript language is set up to ignore the fields whose index is a Symbol. But, using the Reflect class we CAN get ahold of the actual Symbol instances stashed inside the note2.js module.

> aNote[Reflect.ownKeys(aNote)[2]]
'Falls mainly on the plain'
> aNote[Reflect.ownKeys(aNote)[2]] = 'gibberish'
'gibberish'
> aNote.body
'gibberish'
> 

In other words, someone with enough dedication can access those private fields.

This method of using Symbol instances is the best mechanism to privately store object fields. While it provides a measure of implementation hiding, it is not a perfect barrier. The fields are easily accessible using the method shown here.

« Handling unhandled Promise rejections, avoiding application crash Node.js Web Development, 4th edition, coming soon »
2016 Election 2018 Elections Acer C720 Ad block Affiliate marketing Air Filters Air Quality Air Quality Monitoring AkashaCMS Amazon Amazon Kindle Amazon Web Services America Amiga and Jon Pertwee Android Anti-Fascism AntiVirus Software Apple Apple Flexgate Apple Hardware History Apple Hardware Mistakes Apple iPhone Apple iPhone Hardware April 1st Arduino ARM Compilation Artificial Intelligence Astronomy Astrophotography Asynchronous Programming Authoritarianism Automated Social Posting AWS DynamoDB AWS Lambda Ayo.JS Bells Law Big Brother Big Data Big Finish Big Science Bitcoin Mining Black Holes Blade Runner Blockchain Blogger Blogging Books Botnets Cassette Tapes Cellphones China China Manufacturing Christopher Eccleston Chrome Chrome Apps Chromebook Chromebox ChromeOS CIA CitiCards Citizen Journalism Civil Liberties Climate Change Clinton Cluster Computing Command Line Tools Comment Systems Computer Accessories Computer Hardware Computer Repair Computers Conservatives Cross Compilation Crouton Cryptocurrency Curiosity Rover Currencies Cyber Security Cybermen Cybersecurity Daleks Darth Vader Data backup Data Formats Data Storage Database Database Backup Databases David Tenant DDoS Botnet Department of Defense Department of Justice Detect Adblocker Developers Editors Digital audio Digital Nomad Digital Photography Direct Attach Storage Diskless Booting Disqus DIY DIY Repair DNP3 Do it yourself Docker Docker MAMP Docker Swarm Doctor Who Doctor Who Paradox Doctor Who Review Drobo Drupal Drupal Themes DuckDuckGo DVD E-Books E-Readers Early Computers eGPU Election Hacks Electric Bicycles Electric Vehicles Electron Eliminating Jobs for Human Emdebian Encabulators Energy Efficiency Enterprise Node EPUB ESP8266 Ethical Curation Eurovision Event Driven Asynchronous Express Face Recognition Facebook Fake Advertising Fake News Fedora VirtualBox Fifth Doctor File transfer without iTunes FireFly Flash Flickr Fraud Freedom of Speech Front-end Development G Suite Gallifrey Gig Economy git Github GitKraken Gitlab GMAIL Google Google Adsense Google Chrome Google Gnome Google+ Government Spying Great Britain Green Transportation Hate Speech Heat Loss Hibernate High Technology Hoax Science Home Automation HTTP Security HTTPS Human ID I2C Protocol Image Analysis Image Conversion Image Processing ImageMagick In-memory Computing Incognito Mode InfluxDB Infrared Thermometers Insulation Internet Internet Advertising Internet Law Internet of Things Internet Policy Internet Privacy iOS iOS Devices iPad iPhone iPhone hacking Iron Man iShowU Audio Capture iTunes Janet Fielding Java JavaFX JavaScript JavaScript Injection JDBC John Simms Journalism Joyent jQuery Kaspersky Labs Kext Kindle Kindle Marketplace Large Hadron Collider Lets Encrypt LibreOffice Linux Linux Hints Linux Single Board Computers Logging Mac Mini Mac OS Mac OS X Mac Pro MacBook Pro Machine Learning Machine Readable ID Macintosh macOS macOS High Sierra macOS Kext MacOS X setup Make Money Online Make Money with Gigs March For Our Lives MariaDB Mars Mass Violence Matt Lucas MEADS Anti-Missile Mercurial MERN Stack Michele Gomez Micro Apartments Microsoft Military AI Military Hardware Minification Minimized CSS Minimized HTML Minimized JavaScript Missy Mobile Applications Mobile Computers MODBUS Mondas Monetary System MongoDB Mongoose Monty Python MQTT Music Player Music Streaming MySQL NanoPi Nardole NASA Net Neutrality Network Attached Storage Node Web Development Node.js Node.js Database Node.js Performance Node.js Testing Node.JS Web Development Node.x North Korea npm NSA NVIDIA NY Times Online advertising Online Community Online Fraud Online Journalism Online News Online Photography Online Video Open Media Vault Open Source Open Source and Patents Open Source Governance Open Source Licenses Open Source Software OpenAPI OpenJDK OpenVPN Palmtop PDA Patrick Troughton PayPal Paywalls Personal Flight Peter Capaldi Peter Davison Phishing Photography PHP Plex Plex Media Server Political Protest Politics Postal Service Power Control President Trump Privacy Private E-mail server Production use Public Violence Raspberry Pi Raspberry Pi 3 Raspberry Pi Zero ReactJS Recaptcha Recycling Refurbished Computers Remote Desktop Removable Storage Renewable Energy Republicans Retro Computing Retro-Technology Reviews RFID Rich Internet Applications Right to Repair River Song Robotics Robots Rocket Ships RSS News Readers rsync Russia Russia Troll Factory Russian Hacking Rust SCADA Scheme Science Fiction SD Cards Search Engine Ranking Search Engines Season 1 Season 10 Season 11 Security Security Cameras Server-side JavaScript Serverless Framework Servers Shell Scripts Silence Simsimi Skype SmugMug Social Media Social Media Networks Social Media Warfare Social Network Management Social Networks Software Development Software Patents Space Flight Space Ship Reuse Space Ships SpaceX Spear Phishing Spring Spring Boot Spy Satellites SQLite3 SSD Drives SSD upgrade SSH SSH Key SSL Stand For Truth Strange Parts Swagger Synchronizing Files Tegan Jovanka Telescopes Terrorism The Cybermen The Daleks The Master Time-Series Database Tom Baker Torchwood Total Information Awareness Trump Trump Administration Trump Campaign Twitter Ubuntu Udemy UDOO US Department of Defense Video editing Virtual Private Networks VirtualBox VLC VNC VOIP Vue.js Walmart Weapons Systems Web Applications Web Developer Resources Web Development Web Development Tools Web Marketing Webpack Website Advertising Website Business Models Weeping Angels WhatsApp William Hartnell Window Insulation Windows Windows Alternatives Wordpress World Wide Web Yahoo YouTube YouTube Adpocalypse YouTube Monetization