Showing posts with label stoyan stefanov. Show all posts
Showing posts with label stoyan stefanov. Show all posts

Thursday, February 26, 2015

"JavaScript Patterns" - book review

Hello. In this post I will share my thoughts on a book called "JavaScript Patterns" written by Stoyan Stefanov. Published in 2010 this book in about 210 pages covers the most important patterns that you can use in JavaScript to ease you work with this language. My guess is that this book is not for beginner JavaScript programmer, but you can always read this great book by Douglas Crockford, "JavaScript: The Good Parts" as an intro to JavaScript. These two books go hand in hand. Check my other book review here: http://tunephp.blogspot.com/2014/11/javascript-good-parts-book-review.html
If you really want to learn proper JS programming read both of them, first "JS the good parts" and then "JS Patterns" and try some programming practices in Firefox/Firebug.

The book has 8 chapters and has lot of useful code snippets you can use in building your brand new JavaScript application.

  1. Intruduction. Explains basic concepts of JS, like there are no classes in JS, everything is a object including, functions, arrays, objects etc... It also notes that you can use strict ("use strict", ECMAScript 5) mode, a modification of JS that removes some of the bad parts in JS. This chapter also introduces JSLint and the Console as debugging tools. You should always check your code with JSLint.
  2. Essentials. This chapter covers one of the main drawbacks in JS, and those are global variables. It also gives some tips how to avoid using too many globals, how to use "for", "for in" loops, avoiding implied typecasting with "==" sign, number conversions with parseInt() and parseFloat(), writing proper code, indentation, writing comments and using Yuidoc and JSLint.
  3. Literals and Contructors. It covers Object literal ({}) notation, JS constructors and naming convention for them and how to avoid problems using constructors. Array literals, check for "Arrayness" (arrays are objects in JS), working with JSON, regular expressions, primitive wrappers and throwing Errors/Exceptions.
  4. Functions. Covers function decalrations vs function expressions. Variable hoisting, function scope, function callbacks, returning functions, self defining functions, immediate functions and couple of useful tricks like memoization, curry, init-time branching etc... Probably the most important chapter in the book, you must not miss it as functions are the hearth of JavaScript.
  5. Object Creaing Patterns. Some object creating patterns like namespace patters(there is a very useful function for creating a tree of subobject, you can use it in your project.), private members, how to achieve real privacy in JS, Module pattern, Sandbox patterns, static members, JS "Constants", Chaining pattern etc... This chapter gives you better idea how to structure your code so that your project doesn't have trouble in the end.
  6. Code Reuse Patterns. Classical vs Prototypal inheritance patterns. You should avoid Class inheritance in JS since it is just a "syntaxic sugar", and always use Prototypal inheritance. This chapter also mentions some useful code reuse techniques, like borrowing methods, copying properties, some Class patterns etc...
  7. Design Patterns. This chapter covers how to code basic software engineering patterns in JavaScript, probably a chapter you don't want to miss. Singleton, Factory, Iterator, Decorator, Strategy, Facade, Proxy, Mediator and Observer. 
  8. DOM and Browser Patters. Covers some tools/concepts/patterns that can be used in a browser, like how to manipulate DOM tree, event handling, long running scripts, Ajax alikes, JSNOP, combining JS scripts, minifying, Loading techniques and preloading.
 So yes, this book cover very useful features of JavaScript and how to use them in browser. If you really want to become a web dev. this is the right book for you.

There are similarities with "JavaScript: The Good Parts" in the beginning, I felt like I was reading Doug's book again, but that is ok. It has lot of code and it is well explained so my grade for it is 4.5/5. Defineitely a book you should read.

Thursday, January 8, 2015

JavaScript Patterns - practice application

Hello. In this post I will explain some JavaScript patters using a script I made.
I have being reading this book: JavaScript Patterns by Stoyan Stefanov, and I covered half by now.

 You can download the script from this link https://github.com/bluePlayer/practices/tree/master/CalculatorJS

I also wrote comments for each property/method/object/module in YuiDoc format and generated in Java style, documentation using YuiDoc Node.js extension. For more info check this link: http://yui.github.io/yuidoc/

So the name of my application is APPLICATION and it is written with capitalized letters because it is global variable. Some JS coding convetions say that you should write constants and globals with capitalized letters because it is easier to distingush from other properties/variables.

It is always good to initialize main application object if it doesn't exist, or prevent initiazlization if it already exist:

var APPLICATION = {};
if (typeof APPLICATION === "undefined") {
    var APPLICATION = {};
}
// shorter
var APPLICATION = APPLICATION || {};

While I was reading "JavaScript Patterns" it mentioned a general namespace function which enables you to create objects and subobjects by just sending dot notated string, like this: "my.new.object".
I made some improvements to this function and added it as a part of the application's namespace, like this:

APPLICATION.namespace = function (nsString, newObjectDefinition) {
    var parts = nsString.split('.'),
        parent = this,
        newObject = {};
       
    newObject.Constants = new this.Constants();
    newObject.constMap = {};
   
    if (parts[0] === this.getName()) {
        parts = parts.slice(1);
    }
   
    for (var i = 0; i < parts.length; i += 1) {
        if (typeof parent[parts[i]] === "undefined") {           
            for (var property in newObjectDefinition) {
                if (property === property.toUpperCase()) {
                    newObject.Constants.set(property, newObjectDefinition[property]);
                    newObject.constMap[property] = property;
                } else {
                    newObject[property] = newObjectDefinition[property];
                }
            }
            parent[parts[i]] = newObject;
        }
        parent = parent[parts[i]];
    }   
   
    return parent;
};


