Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Javascript Unlocked

You're reading from   Javascript Unlocked Improve your code maintainability, performance, and security through practical expert insights and unlock the full potential of JavaScript

Arrow left icon
Product type Paperback
Published in Dec 2015
Publisher
ISBN-13 9781785881572
Length 182 pages
Edition 1st Edition
Languages
Arrow right icon
Toc

Table of Contents (10) Chapters Close

Preface 1. Diving into the JavaScript Core FREE CHAPTER 2. Modular Programming with JavaScript 3. DOM Scripting and AJAX 4. HTML5 APIs 5. Asynchronous JavaScript 6. A Large-Scale JavaScript Application Architecture 7. JavaScript Beyond the Browser 8. Debugging and Profiling Index

The most effective way of declaring objects

How do we declare an object in JavaScript? If we need a namespace, we can simply use an object literal. But when we need an object type, we need to think twice about what approach to take, as it affects the maintainability of our object-oriented code.

Classical approach

We can create a constructor function and chain the members to its context:

"use strict"; 
/**
 * @class
 */
var Constructor = function(){
   /**
   * @type {String}
   * @public
   */
   this.bar = "bar";
   /**
   * @public
   * @returns {String}
   */
   this.foo = function() {
    return this.bar;
   };
 },
 /** @type Constructor */
 instance = new Constructor();

console.log( instance.foo() ); // bar
console.log( instance instanceof Constructor ); // true

We can also assign the members to the constructor prototype. The result will be the same as follows:

"use strict";
/**
* @class
*/
var Constructor = function(){},
   instance;
/**
* @type {String}
* @public
*/
Constructor.prototype.bar = "bar";
/**
* @public
* @returns {String}
*/
Constructor.prototype.foo = function() {
 return this.bar;
};
/** @type Constructor */
instance = new Constructor();

console.log( instance.foo() ); // bar
console.log( instance instanceof Constructor ); // true

In the first case, we have the object structure within the constructor function body mixed with the construction logic. In the second case by repeating Constructor.prototype, we violate the Do Not Repeat Yourself (DRY) principle.

Approach with the private state

So how can we do it otherwise? We can return an object literal by the constructor function:

"use strict";
/**
 * @class
 */
var Constructor = function(){
     /**
     * @type {String}
     * @private
     */
     var baz = "baz";
     return {
       /**
       * @type {String}
       * @public
       */
       bar: "bar",
       /**
       * @public
       * @returns {String}
       */
       foo: function() {
        return this.bar + " " + baz;
       }
     };
   },
   /** @type Constructor */
   instance = new Constructor();

console.log( instance.foo() ); // bar baz
console.log( instance.hasOwnProperty( "baz") ); // false
console.log( Constructor.prototype.hasOwnProperty( "baz") ); // false
console.log( instance instanceof Constructor ); // false

The advantage of this approach is that any variables declared in the scope of the constructor are in the same closure as the returned object, and therefore, available through the object. We can consider such variables as private members. The bad news is that we will lose the constructor prototype. When a constructor returns an object during instantiation, this object becomes the result of a whole new expression.

Inheritance with the prototype chain

What about inheritance? The classical approach would be to make the subtype prototype an instance of supertype:

"use strict";
 /**
 * @class
 */
var SuperType = function(){
       /**
       * @type {String}
       * @public
       */
       this.foo = "foo";
     },
     /**
      * @class
      */
     Constructor = function(){
       /**
       * @type {String}
       * @public
       */
       this.bar = "bar";
     },
     /** @type Constructor */
     instance;

 Constructor.prototype = new SuperType();
 Constructor.prototype.constructor = Constructor;

 instance = new Constructor();
 console.log( instance.bar ); // bar
 console.log( instance.foo ); // foo
 console.log( instance instanceof Constructor ); // true
 console.log( instance instanceof SuperType ); // true  

You may run into some code, where for instantiation Object.create is used instead of the new operator. Here you have to know the difference between the two. Object.create takes an object as an argument and creates a new one with the passed object as a prototype. In some ways, this reminds us of cloning. Examine this, you declare an object literal (proto) and create a new object (instance) with Object.create based on the first one. Whatever changes you do now on the newly created object, they won't be reflected on the original (proto). But if you change a property of the original, you will find the property changed in its derivative (instance):

"use strict";
var proto = {
 bar: "bar",
 foo: "foo"
}, 
instance = Object.create( proto );
proto.bar = "qux",
instance.foo = "baz";
console.log( instance ); // { foo="baz",  bar="qux"}
console.log( proto ); // { bar="qux",  foo="foo"}

Inheriting from prototype with Object.create

