Coming from the Java language and landing in the JavaScript realm can be kind of confusing experience. All you see is functions and thinking in objects can be hard if you are not familiar with the language idioms.
This post is an attempt to help overcome the initial struggle and can be used as a cheat sheets for Java developers who want to start writing in JavaScript.
Let’s start with something simple – how to create objects.
Object Creation
Java sample that declares a Book class and creates an instance of it
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class Book { private String name; private String author; public Book() { this(null, null); } public Book(String name, String author) { this.name = name; this.author = author; } public String getAuthor() { return author; } public String getName() { return name; } public void setAuthor(String author) { this.author = author; } public void setName(String name) { this.name = name; } public String greet() { return "Hi! My name is " + this.author; } public static void main(String[] args) { Book book = new Book("sample-book", "Sample Author"); System.out.println(book.greet()); } } |
The Java code should be clear so no need to dwell on it. However let’s see the JavaScript code for the same thing.
JavaScript samples for creating and using a Book object
The easiest way is to define an Object literal (a.k.a inline object)
1 2 3 4 5 6 7 8 9 |
var firstBook = { name: "first-book", author: "First Author", greet: function () { return "Hi! My name is " + this.author; } } console.log(firstBook.name + " by " + firstBook.author + " (" + firstBook.greet() + ")") ; |
As you can see JavaScript does not have any notion of classes – you define objects instead.
What about creating several object that have the same properties and methods? One way is to place object literals in the code however this can be a bit frustrating. To avoid code duplication and accidental typos a function that creates such objects can be defined
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function createBook(name, author) { var book = {}; book.name = name; book.author = author; book.greet = function() { return "Hi! My name is " + this.author; } return book; } var secondBook = createBook("second-book", "Second Author"); console.log(secondBook.name + " by " + secondBook.author + " (" + secondBook.greet() + ")") ; |
There is a shortcut to this approach and it is to use constructor function:
1 2 3 4 5 6 7 8 9 10 |
function Book(name, author) { this.name = name; this.author = author; this.greet = function() { return "Hi! My name is " + this.author; } } var thirdBook = new Book("third-book", "Third Author"); console.log(thirdBook.name + " by " + thirdBook.author + " (" + thirdBook.greet() + ")") ; |
The constructor function is the closest version of a class in JavaScript. It basically defines properties and methods.
Note that in this case the new
operator is used to invoke the function. Also by convention the name of a constructor function starts with a capital letter.
There is still another way to create an object – by invoking the Object()
constructor.
1 2 3 4 5 6 7 8 |
var fourthBook = new Object(); fourthBook.name = "fourth-book"; fourthBook.author = "Fourth Author"; fourthBook.greet = function() { return "Hi! My name is " + this.author; } console.log(fourthBook.name + " by " + fourthBook.author + " (" + fourthBook.greet() + ")") ; |
You can use this constructor to create generic object and attach properties and methods to it later.
Default values for properties and methods can be passed to the Object
constructor:
1 2 3 4 5 6 7 8 9 |
var fifthBook = new Object({ name : "fifth-book", author : "Fifth Author", greet : function() { return "Hi! My name is " + this.author; } }); console.log(fifthBook.name + " by " + fifthBook.author + " (" + fifthBook.greet() + ")") ; |
Polymorphism
Java sample – Render Rectangle and Square
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class Rectangle { protected final float width; protected final float height; public Rectangle(float width, float height) { this.width = width; this.height = height; } public void render() { System.out.println("I'm a " + this.width + " x " + this.height + " rectangle"); } } public class Square extends Rectangle { public Square(int dimension) { super(dimension, dimension); } @Override public void render() { System.out.println("I'm a square"); } } public class Application { public static void main(String[] args) { Rectangle rectangle = new Rectangle(2, 4); rectangle.render(); Square square = new Square(16); square.render(); } } |
Again the Java code should be clear so let’s see the JavaScript part.
JavaScript samples showing the dynamic nature of prototypal inheritance
Since JavaScript has no notion of classes the inheritance is implemented by using a prototype object. Basically each object has a prototype object that serves as a template and extenders inherit properties and methods from it. It might sound confusing at first but after running the following examples it makes sense.
Let’s start by defining a Rectangle
object.
1 2 3 4 |
function Rectangle(width, height) { this.width = width; this.height = height; }; |
It has two properties. If this object is designed to be extendable all methods that can be inherited must be defined in the prototype object.
(Remember that each object has a prototype object)
1 2 3 |
Rectangle.prototype.render = function() { console.log("I'm a " + this.width + " x " + this.height + " rectangle"); }; |
The code above adds the render
method to the prototype object thus any object that inherits Rectangle
can override it.
1 2 |
var rectangle = new Rectangle(2, 4); rectangle.render(); |
Now let’s define another object – Square
, that will extend Rectangle
1 2 3 |
function Square(dimension) { Rectangle.call(this, dimension, dimension); }; |
As you can see it has only one argument and call
s Rectangle
‘s constructor passing it.
Now If we call render()
1 2 |
var square = new Square(16); square.render(); |
We’ll see the following error:
1 |
Uncaught TypeError: square.render is not a function |
To fix this we have to copy the prototype object from the parent object
1 |
Square.prototype = Object.create(Rectangle.prototype); |
Calling render()
again will result in:
1 |
I'm a 16 x 16 rectangle |
Now we can override the behaviour of render
for Square objects:
1 2 3 |
Square.prototype.render = function() { console.log("I'm a square!"); } |
And here is the full code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function Rectangle(width, height) { this.width = width; this.height = height; }; Rectangle.prototype.render = function() { console.log("I'm a " + this.width + " x " + this.height + " rectangle"); }; var rectangle = new Rectangle(2, 4); rectangle.render(); function Square(dimension) { Rectangle.call(this, dimension, dimension); }; Square.prototype = Object.create(Rectangle.prototype); var square = new Square(16); square.render(); Square.prototype.render = function() { console.log("I'm a square!"); } square.render(); |
Can you guess the output?
Encapsulation
Java sample – private Rectangle properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Rectangle { private int width = 0; private int height = 0; public void resize(int width, int height) { this.width = width; this.height = height; } public void printSizes() { System.out.println(width + " " + height); } public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.printSizes(); rectangle.resize(10, 10); rectangle.printSizes(); } } |
The Java code defines a Rectangle
whose properties are not exposed and can be changed only through the resize
method.
Let’s see how this can be achieved in JavaScript
JavaScript counterpart – function scoped variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var rectObject = (function () { var width = 0; var height = 0; return { resize: function(w, h) { width = w; height = h; }, printSizes: function() { console.log(width + " " + height); } } }()); rectObject.printSizes(); rectObject.resize(10, 10); rectObject.printSizes(); console.log(rectObject.width) |
The code looks a bit stranger than the previous snippets so let’s start from the top. The expression
1 |
(function(){}()) |
defines anonymous function and executes it. If the last ()
is omitted then only a function will be declared but not executed (if no variable points to it then the function cannot be called at later time).
The next important thing is this idiom:
1 2 3 4 5 |
(function(){ return { } }()) |
This is an anonymous function that returns an empty object. Having in mind that the variables are scoped in a function this means that everything that is declared in the function and before the return
statement will be visible to the inlined object and not visible to the callers of the function. Example:
1 2 3 4 5 6 7 8 9 |
var result = (function(){ var x = 5; var y = 5; return { sum : x+y } }()) console.log(result.sum); |
This will log 10 in the console output. And this will log undefined
1 2 3 4 5 6 7 8 9 |
var result = (function(){ var x = 5; var y = 5; return { sum : x+y } }()) console.log(result.x); |
Hopefully the code we started with is more clear now. The next question is – What about having multiple objects with scoped state?
JavaScript sample – multiple objects with scoped state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function Rectangle() { var width = 0; var height = 0; return { resize: function(w, h) { width = w; height = h; }, printSizes: function() { console.log(width + " " + height); } } } var rectangle = new Rectangle(); rectangle.printSizes(); rectangle.resize(10, 10); rectangle.printSizes(); console.log(rectangle.width) |
As a last note – this approach is not restricted to variables only. Functions declared outside the return statement won’t be visible as well.