JavaScript object-orientated and prototype-based programming

As you learned in the previous chapter on JavaScript data types, JavaScript relies heavily on the concept of objects. While being familiar with other object-orientated programming(OOP) languages (e.g. C#, Java) helps, JavaScript works differently than most mainstream OOP languages.

JavaScript was conceived as a prototype-based programming language, which is a type of OOP language. Although the differences are subtle, exploring prototype-based concepts is critical to better understanding JavaScript in general, not to mention knowing such differences also makes it easier to grasp JavaScript's object-orientated evolution.

Prototype-based programming and the prototype property  

A prototype, according to the dictionary is: an original model on which something is patterned. By extension, it means prototype-based programming languages use prototypes as their building blocks. But re-read the prototype definition again, doesn't it sound pretty similar to the concept of classes in OOP ? After all, in OOP languages, classes are used to describe models that describe how a system works.

It turns out OOP and prototype-based programming are behaviorally similar, however, prototype-based design is a little different than class-based design. To make your first encounter with prototype-based design in JavaScript as simple as possible, I'll start with a scenario based on the String data type presented in the previous chapter.

The JavaScript String data type has its own set of properties and methods provided out-of-the-box, which are made available to string primitives and instances of the String data type, as shown toward the end of listing 4-2.

Now, suppose you're faced with the problem of constantly formatting the strings in your application with a variation that isn't provided by the String data type (e.g. adding exclamation marks or wrapping it as HTML bold statements). In an OOP language, to simplify this repetitive process you would create a String sub-class -- to inherit the out-of-the-box String properties and methods -- and be able to add custom properties or methods to perform these repetitive operations. In a prototype-based programming language, specifically JavaScript, all you need to do is alter the String data type itself via its prototype property to make new methods accesible to all String instances, a process that's illustrated in listing 5-1.

Note In JavaScript all objects have properties. An object property assigned a function() statement is referred to as a method, whereas an object property assigned a literal value (e.g. a string, a number) is referred to as a property. Therefore the properties term can be used in a broad or narrow sense, the former in reference to both methods and properties with literal values, the latter in reference to properties with literal values.
Listing 5-1. String data type with new methods added via the prototype property.
String.prototype.wildExclamation = function() { 
  return `${this.toString()}!#?!#?`;
};

String.prototype.boldHtml = function() { 
  return `<b>${this.toString()}</b>`;
};


// string primitive with literal string
let hello = "Hello";
// string access to built-in String method
console.log(hello.toUpperCase()); 
// string access to custom String methods added via prototype
console.log(hello.wildExclamation());
console.log(hello.boldHtml());

// string primitive with String constructor as plain function
let world = String("World");
// string access to built-in String method
console.log(world.toUpperCase());
// string access to custom String methods added via prototype
console.log(world.wildExclamation());
console.log(world.boldHtml());

The first two snippets in listing 5-1 add the wildExclamation() and boldHtml() methods to the built-in String data type using the prototype property.

The additional methods added to the String data type in listing 4-1 make use of the this keyword to access the current instance of a string and then leverage the String.toString() method to access its actual value. So for the let hello = "Hello"; statement, a call to this.toString() with the hello string instance returns "Hello". In both cases, the methods make use of template literals with backticks ` and ${} to wrap each value with custom markup, in the case of wildExclamation() adding the trailing characters !#?!#? and for boldHtml() wrapping the content in an HTML <b> element.

Once a data type's prototype is updated, all instances of the data type gain access to these new functionalities. You can see in listing 5-1 both the hello and world string primitives are able to access the standard String.toUpperCase() method included with the String data type, as well as the custom wildExclamation() and boldHtml() methods added via the prototype property.

Now that you have an initial understanding of the JavaScript prototype property with the String data type, lets take a look at the prototype chain.

Prototype-based programming and the prototype chain 

The past section illustrated how special the prototype property is to JavaScript data types, since it allows adding functionalities to a data type to become available to all object instances of the data type. Unlike object-orientated languages that use classes to define inheritance hierarchies by extending other classes or use other constructs like abstract classes and interfaces to build secondary classes, in a prototype-based programming language like JavaScript, the inheritance process is much simpler and achieved through the prototype property.

You already learned a little bit about the prototype property in the example in listing 5-1, that adds a couple of custom methods to the built-in String data type so they're accesible to all String object instances. Now, we're going to add a custom property to the more generic Object data type, so it becomes accesible to all Object object instances, as well as data types that have the Object data type in their prototype chain.

Listing 5-2. Prototype chain, property resolution and shadowing
let javascript = new Object();
console.log("javascript is: %o", javascript);
console.log("javascript.typed is: %o", javascript.typed);

// Add "typed" property to Object.prototype
Object.prototype.typed = "dynamically";

// Pre-existing Object objects gain access to "typed" property
console.log("javascript is: %o", javascript);
console.log("javascript.typed is: %o", javascript.typed);

// New Object objects get "typed" property
let python = new Object();
console.log("python is: %o", python);
console.log("python.typed is: %o", python.typed);

let c = new Object();
console.log("c is: %o", c);
console.log("c.typed is: %o", c.typed);
// Override "typed" property by adding it directly to object
c.typed = "statically";
// Object object "typed" property belongs to instance 
console.log("c is: %o", c);
console.log("c.typed is: %o", c.typed);


// Create string primitive / String object
let hello = "Hello";

console.log("hello is: %o", hello);
// String object has access to "typed" property added to Object.prototype
console.log("hello.typed is: %o", hello.typed);

// String can shadow the Object.prototype "typed" property with its own String.prototype
String.prototype.typed = "N/A";
console.log("hello.typed is: %o", hello.typed);

Listing 5-2 starts with the creation of a new Object assigned to the javascript reference. Next, you can see that attempting to output the typed property on the javascript reference shows undefined, since the Object instance doesn't know anything about this property.

Then you can see the statement Object.prototype.typed = "dynamically"; adds the typed property with a value of "dynamically" to the Object data type's prototype property, effectively making the typed property avaiable to all Object instances. The next console statements show two important behaviors for properties added through the prototype property:

Next in listing 5-2, a pair of new Object instances are created and assigned to the python and c references. For both references you can see that attempting to output the typed property outputs dynamically, confirming the new objects automatically get the typed property from the Object data type's prototype property. However, notice it's also possible to override a prototype property by adding a property with the same name directly to the object instance, in this case, the c object overrides the typed property value with the c.typed = "statically"; statement.

The most interesting aspect in listing 5-2 though is the creation of the String object -- via a string primitive and literal "Hello" -- which based on the console statements also has access to the typed property with a value of dynamically. The reason a string primitive -- which automatically gains access to the functionalities in the String data type -- is because the String data type has a prototype chain linked to the Object data type. In addition, notice it's also possible to shadow a prototype property of an upstream data type in the prototype chain by adding a property to the prototype property to the working data type. In this case, the statement String.prototype.typed = "N/A";, shadows the Object data type's prototype typed property with a different prototype typed property value for the String data type applicable to all String objects.

As you can see from the examples in listing 5-2, there's a lot more going on behind the scenes when JavaScript attempts to resolve object properties, which is directly related to the prototype chain. The resolution process is as follows:

It's thanks to this prototype chain behavior, most objects are able to access properties and methods from upstream data types in their prototype chain or override such properties and methods from their own working data type. It's also the reason why most built-in data type properties and methods are documented under the prototype property (e.g. <data_type>.prototype.<property_name>) to indicate they're part of a data type's prototype.

There's no single call to get the prototype chain from a given object, but you can get the prototype of a given object, followed by the prototype of the prototype and the prototype of the prototype's prototype and so on, to net you an object's prototype chain. There are two ways to obtain the prototype for a given object, one is through the standard Object.getPrototypeOf() static method and the other through the private object reference __proto__. Both approaches deliver the same results, it's simply the __proto__ property is a browser maker implemented syntax that isn't guaranteed to always be available, whereas the Object.getPrototypeOf() static method is a standard ECMAScript construct.

Tip See the Do JavaScript objects have other encapsulation/accessibility constructs besides getters and setters ? section, for additional details on the use of references that use underscores _

Listing 5-3 illustrates how to access an object's prototype and build its prototype chain using both __proto__ and Object.getPrototypeOf(), as well as where the plain .prototype property fits in the context of the prototype chain.

Listing 5-3. Prototype chain determination with __proto__ and Object.getPrototypeOf()
let vowels = ["a","e","i","o","u"];

console.log("Object.getPrototypeOf(vowels)",
Object.prototype.toString.call(Object.getPrototypeOf(vowels)))
console.log("Object.getPrototypeOf(Object.getPrototypeOf(vowels))",
Object.prototype.toString.call(Object.getPrototypeOf(Object.getPrototypeOf(vowels))))
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(vowels)))",
Object.prototype.toString.call(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(vowels)))))

console.log("vowels.__proto__: %s", Object.prototype.toString.call(vowels.__proto__));
console.log("vowels.__proto__.__proto__: %s", Object.prototype.toString.call(vowels.__proto__.__proto__));
console.log("vowels.__proto__.__proto__.__proto__: %s", Object.prototype.toString.call(vowels.__proto__.__proto__.__proto__));

if ((Object.getPrototypeOf(vowels) === vowels.__proto__) && (vowels.__proto__  === Array.prototype)) { 
  console.log("(Object.getPrototypeOf(vowels) === vowels.__proto__) && (vowels.__proto__  === Array.prototype)");
}

if (vowels.__proto__ != vowels.prototype) { 
  console.log("Array.prototype != vowels.prototype");
}

The first statement in listing 5-3 declares a JavaScript Array object in the vowels reference. Next, you can see the statement Object.getPrototypeOf(vowels) outputs the vowels prototype. Since the vowels reference and its prototype are objects, we wrap the output around Object.prototype.toString.call() to get a specific data type object value, as described in listing 4-9.

As expected the output of Object.getPrototypeOf(vowels) is [object Array], indicating the reference's prototype is an Array data type. To get the upstream prototype of the Array data type another Object.getPrototypeOf() statement is wrapped around the first, which outputs [object Object], indicating the prototype's prototype is an Object data type. And finally a third Object.getPrototypeOf() statement is wrapped around the initial two to get the prototype's prototype prototype, which outputs [object Null], indicating the end of the prototype chain.

The second set of console statements in listing 5-3 also outputs the vowels object prototype chain, but instead of using the Object.getPrototypeOf() static method it uses the object's .__proto__ property. The conditionals that follow confirm the results of the Object.getPrototypeOf() static method and an object's .__proto__ property are identical.

Finally, another key concept shown in listing 5-3 is the .prototype property in the context of the prototype chain. Notice the results of the Object.getPrototypeOf() static method and an object's .__proto__ property are equal to the object's data type .prototype property, in this case Array.prototype. Also notice accesing the .prototype property on the object instance itself vowels.prototype outputs undefined. This behavior is because the .prototype property is intended to be used on data types -- like it's done in listing 5-1 and listing 5-2. When the .prototype property is used on object instances, it's treated like any other property, so the vowels.prototype output is undefined since the object doesn't know anything about this property. In conclusion, an object instance knows about its prototype, but only through the Object.getPrototypeOf() static method and its own .__proto__ property, the .prototype property is not intended for object instance use.

Now that you have an initial understanding of the JavaScript prototype property and the prototype chain, lets take a look at another JavaScript prototype-based programming concept: constructor functions.

Prototype-based programming constructors & static properties, the early years  

A constructor as its name implies is used to build something. In OOP and prototype-based programming, a constructor is what's used to build object instances from a data type.

Up to this point, you've worked with various constructors from JavaScript's built-in data types (e.g. String(), Date()). However, the thing about constructors offered by built-in data types is they're too versatile, since some can be used as constructors, as well as plain functions, something that can make it hard to pin down what to expect from pure JavaScript constructors.

In order to truly grasp how JavaScript constructors work, I'm going to start with a clean slate using a custom JavaScript data type called Language that is its own constructor. Listing 5-4 illustrates the internal workings of constructor on a custom data type.

Listing 5-4. Constructor functions for custom data types
// Constructor function
var Language = function(name,version) {
 //"use strict"; // prevents access to 'this' if not local
 console.log("'this' execution context at constructor function start: %s", this);
 this.name = name;
 this.version = version;
 this.hello = function() { 
   return `Hello from ${this.name}`;
 }
};
// Static property, added to constructor
Language.KIND = "HighLevel";


// Create object instances with new 
let javascript = new Language("JavaScript","2022");
let python = new Language("Python","3.10");

// Verify instance values
console.log("javascript type: %s", typeof javascript);
console.log("javascript.name: %s", javascript.name);
console.log("javascript.version: %s", javascript.version);
console.log("Object.getPrototypeOf(javascript): %s", 
Object.getPrototypeOf(javascript));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(javascript)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(javascript)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(javascript))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(javascript))));
console.log("Object.getPrototypeOf(javascript.constructor): %s",
Object.getPrototypeOf(javascript.constructor));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(javascript.constructor))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(javascript.constructor)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(javascript.constructor)))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(javascript.constructor))));

console.log("python type: %s", typeof python);
console.log("python.name: %s", python.name);
console.log("python.version: %s", python.version);
console.log("python.hello(): %s", python.hello());
console.log("Object.getPrototypeOf(javascript): %s",
Object.getPrototypeOf(python));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(python)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(python)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(python))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(python))));
console.log("Object.getPrototypeOf(python.constructor): %s",
Object.getPrototypeOf(python.constructor));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(python.constructor))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(python.constructor)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(python.constructor)))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(python.constructor))));

// Call constructor as a plain function without new
let ruby = Language("Ruby","3");
console.log("ruby type: %s", typeof ruby);
console.log("ruby.name: %s", ruby && ruby.name ? ruby.name: undefined);
console.log("ruby.version: %s", ruby && ruby.version ? ruby.version: undefined);
console.log("ruby.hello(): %s", ruby && ruby.hello() ? ruby.hello(): undefined);
console.log("Object.getPrototypeOf(ruby): %s", ruby && Object.getPrototypeOf(ruby) ? Object.getPrototypeOf(ruby): undefined);
console.log("ruby.constructor: %s", ruby && ruby.constructor ? ruby.constructor: undefined);

// Get Language static property 'KIND' 
console.log("Language.KIND: %s", Language.KIND);
// Static properties aren't available in object instances
console.log("javascript.KIND: %s", javascript && javascript.KIND ? javascript.KIND: undefined);
console.log("python.KIND: %s", python && python.KIND ? python.KIND: undefined);
console.log("ruby.KIND: %s", ruby && ruby.KIND ? ruby.KIND: undefined);

Initially the var Language statement in listing 5-4 can appear like a plain function expression -- presented in first class functions: Function declarations, function expressions, hoisting & undefined -- but it's actually a constructor due to its characteristics:

So the var Language statement in listing 5-4 is a constructor because of its characteristics. In this case, the this keyword is used to reference the object's instance and assign it the name and version properties with values from the constructor's input also called name and version, as well as give the object instance a hello() method that returns a string based on the object's instance name property. In addition, notice after the constructor definition, the KIND = "HighLevel"; property is added directly to the Language constructor to work as a static property, which by convention, uses all uppercase letters. By being a static property, it means the property doesn't change across instances of the Language data type.

Although the syntax characteristic of Language make it a constructor, because it's still a function expression, it's entirely valid to call the constructor in one of two ways: with the new keyword or without it as a plain function. The most important side effects of calling a function expression with the new keyword are:

That said, let's take a closer look at the differences of using and not using the new keyword as they're presented in listing 5-4.

The new Language("JavaScript","2022"); and new Language("Python","3.10"); statements create two different Language instances. You'll notice that when each instance is created, the first log statement in the constructor function outputs the this reference as a Language object, confirming a dedicated execution context for the constructor. In the log statements after the instances are created, you can also see the reference for each instance is a typeof object, confirming the constructor automatically returns the object instance even though it lacks an explicit return statement. In addition, in the remaining log statements, you can also see: the output for the name & version properties and hello() method for each instance is output based on the constructor inputs; the prototype output for both objects is the Language data type, with a prototype chain Language - Object - null; and both objects have a constructor property -- which points to the constructor function -- that itself has a prototype output of the Function data type, with a prototype chain Function - Object - null.

The Language("Ruby","3"); statement calls the constructor as a plain function. The first thing that's different from making a call without the new keyword, is the log statement in the constructor function outputs the this reference as an Object object, confirming the execution context inside the function is pointing toward the outer this reference -- in this case the global Object -- a behavior described in the lexical scope and the this execution context section. This behavior is not only wrong but dangerous, since the this statements inside the constructor are influencing properties and methods in the outer scope, potentially overwriting them or adding them where they aren't needed, in this case the global Object. To disallow this behavior of accessing the this reference from another execution context, declaring the "use strict" at the beginning of the function -- commented out in the example -- generates an error when trying to access a this reference, because the constructor won't have a reference to this unless the constructor is called with the new keyword, which essentially forces the constructor to only work when called with the new keyword.

You can also see the results of the Language("Ruby","3"); statement assigned to the ruby reference are off. Because the constructor is called as a plain function, its return value is undefined due to a lack of an explicit return statement, therefore the ruby reference is undefined and not an object instance like the previous calls with new. And because the ruby reference is itself undefined, the expected properties and methods of a Language instance are also output as undefined, including the object's constructor and prototype.

Finally, the last statements in listing 5-4 output the Language.KIND static property which doesn't require the new keyword, it's simply a matter of referencing Language with the static property directly, like it's done to access static properties in built-in JavaScript data types. In addition, notice the KIND static property isn't available through Language instances (e.g. python, javascript), since it's part of the data type/constructor itself.

Now that you understand how constructors work with custom JavaScript data types, we can take a look at constructor inheritance using multiple custom JavaScript data types.

Prototype-based programming constructors and inheritance, the early years  

The prototype chains you've seen up to this point are very simple, with either a built-in JavaScript data type prototype (e.g. Array, Function, String) or a single custom JavaScript data type prototype (e.g. Language in listing 5-4), followed by the prototype chain Object - null. Now let's take a look at a more elaborate prototype chain case involving two constructors and inheritance, where one constructor inherits its behavior from another constructor, similar to how classes inherit their behavior from one another in object-orientated programming languages.

Listing 5-5 illustrates how to use multiple constructors with one inheriting behavior from the other, to create a prototype chain that spans multiple custom JavaScript data types.

Listing 5-5. Constructor functions with inheritance and the prototype chain
// Constructor function
var Letter = function(value)  {
  "use strict"; // prevents access to 'this' if not local
  this.value = value;
  this.iam = function() { 
   return `I am the ${this.constructor.name} ${this.value}`;
  };
  this.alphabet = function() { 
   return `${this.value} is letter No.${Letter.ALPHABET.indexOf(this.value)+1} in the alphabet`;
  };
};
// Static property, added to constructor
Letter.ALPHABET = "abcdefghijklmnopqrstuvwxyz";


let test = new Letter("a");
console.log(test.iam());
console.log(test.alphabet());
console.log("Object.getPrototypeOf(test): %s",
Object.getPrototypeOf(test));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test))));
console.log("Object.getPrototypeOf(test.constructor): %s",
Object.getPrototypeOf(test.constructor));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test.constructor)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test.constructor)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test.constructor))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test.constructor))));


// Constructor function
var Vowel = function(value) { 
  "use strict"; // prevents access to 'this' if not local
  if (["a","e","i","o","u"].indexOf(value) === -1)
        throw new SyntaxError("Invalid vowel");
  // Call parent constructor.
  Letter.call(this,value);
};

// Works, but Vowel.prototype needs adjustments to show upstream Letter.prototype
let test2 = new Vowel("e");
console.log(test2.iam());
console.log(test2.alphabet());
// Won't work as expected until Vowel.prototype is adjusted
console.log("Object.getPrototypeOf(test2): %s",
Object.getPrototypeOf(test2));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test2)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test2)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test2))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test2))));
console.log("Object.getPrototypeOf(test2.constructor): %s",
Object.getPrototypeOf(test2.constructor));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test2.constructor)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test2.constructor)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test2.constructor))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test2.constructor))));

// Assign the Vowel.prototype an object instance of Letter.prototype
Vowel.prototype = Object.create(Letter.prototype);
// Reassigns the correct constructor to Vowel.prototype since its a copy of Letter.prototype
Vowel.prototype.constructor = Vowel;

let test3 = new Vowel("i");
console.log(test3.iam());
console.log(test3.alphabet());
console.log("Object.getPrototypeOf(test3): %s",
Object.getPrototypeOf(test3));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test3)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test3)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test3))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test3))));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test3)))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test3)))));
console.log("Object.getPrototypeOf(test3.constructor): %s",
Object.getPrototypeOf(test3.constructor));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test3.constructor)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(test3.constructor)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(test3.constructor))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(test3.constructor))));

// Get Letter static property 'ALPHABET' 
console.log("Letter.ALPHABET: %s", Letter.ALPHABET);

The first statement in listing 5-5 is a constructor function that's designed to build a custom object data type named Letter with the value property and iam() and alphabet() methods. The only novelties of the Letter constructor function vs. the Language constructor from listing 5-4 are: the iam() method uses the this.constructor.name reference to output the name of the constructor used to build the object -- the reason why it's used will be clearer in a moment; and the alphabet() method uses the Letter.ALPHABET static property in its logic. Next, you can see an instance of the Letter object is created with the letter "a", followed by log statements showing the output of the iam() and alphabet() methods, as well as the object's prototype chain and the object's constructor prototype chain.

The second constructor function in listing 5-5 Vowel also accepts an input value like the Letter constructor, however, notice the constructor function logic lacks properties and methods. The first step in the Vowel constructor is to verify the input value is a vowel, if it isn't the function immediately throws an error. If the input value is a vowel, then a call is made with Letter.call(this, value) -- available thanks to the function's prototype chain that has access to Function.prototype.call() -- which calls the Letter function, where this is the execution context (i.e. for Vowel) passed to the Letter function so it works without the new keyword, plus value is the expected input for the Letter constructor function.

At this juncture, the Vowel constructor is equipped to construct objects using the Letter constructor, notice the test2 object is capable of calling the iam() and alphabet() methods that are part of the Letter data type, as well as changing the output of the iam() method to I am the Vowel e, which uses the this.constructor.name statement depending on the object instance type (i.e. Letter or Vowel). However, Vowel objects are unaware the Letter constructor forms part of the prototype chain. This can be confirmed in the log statements that show the Vowel object created with the letter "e", output the object's prototype chain as Vowel - Object - null. The purpose of next the two lines in listing 5-5 is precisely to update the Vowel constructor's prototype, so all Vowel objects become aware of the Letter constructor.

The Vowel.prototype = Object.create(Letter.prototype); statement works similarly to the prototype statements presented in earlier examples, except it assigns all the object properties in one step, it says: create an object with all the prototype properties of the Letter data type and assign them to the prototype property of the Vowel data type. Next, because this one step assignment of the prototype property also includes a constructor function, the second Vowel.prototype.constructor = Vowel; statement ensures the Vowel data type is reassigned the Vowel constructor.

Next, an instance of Vowel is created with the letter "i", followed by log statements showing the output of the iam() and alphabet() methods, as well as the object's prototype chain and the object's constructor prototype chain. In this case, notice the prototype chain for the test3 object is four levels deep: Vowel { constructor: [Function: Vowel] }- Letter - Object - null, illustrating how it's possible to create prototype chains with multiple custom data types. Finally, the Letter.ALPHABET static property is output, showing you don't need to use the new keyword to output static properties.

To new or not to new  

The new keyword is used to invoke JavaScript functions and make them behave like constructors to create objects. This essentially means any JavaScript function can work as a data type to produce objects when called with the new keyword. However, a distinguishing characteristic of calling a function with the new keyword is it always creates and returns an object instance referenced with the this keyword, using the function as a boilerplate for a data type.

A source of confusion that can arise with the new keyword, is some functions support being called without the new keyword -- in which case they act as plain functions -- as well as with the new keyword, in which case they act as constructor functions. When to use the new keyword or not, is largely dependant on what you expect a function to do. Do you expect a function to return a full-fledged object instance, where the function operates as a data type to produce object instances ? Use new. Or do you expect a function to run some business logic and return a value -- a primitive or inclusively object -- that's unrelated to deriving a data type from the function ? Don't use new.

Listing 5-6 illustrates a function that can be called both as a plain function -- without the new keyword -- and as a constructor function with the new keyword. In addition, the same constructor function also declares a static method to leverage without creating an object instance.

Listing 5-6. Function that works as a constructor function and plain function
var Square = function(number) { 
  "use strict";
  if (this && this.constructor.name == "Square") { 
    this.number = number;
    this.result = number**2;
  } else {
    return number**2;
  }
}
// Static method, added to constructor
Square.calculate = function(number) { return number**2 }

var twoPlain = Square(2);
console.log(twoPlain)
console.log("twoPlain type: %s", typeof twoPlain);


var twoObject = new Square(2);
console.log(twoObject);
console.log("twoObject type: %s", typeof twoObject);
console.log("Object.getPrototypeOf(twoObject): %s",
Object.getPrototypeOf(twoObject));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(twoObject)): %s",
Object.getPrototypeOf(Object.getPrototypeOf(twoObject)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(twoObject))): %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(twoObject))));

var twoStatic = Square.calculate(2);
console.log(twoStatic)
console.log("twoStatic type: %s", typeof twoStatic);

The Square constructor/plain function in listing 5-6 takes a number argument and produces its square value. To allow the function to use "use strict" and make it work as either a constructor function or plain function, there's a check on the this reference and its constructor.name property. When a call is made with new, the function gets access to an object created by the constructor through the this reference and enters the this.number and this.result assignment logic and returns the this reference by default. When a call is made without new, the function won't have access to any this reference -- thanks to "use strict", otherwise it would have access to the external this reference, albeit the external this reference would fail the this.constructor.name check since it wasn't constructed by Square -- entering the explicit return statement the calculates the square of the number argument. In addition, Square also declares the calculate property as a static method, to also calculate the square value of a given number without needing to create an object instance.

You can see both calls to Square(2) and new Square(2) work, but produce different outcomes. The Square(2) call produces a value of 2 as a number primitive. The new Square(2) call produces a Square object with the two properties, number with the argument value and result with the square of number, also notice the resulting Square object has access to a prototype chain with Square - Object - null. Finally, the call Square.calculate(2) illustrates how a call is made directly to a static method, without the new keyword to create an object instance.

This behavior shown in listing 5-6 is the same one exhibited by built-in object data types that have equivalent primitive data types. For example, constructors for object data types like String and Number produce primitive values when called without new and produce full-fledged object instances of the data type when called with new. Both produce different outcomes and it's the reason why the new keyword is discouraged with these type of data type constructors, since new creates objects that by their nature are never the same and difficult to compare.

Listing 5-7 illustrates the String constructor used without the new keyword -- as it's generally recommended -- and with new keyword -- which is generally discouraged. as well as a String static method that doesn't require the new keyword.

Listing 5-7. String constructor called without new and with new, including String static method without new
let lang = String("JavaScript");
let language = "JavaScript";
let langStatic = String.fromCharCode(74, 97, 118, 97, 83, 99, 114, 105, 112, 116);


console.log("lang value: %s", lang);
console.log("language value: %s", language);
console.log("langStatic value: %s", langStatic);
console.log("lang type: %s", typeof lang);
console.log("language type: %s", typeof language);
console.log("langStatic type: %s", typeof langStatic);

if (lang == language) { 
  console.log("lang == language");
}
if (lang === language) { 
  console.log("lang === language");
}
if (language == langStatic) { 
  console.log("language == langStatic");
}
if (language === langStatic) { 
  console.log("language === langStatic");
}


let langNew = new String("JavaScript");
let languageNew = new String("JavaScript");

console.log("langNew value: %s", langNew);
console.log("languageNew value: %s", languageNew);
console.log("langNew type: %s", typeof langNew);
console.log("langNew type: %s", typeof langNew);

if (langNew == languageNew) { 
  console.log("langNew == languageNew");
} else { 
  console.log("langNew != languageNew");
}
if (langNew === languageNew) { 
  console.log("langNew === languageNew");
} else { 
  console.log("langNew !== languageNew");
}

Listing 5-7 starts by creating the lang reference with the String constructor String("JavaScript") and the language reference with the literal value "JavaScript", both values are string primitives and they're both equivalent. Next, you can see the langStatic reference is assigned a string value with the String static method String.fromCharCode() which also doesn't require the new keyword and that its value is also a string primitive equivalent to the lang and language references. Finally, notice the creation of the langNew and languageNew references with a value of new String("JavaScript"). In this last case, both values are String objects on top of which both values fail to be equivalent by value or type since they're full-fledged objects.

An interesting behavior of primitive data types produced by built-in object data type constructors or through literal syntax -- like the strings created in in listing 5-7 -- is that even though the underlying result is a primitive, it's a primitive with access to a data type constructor and prototype chain, as if it were an object created with new, when it isn't. This is the reason why primitives have access to their equivalent full-fledged JavaScript object data types, since primitives have a constructor and prototype chain.

Listing 5-8 illustrates primitive and literal definitions with access to a constructor and prototype chain.

Listing 5-8. Primitives and literals with constructors & prototype chains
let lang = String("JavaScript");

console.log("lang value: %s", lang);
console.log("lang type: %s", typeof lang);
console.log("lang.constructor.name : %s", lang.constructor.name);
console.log("Object.getPrototypeOf(lang) : ",
Object.getPrototypeOf(lang));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(lang)) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(lang)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(lang))) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(lang))));


let language = "JavaScript";
console.log("language value: %s", language);
console.log("language type: %s", typeof language);
console.log("language.constructor.name : %s", language.constructor.name);
console.log("Object.getPrototypeOf(language) : ",
Object.getPrototypeOf(language));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(language)) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(language)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(language))) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(language))));


let languages = ["JavaScript","Python"];
console.log("languages value: %s", languages);
console.log("languages type: %s", typeof languages);
console.log("languages.constructor.name : %s", languages.constructor.name);
console.log("Object.getPrototypeOf(languages) : ",
Object.getPrototypeOf(languages));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(languages)) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(languages)));
console.log("Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(languages))) : %s",
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(languages))));

The first definition in listing 5-8 is a value created with the String() data type constructor as a plain function, where the log statements confirm it's a string primitive that has the String data type constructor, as well as a prototype chain. The second definition in listing 5-8 is declared as a literal "JavaScript" value, notice the log statements also confirm it's a string primitive that has the String data type constructor, as well as a prototype chain. Finally, the third definition is listing 5-8 is declared as literal array, notice the log statements confirm it's an Array object that has the Array data type constructor, as well as a prototype chain.

You can conclude from the examples in listing 5-8, that for data types to have access to a constructor or prototype chain, you don't necessarily need to create them it with the new keyword. The new keyword is just necessary to create and return full-fledged object instances, prototyped from the function it calls.

Prototype-based getters and setters with get and set keywords & property descriptors, the early years  

Encapsulation is one of the primary features in OOP (Object Orientated Programming) and consists of restricting access to object properties so they're administered through a dedicated set of methods. In OOP parlance, objects are said to have getters and setters to achieve orderly access to their properties.

In JavaScript, this means a getter method is used to return the value of an object property with business logic or formatting applied to it vs. returning a property's raw value. While a setter method is used to modify and set a property's raw value with business logic or formatting applied to it vs. setting the value of an object property with its raw input.

Getters and setters can be defined through the Object property descriptors get and set, as well as the get and set keywords directly in an object literal definition.

Listing 5-9 illustrates how to use both techniques to set an object's getter and setter methods.

Listing 5-9. Getters and setters for objects
var literalLanguage = {
 name : "Python",
 version: "3.10",
 hello: function() { return `Hello from ${this.name}`},
 get language() { 
   return `${this.name} ${this.version}`;
 },
 set language(value) { 
  [this.name, this.version] = value.split(" ");
 },
};

// Verify property value access
console.log(literalLanguage.name);
console.log(literalLanguage.version);
console.log(literalLanguage.hello());
// Access language that uses get 
console.log(literalLanguage.language);

// Update language with setter 
literalLanguage.language = "JavaScript 2022";

// Verify property values
// Access updated language that uses get
console.log(literalLanguage.language);
// language set updated inividual object properties  
console.log(literalLanguage.name);
console.log(literalLanguage.version);
console.log(literalLanguage.hello());

var Language = function(name,version) { 
 this.name = name;
 this.version = version;
 this.hello = function() { 
   return `Hello from ${this.name}`;
 }
};

var instanceLanguage = new Language("Python","3.10");

Object.defineProperties(Language.prototype, {
        language: {
             get: function()    { return `${this.name} ${this.version}`; }
            ,set: function(value) { [this.name, this.version] = value.split(" ");   }
        }
});

// Verify property value access
console.log(instanceLanguage.name);
console.log(instanceLanguage.version);
console.log(instanceLanguage.hello());
// Access language that uses get 
console.log(instanceLanguage.language);

// Update language with setter 
instanceLanguage.language = "JavaScript 2022";

// Verify property values
// Access updated language that uses get
console.log(instanceLanguage.language);
// language set updated inividual object properties  
console.log(instanceLanguage.name);
console.log(instanceLanguage.version);
console.log(instanceLanguage.hello());

Listing 5-9 starts by declaring a literal object, but notice the object properties prefixed with the get and set keywords. The get language() statement indicates that when the language property on the object is accessed (e.g. literalLanguage.language) it call this getter method. The set language(value) statement indicates that when a value is set on the language property (e.g. literalLanguage.language = "JavaScript") it call this setter method. In the case of the get statement, the logic consists of returning a composite string made up of an object's name and version properties, whereas in the case of the set statement, the logic consists of taking the value input and using it to reassign values to an object's name and version properties.

As you can see in listing 5-9, the literalLanguage object makes various calls to its properties, including getting the language value -- supported by get -- and setting the language -- supported by set. In each case, you can see how the getter and setter methods are used to retrieve and define object values, but more importantly, how the data remains encapsulated in the object.

The Language constructor function in listing 5-9 is like the literal object above it, but unlike the literalLanguage literal object that directly declares a getter with the get keyword and a setter with the set keyword, a constructor function cannot use the same syntax. For this reason, you can see that after the Language constructor function is declared and the object instanceLanguage is created, the Object.defineProperties() method is used to add a getter and setter to the object's language property.

The Object.defineProperties() method leveraged in listing 5-9 is the same one used in listing 4-11-A and described in the Object object property descriptors section. If you look closely, the first argument to Object.defineProperties() is Language.prototype, which indicates to add a property on the Language's prototype, meaning that all Language object instances will get said property. Next, is the language property statement which defines get and set property descriptors with getter and setter method logic like the one used in the literalLanguage literal object from the beginning of the listing. Finally, the same series of operations are performed on the instanceLanguage object instance, illustrating how it leverages the getter and setter methods added with Object.defineProperties() on custom data types built with constructor functions.

Do JavaScript objects have other encapsulation/accessibility constructs besides getters and setters ?


In most OOP languages, getter and setter methods are closely used in conjunction with private properties or protected methods to restrict direct access to the properties themselves. In JavaScript there's no such concept, however, JavaScript uses an ad hoc approach with underscore _ syntax to indicate something is not intended to be accessed directly.

As a mere convention, an underscore prefix _ (e.g. _dontaccess) is used to indicate a reference is private, while a double underscore prefix & suffix (e.g. __dontcall__) is used by browser makers to also indicate something is private. Of course, this doesn't preclude someone from accessing either of these constructs directly, it's simply a matter of a distinctive enough syntax to make it obvious something isn't meant to be called directly.

If you're looking for more formal support of private properties or protected methods in JavaScript, one option is to use TypeScript. TypeScript does support constructs like the private and protected keywords, which in combination with getters and setters provides a similar feel to encapsulation/accessibility behaviors present in other OOP languages (e.g. Java, C#).

Besides underscore _ & __ syntax, JavaScript also supports encapsulation/accessibility of objects through Object property descriptors, which is how getters and setters are supported, including the ability to change a property value, delete a property and contemplate a property for enumeration. In addition, it's also possible to freeze or seal JavaScript objects to limit what actions can be made on them.

JavaScript OOP keywords: class, constructor, extends, super, get, set and static, the modern years  

If you look closely again at the examples since the beginning of this chapter, you'll realize that with the exception of the subtle prototype-based programming behavior, JavaScript isn't too far away from most OOP behaviors. For example, JavaScript data types are very much like OOP classes, just as constructor functions are pretty similar to OOP constructors. In a similar way, listing 5-5 illustrates OOP functionalities like static properties, inheritance between objects, as well as the ability to call parent constructors like it's done in OOP inheritance. And let's not forget, data types also make use of the this keyword methods to reference instances, and can also implement getter/setter methods with the get and set keywords like other OOP languages.

However, for all these OOP syntax similarities JavaScript offers, they lack one thing: widely used OOP syntax as it's used in other programming languages. Someone with a background in another OOP language (e.g. Java, C#, C++) would be at a loss trying to interpret most of the OOP behavior written in the past sections. Therefore, starting with ES6 (ES2015) JavaScript aligned itself more closely to mainstream OOP syntax used in other languages to make JavaScript object-orientated and prototype-based programming more intuitve.

The class and constructor keywords: JavaScript classes

The class and constructor keywords are intended to make a clearer delineation between what constitutes a data type and its constructor function. Recall, in previous examples a function served as both a constructor and data type definition, while relying on a title case convention to determine if a function is a constructor. With the aid of the class keyword it becomes clearer what's referring to the blueprint of a data type, whereas the constructor keyword makes it clear a function is dedicated to build objects of the class it's associated with. Listing 5-10 illustrates how to use the class and constructor keywords.

Listing 5-10. Classes with class and constructor keywords
class Language  {
   constructor(name,version) {
     this.name = name;
     this.version = version;
   }
   hello() {
     return `Hello from ${this.name}`;
   }
}

// Create object instances from class
let javascript = new Language("JavaScript","2022");
let python = new Language("Python","3.10");


// Verify property value access
console.log(javascript.name);
console.log(javascript.version);
console.log(javascript.hello());
console.log(python.name);
console.log(python.version);
console.log(python.hello());

// Properties can be added to an object instances with class
python.typed = "Dynamically";
console.log(python.typed);
// But the javscript instance won't have a 'typed' property
console.log(javascript.typed); // undefined

// The 'prototype' is also accesible with class
Language.prototype.typed = "Dynamically";
// Now the javascript instance has a 'typed' property, because it was added to its prototype
console.log(javascript.typed);

First off, you can compare the examples in listing 5-10 with those in listing 5-4, because they both achieve the same end result but with different syntax.

The first statement in listing 5-10 relies on the class keyword to create a class named Languge. Next, inside the Language class statement are two methods: constructor and hello. If it wasn't obvious by its name, the constructor method is designed to be called every time an object instance of a class is created (i.e. with the new keyword), a concept which is almost universal across all OOP languages. In this case, the constructor method accepts two arguments which means all Language object instances must be created with two parameters, in addition, the constructor uses the two arguments to create the name and version object properties relying on the this keyword to reference the object instance. Finally, the hello class method returns a text message accompanied by the current value of an object's name property.

Next, two instances of the Language class are created with the new keyword and the statements that follow output the instance's properties and call its hello method, a process which is identical to the sequence presented in listing 5-4 but which relies on a constructor function and a function assigned to a property.

So what's the difference between creating JavaScript object instances like it's done in listing 5-10 and listing 5-4 ? Functionally none, the only difference is the syntax in listing 5-10 is much more obvious, particularlly for those with OOP experience in other languages. To further confirm both approaches are functionally equivalent, notice the second part of listing 5-10 illustrates how it's possible to add a property to an individual object instance, as well as alter the behavior of a class through its prototype, just like it's done in listing 5-2.

The extends and super keywords: JavaScript class inheritance

Inheritance is one of the major features of OOP since it allows classes to retain behaviors from other classes, a process which favors code reusability and the creation of object hierarchies. For example, with inheritance it's possible to have a parent class (e.g.Builiding) and reuse it to create more granular classes (e.g. ApartmentComplex, Hospital, Firehouse) without the need to reimplement the logic in the parent class.

Although early JavaScript syntax supports object inheritance -- as illustrated in listing 5-5 -- its implementation is not very obvious, especially if you compare it to other OOP languages which have dedicated keywords for inheritance scenarios. The extends and super keywords are intended to simplify JavaScript class inheritance. Listing 5-11 illustrates the use of the extends and super keywords in conjunction with the class and constructor keywords presented in the previous section.

Listing 5-11. JavaScript class inheritance with extends and super
class Letter {
  constructor(value) { 
        this.value = value;
    }
    
  iam() { 
   return `I am the ${this.constructor.name} ${this.value}`;
   }
   
  alphabet() { 
   return `${this.value} is letter No.${Letter.ALPHABET.indexOf(this.value)+1} in the alphabet`;
  }
  static ALPHABET = "abcdefghijklmnopqrstuvwxyz";
}



let test = new Letter("a");
console.log(test.iam());
console.log(test.alphabet());

class Vowel extends Letter {
  constructor(value) { 
  super(value);
  if (["a","e","i","o","u"].indexOf(value) === -1)
        throw new SyntaxError("Invalid vowel");
  }
}


let test2 = new Vowel("i");
console.log(test2.iam());
console.log(test2.alphabet());


let test3 = new Vowel("d"); // Raises syntax error in constructor

First off, you can compare the examples in listing 5-11 with those in listing 5-5, because they both achieve the same end result but with different syntax.

The first declaration in listing 5-11 defines the Letter class that makes use of the class and constructor keywords, in addition to declaring the iam() and alphabet() methods. Next, an instance of the Letter class is created and calls are made to its various methods. Up to this point, it's a standard class and object creation sequence.

The second declaration in listing 5-11 defines the Vowel class which makes use of the extends keyword with the Letter class. This syntax allows the Vowel class to inherit the same behaviors (i.e. properties and methods) declared in the Letter class. Next, inside the Vowel class you can see it only contains a constructor method which uses the super keyword and generates an error if a Vowel object is created with something other than vowel (i.e. a, e, i, o, u). So why doesn't the Vowel class have any properties and methods ? It could, but in this case it inherits the value property and iam() and alphabet() methods from the Letter class.

When a Vowel object instance is created with var test2 = new Vowel("i");, the Vowel constructor method is called and the super(value) tells JavaScript to call the parent class's constructor (i.e. Letter) which creates the value property and gives it access to the iam() and alphabet() methods. Next, you can see how it's possible to call the iam() and alphabet() methods on a Vowel object instance. Finally, the last line in listing 5-11 attempts to create a Vowel object instance with the d value, but because the constructor raises an error in case the input is not a vowel the object instance creation fails.

The get & set keywords: JavaScript class getters and setters

The get and set keywords offer greater versatilty to define getters and setters when used in the context of class definitions. Remember back in listing 5-9 how get and set statements were limited to literal objects or required using either the Object.defineProperty() or Object.defineProperties() methods ? With the introduction of classes, get and set statements can be part of a class just like they're used in other OOP languages. Listing 5-12 illustrates the use of getters and setters in JavaScript classes.

Listing 5-12. Getters and setters in classes
class Language  {
   constructor(name,version) {
     this._name = name;
     this._version = version;
   }
   get name() {
     return this._name;
   }
   set name(value) {
     this._name = value;
   }
   get version() {
     return this._version;
   }
   set version(value) {
     this._version = value;
   }
   hello() {
     return `Hello from ${this.name}`;
   }
}

// Create object instances from class
let javascript = new Language("JavaScript","2022");

// Verify property value access through getters 
console.log(javascript.name);
console.log(javascript.version);
console.log(javascript.hello());

// Reassign property values through setters
javascript.name = "ECMAScript";
javascript.version = "ES2022";

// Verify property value updates through getters
console.log(javascript.name);
console.log(javascript.version);
console.log(javascript.hello());

The Language constructor in listing 5-12 starts by creating the _name and _version object properties -- notice the leading underscore in both properties, which as mentioned previously is a common convention to name private JavaScript properties. Next, a couple of getters and setters are declared for the name and version properties, both of which leverage the _name and _version properties, respectively.

Next, a Languge instance is created and assigned to the javascript reference. Immediatly after, the instance's name and version properties are accessed, both of which are supported through the class's getter methods. Next, the instance's name and version properties are reassigned -- both of which are supported through the class's setter methods -- and later output to confirm the reassignment setter logic.

The static keyword: JavaScript class static properties

Finally, another functionality available in JavaScript classes is the static keyword. Remember back in listing 5-4, listing 5-5 and listing 5-6 you learned about object static properties & methods ? Static properties and methods are those that don't change between object instances and can be accessed without creating an object instance with new. With support for the static keyword, JavaScript classes support static properties and methods, as illustrated in listing 5-13.

Listing 5-13. Static properties in classes
class Language  {
   constructor(name,version) {
     this.name = name;
     this.version = version;
   }
   hello() {
     return `Hello from ${this.name}`;
   }
   static KIND = "HighLevel";
}

// Get Language static property 'KIND' 
console.log("Language.KIND: %s", Language.KIND);

// Create object instances with new 
let javascript = new Language("JavaScript","2022");
let python = new Language("Python","3.10");
// Static properties aren't available in object instances
console.log("javascript.KIND: %s", javascript && javascript.KIND ? javascript.KIND: undefined);
console.log("python.KIND: %s", python && python.KIND ? python.KIND: undefined);


class Square { 
  constructor(number, result) { 
     this.number = number;
     this.result = result;
  }
  static calculate = function(number) { 
    return number**2
  }
}

// Call Square static emthod calculate
console.log("Square.calculate(2): %s", Square.calculate(2));

// Create object instance with new 
var twoObject = new Square(2);
// Static methods aren't available in object instances
console.log("twoObject.calculate(2): %s", twoObject && twoObject.calculate ? twoObject.calculate(2): undefined);

The Language class in listing 5-13 contains the static KIND static property, identical to the static property declared in listing 5-4 added as a property to the constructor outside its main body. The use of the static keyword like it's used in other OOP languages, makes it much more obvious to detect when a JavaScript property is intended to be the same across object instances. More importantly, notice the static property behavior in a class, is the same as when declaring a static property in a constructor function, a static property is accessed without the new keyword and it can't be accessed from object instances.

The Square class in listing 5-13 also uses the static keyword to define the calculate static method, identical to the static method declared in listing 5-6 added as a method to the constructor outside its main body. In this case, you can also see the use of the static makes it more obvious to detect when a JavaScript method is intended to be the same across object instances. In addition, you can also confirm static methods are accesible without the new keyword and they can't be called from object instances.