In contrast to the new operator, Object.create does not invoke the constructor. So when we use it to populate a subtype prototype, we are losing all the logic located in a supertype constructor. This way, the supertype constructor is never called:

// ...
SuperType.prototype.baz = "baz";
Constructor.prototype = Object.create( SuperType.prototype );
Constructor.prototype.constructor = Constructor;

instance = new Constructor();

console.log( instance.bar ); // bar
console.log( instance.baz ); // baz
console.log( instance.hasOwnProperty( "foo" ) ); // false
console.log( instance instanceof Constructor ); // true
console.log( instance instanceof SuperType ); // true

Inheriting from prototype with Object.assign

When looking for an optimal structure, I would like to declare members via an object literal, but still have the link to the prototype. Many third-party projects leverage a custom function (extend) that merge the structure object literal into the constructor prototype. Actually, ES6 provides an Object.assign native method. We can use it as follows:

"use strict";
   /**
    * @class
    */
var SuperType = function(){
     /**
     * @type {String}
     * @public
     */
     this.foo = "foo";
   },
   /**
    * @class
    */
   Constructor = function(){
     /**
     * @type {String}
     * @public
     */
     this.bar = "bar";
   },
   /** @type Constructor */
   instance;

Object.assign( Constructor.prototype = new SuperType(), {
 baz: "baz"
});
instance = new Constructor();
console.log( instance.bar ); // bar
console.log( instance.foo ); // foo
console.log( instance.baz ); // baz
console.log( instance instanceof Constructor ); // true
console.log( instance instanceof SuperType ); // true

This looks almost as required, except there is one inconvenience. Object.assign simply assigns the values of source objects to the target ones regardless of their type. So if you have a source property with an object (for instance, an Object or Array instance), the target object receives a reference instead of a value. So you have to reset manually any object properties during initialization.

Approach with ExtendClass

ExtendClass, proposed by Simon Boudrias, is a solution that seems flawless (https://github.com/SBoudrias/class-extend). His little library exposes the Base constructor with the extend static method. We use this method to extend this pseudo-class and any of its derivatives:

"use strict";
   /**
    * @class
    */
var SuperType = Base.extend({
     /**
      * @pulic
      * @returns {String}
      */
     foo: function(){ return "foo public"; },
     /**
      * @constructs SuperType
      */
     constructor: function () {}
   }),
   /**
    * @class
    */
   Constructor = SuperType.extend({
     /**
      * @pulic
      * @returns {String}
      */      
     bar: function(){ return "bar public"; }
   }, {
     /**
      * @static
      * @returns {String}
      */      
     bar: function(){ return "bar static"; }
   }),
   /** @type Constructor */
   instance = new Constructor();
   
console.log( instance.foo() ); // foo public
console.log( instance.bar() ); // bar public
console.log( Constructor.bar() ); // bar static
console.log( instance instanceof Constructor ); // true
console.log( instance instanceof SuperType ); // true

Classes in ES6

TC39 (the EcmaScript working group) is pretty aware of the problem, so the new language specification provides extra syntax to structure object types:

"use strict";
class AbstractClass {
 constructor() {
   this.foo = "foo";
 }
}
class ConcreteClass extends AbstractClass {
 constructor() {
   super();
   this.bar = "bar";
 }
 baz() {
   return "baz";
 }
}

let instance = new ConcreteClass();
console.log( instance.bar ); // bar
console.log( instance.foo ); // foo
console.log( instance.baz() ); // baz
console.log( instance instanceof ConcreteClass ); // true
console.log( instance instanceof AbstractClass ); // true

The syntax looks class-based, but in fact this a syntactic sugar over existing prototypes. You can check with the type of ConcreteClass, and it will give you function because ConcreteClass is a canonical constructor. So we don't need any trick to extend supertypes, no trick to refer the supertype constructor from subtype, and we have a clean readable structure. However, we cannot assign properties the same C-like way as we do now with methods. This is still in discussion for ES7 (https://esdiscuss.org/topic/es7-property-initializers). Besides this, we can declare a class's static methods straight in its body:

class Bar {
 static foo() {
   return "static method";
 }
 baz() {
   return "prototype method";
 }
}
let instance = new Bar();
console.log( instance.baz() ); // prototype method
console.log( Bar.foo()) ); // static method

Actually, there are many in the JavaScript community who consider the new syntax as a deviation from the prototypical OOP approach. On the other hand, the ES6 classes are backwards compatible with most of the existing code. Subclasses are now supported by the language and no extra libraries are required for inheritance. And what I personally like the most is that this syntax allows us to make the code cleaner and more maintainable.

You have been reading a chapter from
Javascript Unlocked
Published in: Dec 2015
Publisher:
ISBN-13: 9781785881572
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image