I added a second parameter which adds functionality to the last object created in the tree. So if the fisrt parameter is string: "my.new.object", the function will create three objects, my, new and object in the APPLICATION's tree and object will be initialized with properties and functions from the second parameter. I also added two more subobjects inside every newly created object, so my, new, object will have Contants and constMap objects. Constants is object which gets created with Constants() constructor (from APPLICATION) and it contains, set(), get(), isDefined(), list(). On the other hand constMap contains the keys for calling constants using the get() function. So to get a constant value you should do this:

var myVar = APP.subModule.Constants.get(APP.subModule.constMap.CONST_NAME);

Note: This looks quite clumsy and big. Lot of letters to write. Perhaps it will be easier to write in some JS IDE like WebStrom. Anyway, at the time of writing this code I realised there is a place for improvement. Maybe there should be one general get() function for constants in the application.

As you can see from the application I use namespace pattern. All constructors, methods, events, properties, submodules should be part of one general object. Also I have submodules like, Main, Trigonometry, EventFunctions  and all related properties or methods are kept in its own submodule. This is called module pattern.

What if I want to add static properties? Well you can do it easily by just adding a new property to a given object or subobject. You can also use prototype part of the object and it will be inherited.
example:

var MyObject = function () {};
MyObject.name = "TunePHP";
var newObject = new MyObject();
typeof newObject.name === "undefined" //true
MyObject.prototype.name = MyObject.name;
console.log(newObject.name); // "TunePHP";

It is always useful to have a main init() method for the whole application, where you will initialize all necessary objects/variables/properties after the page has loaded.

Here is the complete code I wrote. Let me know your thoughts.

var APPLICATION = APPLICATION || {
    name: "Calculator",
    version: "1.0.0",
    Constants: function () {},
    namespace: function () {},
    getName: function () {
        return name;
    },
   
    init: function (config) {
        try {
            this.Calculator.Main.currentState = this.Calculator.Main.Constants.get(this.Calculator.Main.constMap.STANDARD);
            this.Calculator.Trigonometry.currentAngleFormat = this.Calculator.Trigonometry.Constants.get(this.Calculator.Trigonometry.constMap.DEGREES);
            return this;
        } catch (error) {
            console.log(error.message);
            return false;
        }
    }
};

APPLICATION.Constants = function (constMapObject) {
    var constants = {},
        ownProp = Object.prototype.hasOwnProperty,
        allowed = {
            string: 1,
            number: 1,
            boolean: 1
        },
        prefix = (Math.random() + "_").slice(2);
       
    return {
   
        set: function (name, value) {
            if (this.isDefined(name)) {
                return false;
            }
            if (!ownProp.call(allowed, typeof value)) {
                return false;
            }
                   
            constants[prefix + name] = value;
            /**
             * @todo use constMapObject to create new constant key in the given constants map object.
             */
            return true;
        },

        isDefined: function (name) {
            return ownProp.call(constants, prefix + name);
        },
       
        get: function (name) {
            if (this.isDefined(name)) {
                return constants[prefix + name];
            }
            return null;
        },
       
        list: function () {
            var index = 0,
                newStr = "";
               
            for (constant in constants) {
                index = constant.indexOf('_');
                newStr = constant.substr(index + 1);
                console.log(newStr + ": " + constants[constant]);
            }
        }
    };
};

APPLICATION.namespace = function (nsString, newObjectDefinition) {
    var parts = nsString.split('.'),
        parent = this,
        newObject = {};
       
    newObject.Constants = new this.Constants();
    newObject.constMap = {};
   
    if (parts[0] === this.getName()) {
        parts = parts.slice(1);
    }
   
    for (var i = 0; i < parts.length; i += 1) {
        if (typeof parent[parts[i]] === "undefined") {           
            for (var property in newObjectDefinition) {
                if (property === property.toUpperCase()) {
                    newObject.Constants.set(property, newObjectDefinition[property]);
                    newObject.constMap[property] = property;
                } else {
                    newObject[property] = newObjectDefinition[property];
                }
            }
            parent[parts[i]] = newObject;
        }
        parent = parent[parts[i]];
    }   
   
    return parent;
};

 APPLICATION.namespace('Calculator', {
   
});

APPLICATION.namespace('Calculator.Main', {
   
    STANDARD: 1,

    SCIENTIFIC: 2,

    PROGRAMMER: 3,
   
    currentState: null,

    currentValue: 0,

    memoValue: 0,
   
    digitGrouping: false,
   
    historyPanel: false,
   
    unitConversionPanel: false,

    dateCalculationPanel: false,

    changeState: function (event) {
        /**
         * @todo currentState = event.newState;
         */
    },
       
    showHistoryPanel: function (event) {
        /**
         * @todo
         */
    },
       
    showUnitConversionPanel: function (event) {
        /**
         * @todo
         */
    },

    showDateCalculationPanel: function (event) {
        /**
         * @todo
         */
    }
});

APPLICATION.namespace('Calculator.Trigonometry', {

    DEGREES: "degrees",
   
    RADIANS: "radians",

    GRADS: "grads",
   
    currentAngleFormat: null
});

APPLICATION.namespace('Calculator.EventFunctions', {

    pressZero: function (event) {
   
    },

    pressOne: function (event) {
   
    },

    pressTwo: function (event) {
   
    },
   
    pressSum: function (event) {
   
    },

    pressSubtract: function (event) {
   
    },
   
    pressMultiply: function (event) {
   
    },

    pressDivide: function (event) {
   
    }
});

APPLICATION.init({});