Wednesday, April 20, 2011

JavaScript Tips and Tricks; JavaScript Best Practices

Please, share your tips and tricks related to JavaScript coding. The ones which make code more elegant and faster.

See also:

From stackoverflow
  • Use of a library such as jQuery

  • Check if an object is not empty

    // JavaScript 1.5 
    function isNotEmpty(obj) {
        for (var tmp in obj)
            return true
    }
    
    // JavaScript 1.8 
    function isNotEmpty(obj) obj.__count__;
    

    Transform the arguments object into an array

    function foo() {
        Array.slice(arguments); // is ['aa',11]
    }
    foo('aa', 11);
    

    JavaScript Minifier / comment remover

    var script = new Script('var a; /* this is a variable */ var b; ' +
                 '// another variable');
    Print( script.toString() );
    // prints:
    // var a;
    // var b;
    

    Singleton pattern

    function MySingletonClass() {
        if ( arguments.callee._singletonInstance )
            return arguments.callee._singletonInstance;
        arguments.callee._singletonInstance = this;
        this.Foo = function() {
            // ...
        }
    }
    
    var a = new MySingletonClass();
    var b = MySingletonClass();
    Print( a === b ); // prints: true
    

    Factory method pattern

    Complex = new function() {
        function Complex(a, b) {
            // ...
        }
        this.fromCartesian = function(real, mag) {
            return new Complex(real, imag);
        }
        this.fromPolar = function(rho, theta) {
            return new Complex(rho * Math.cos(theta), rho * Math.sin(theta));
    }}
    var c = Complex.fromPolar(1, Math.pi); // Same as fromCartesian(-1, 0);
    

    Use Functions as an Object

    function Counter() {
        if ( !arguments.callee.count ) {
            arguments.callee.count = 0;
        }
        return arguments.callee.count++;
    }
    
    Print( Counter() ); // prints: 0
    Print( Counter() ); // prints: 1
    Print( Counter() ); // prints: 2
    

    Listen a property for changes

    function onFooChange( id, oldval, newval ) {
        Print( id + " property changed from " + oldval + " to " + newval );
        return newval;
    }
    
    var o = { foo:5 };
    o.watch( 'foo', onFooChange );
    o.foo = 6;
    delete o.foo;
    o.foo = 7;
    o.unwatch('foo');
    o.foo = 8;
    
    // prints: 
    // foo property changed from 5 to 6
    // foo property changed from undefined to 7
    
    bobince : Accessing ‘slice’ directly through the Array object instead of Array.prototype is non-standard and won't work everywhere. Instead use “Array.prototype.slice.call(arraylike, begin[, end])”.
    bobince : (However technically even this usage is non-standard as Array methods are not defined to work on non-Array objects, and it would depend on the internal implementation whether it was OK or not. However, since this trick is quite widely used it will probably continue to be OK in practice.)
    KooiInc : Array.prototype.slice.call(arraylike, begin[, end]) seldom works with IE collections (like stylesheet rules)
    IonuČ› G. Stan : What's that Singleton pattern in there? What's wrong with object literals?
    Sander Versluys : Although not all of them are clear to me why to use them in javascript, some look like really good examples and stuff to think about! thanks ;-)
  • Trick

    Use the "javascript:" protocol

    Youc can store js code in a bookmark so in can be used in any Web site you visit. It's called "bookmarklet", and it's a hell of a fun :-)

    Some bookmarklets for web dev.

    Javascript to enhance IE

    If you are like most of the Web designers, you want IE6 (en 5, 4, etc) to die. But there is a way to make him interpret the CSS just like IE7, which is much better.

    Use conditional comments to load a JS librairy that will tweak his behavior :

    <!--[if lt IE 7]>
    <script src="http://ie7-js.googlecode.com/svn/version/2.0(beta3)/IE7.js"
            type="text/javascript"></script>
    <![endif]-->
    

    Now, you can deal more easily with IE, and not care about the version. IE6 users will have to wait some seconds for JS to load the first visit, that's all. And for those who have IE6 and JS disable, well, they will see a crappy Website but they are masochist anyway ;-)

    Iterate over Arrays using "for in"

    I think everybody know the usefullness of the "for in" loop :

    // creating an object (the short way, to use it like a hashmap)
    var diner = {
    "fruit":"apple"
    "veggetable"="bean"
    }
    
    // looping over its properties
    for (meal_name in diner ) {
        document.write(meal_name+"<br \n>");
    }
    

    Result :

    fruit
    veggetable
    

    But there is more. Since you can use an object like an associative array, you can process keys and values, just like a foreach loop :

    // looping over its properties and values
    for (meal_name in diner ) {
        document.write(meal_name+" : "+diner[meal_name]+"<br \n>");
    }
    

    Result :

    fruit : apple
    veggetable : bean
    

    And since Array are objects too, you can iterate other array the exact same way :

    var my_array = ['a', 'b', 'c'];
    for (index in my_array ) {
        document.write(index+" : "+my_array[index]+"<br \n>");
    }
    

    Result :

    0 : a
    1 : b
    3 : c
    

    Remove easily an known element from an array

    var arr = ['a', 'b', 'c', 'd'];
    var pos = arr.indexOf('c');
    pos > -1 && arr.splice( pos, 1 );
    

    Best practice

    Always use the second argument parseInt()

    parseInt(string, radix)
    

    Indeed, parseInt() converts a string to int, but will try to guess the numeral system if you omit radix, following the rules :

    • If the string begins with "0x", the radix is 16 (hexadecimal)
    • If the string begins with "0", the radix is 8 (octal). This feature is deprecated
    • If the string begins with any other value, the radix is 10 (decimal)

    Most of the time, you will use :

    var my_int = parseInt(my_string, 10);
    

    Check the property ownership while using "for in"

    "for in" iterate other the object properties, but the properties can be ineritated from the object prototype. Since anyone can dynamically alter prototypes, on sensible objects you'd better check if the property is its own before using it :

     for (var name in object) {
          if (object.hasOwnProperty(name)) { 
    
         }
      }
    
    Steve Harrison : Yes, I learnt that "parseInt" best practice the hard way!
    Sander Versluys : Again, great suggestions, this is an awesome question! txn
  • prototype extension of native objects for utility methods

    A couple of my favourites:

    String.prototype.reverse = function () 
    {
        return this.split('').reverse().join('');
    };
    
    Date.prototype.copy = function ()
    {
        return new Date(this);
    };
    
  • Remedial work. Use prototype-based monkey-patching to add new standardised features to old and crappy browsers.

    // Add JavaScript-1.6 array features if not supported natively
    //
    if (![].indexOf) {
        Array.prototype.indexOf= function(find) {
            for (var i= 0; i<this.length; i++)
                if (this[i]==find)
                    return i;
            return -1;
        };
    }
    if (![].map) {
        Array.prototype.map= function(fn) {
            var out= [];
            for (var i= 0; i<this.length; i++)
                out.push(fn(this[i]));
            return out;
        };
    }
    if (![].filter) {
        Array.prototype.filter= function(fn) {
            var out= [];
            for (var i= 0; i<this.length; i++)
                if (fn(this[i]))
                    out.push(this[i]);
            return out;
        };
    }
    
    // Add ECMAScript-3.1 method binding if not supported natively.
    // Note, uses a direct reference, which if assigned to an event
    // handler will cause reference loops/memory leaking in IE6.
    // (Could add a lookup-map cycle-breaker to improve this in the
    // future.)
    //
    if (!Function.prototype.bind) {
        Function.prototype.bind= function(owner/* {, args} */) {
            var method= this;
            var args= Array_fromSequence(arguments).slice(1);
            return function() {
                var allargs= args.length==0? arguments : arguments.length==0? args : Array_fromSequence(args, arguments);
                return method.apply(owner, allargs);
            };
        };
    }
    
    // Compile an Array from one or more Array-like sequences (arguments, NodeList...)
    // Used by Function.bind.
    //
    Array_fromSequence= function(/* args */) {
        var arr= [];
        for (var argi= 0; argi<arguments.length; argi++)
            for (var itemi= 0; itemi<arguments[argi].length; itemi++)
                arr.push(arguments[argi][itemi]);
        return arr;
    };
    

    Generally, it's worth being conservative with the prototype patching, because changes you make will affect all scripts on the page, potentially causing bad interactions. Adding arbitrary methods can go wrong when another script tries to use the same member name for another purpose. But fixing up browsers to comply with new standards is generally harmless.

    Tchalvak : If you're going to go that far, using a framework and sharing work seeems like a better way to go.
  • Logical operators tricks

    var a = 5;
    a == 5 && Print( 'a is 5 \n' );
    a == 7 || Print( 'a is not 7 \n' );
    
    // prints: 
    // a is 5
    // a is not 7
    

    Remove an item by value in an Array object

    var arr = ['a', 'b', 'c', 'd'];
    var pos = arr.indexOf( 'c' );
    pos > -1 && arr.splice( pos, 1 );
    Print( arr ); // prints: a,b,d
    

    shuffle the Array

    var list = [1,2,3,4,5,6,7,8,9];
    list = list.sort(function() Math.random() > 0.5 ? 1 : -1);
    Print( list );
    // prints something like: 4,3,1,2,9,5,6,7,8
    

    Optional named function arguments

    function foo({ name:name, project:project}) {
        Print( project );
        Print( name );
    }
    foo({ name:'web', project:'so' })
    foo({ project:'so', name:'web'})
    

    Might not work in FF. See Comments.

    Floating to integer

    (123.345456).toFixed(); // is: 123
    typeof (1.5).toFixed(); // is: string
    

    Change the primitive value an object with valueOf

    function foo() {
        this.valueOf = function() {
            return 'this is my value';
    }}
    var bar = new foo();
    Print( bar ); // prints: this is my value
    

    New object override

    function foo() {
        return new Array(5,6,7);
    }
    var bar = new foo();
    bar.length // is 3
    

    Nested functions and closures

    function CreateAdder( add ) {
        return function( value ) {
            return value + add;
    }}
    // usage: 
    var myAdder5 = CreateAdder( 5 );
    var myAdder6 = CreateAdder( 6 );
    Print( myAdder5( 2 ) ); // prints 7
    Print( myAdder6( 4 ) ); // prints 10
    

    SyntaxError

    Raised when a syntax error occurs while parsing code in eval()

    try {
        eval('1 + * 5'); // will rise a SyntaxError exception
    } catch( ex ) {
        Print( ex.constructor == SyntaxError ); // Prints true
    }
    

    ReferenceError

    Raised when de-referencing an invalid reference.

    try {
        fooBar(); // will rise a ReferenceError exception
    } catch( ex ) {
        Print( ex.constructor == ReferenceError ); // Prints true
    }
    
    kentaromiura : the latest trick work only in FF. a similar cross-browser trick can be: function foo(x){ with(x){ alert( project ); alert( naame ); } } foo({ naame:'web', project:'so' }) foo({ project:'so', naame:'web'}) but with statement must be used carefully
    Koistya Navin : @kentaromiura, good point. Thanks.
  • Solid type-checking function:

    function type(v) {
        if (v === null) { return 'null'; }
        if (typeof v === 'undefined') { return 'undefined'; }
        return Object.prototype.toString.call(v).match(/\s(.+?)\]/)[1].
               toLowerCase();
    }
    // Usage:
    type({}); // 'object'
    type([]); // 'array'
    type(333); // 'number'
    

    Find the maximum or minimum value in an array of numbers:

    var arrNumbers = [1,4,7,3,5,9,3,2];
    Math.max.apply(null, arrNumbers); // 9
    Math.min.apply(null, arrNumbers); // 1
    
  • I often see people that doesn't have any clue of how javascript really works. So I wish to point out this document: javascript prototypal inheritance

    Sorry, too many time I see people call new without actually fully understanding what they are really doing in javascript.

    Update: Douglas Crockford on prototypal inheritance

  • Test if an image has loaded:

    function hasLoaded ( img ) {
      return img.complete && img.naturalWidth !== 0;
    }
    

    Dec to hex / hex to dec:

    Amazingly people still try to do this computationally:

    // number to hexadecimal
    '0x' + Number( 15 ).toString( 16 );
    
    // hexadecimal to number
    parseInt( String( '8f3a5' ), 16 );
    

    String repeat:

    Perhaps not be the fastest way of doing this, but a nifty approach I picked up somewhere:

    String.prototype.repeat = function ( n ) {
      return new Array( (n || 1) + 1 ).join( this );
    }
    

    Get number signature with boolean math operations:

    Not very useful, but an interesting demonstration of booleans vs. the +/- operators. I've used it in GPS coordinates formating:

    function signature ( n ) {
      return ['-','','+'][ -(0 > n) +(0 < n) +1 ];
    }
    

    Quick & easy namespaces:

    String.prototype.namespace = function ( obj ) {
      var c, b = this.split('.'), 
          p = window;
      while (c = b.shift()) {
        p = (p[c] = (p[c] || {}));
      }
      if (typeof obj === 'object') {
        for (var k in obj) {
          p[k] = obj[k];
        }
      }
      return p;
    };
    

    Doesn't necessarily have to sit on the string prototype, I just think it's neat for the purposes of this example:

    "com.stackoverflow.somemodule".namespace({
      init: function () {
        // some code
      }
    })
    
  • String format like function:

    String.prototype.format = function(){
        var pattern = /\{\d+\}/g;
        var args = arguments;
        return this.replace(pattern, function(capture){ return args[capture.match(/\d+/)]; });
    }
    

    //Example

    var a = 'hello, {0}';
    var b = a.format('world!');
    

0 comments:

Post a Comment