Oct 23, 2009

Best practice to develop jQuery plugin

  1. Create a private scope for $
  2. Attach plugin to $.fn alias
  3. Add implicit iteration
  4. Enable chaining
  5. Add default options
  6. Add custom options
  7. global custom options
<div id="counter1">
</div>
<div id="counter2">
</div>
<script>
(function($) {
    $.fn.count = function(customOptions){

        var options = $.extend({},$.fn.count.defaultOptions, customOptions);
       
        return this.each(function(){
            var $this = $(this);
            $this.text(options.startCount);
            var myInterval = window.setInterval(function(){
                var currentCount = parseFloat($this.text());
                var newCount = currentCount+1;
                $this.text(newCount+'');
            }, 1000);
        });
       
    };
       
    $.fn.count.defaultOptions = {
        startCount:'100'
    };

})(jQuery);
 
jQuery.fn.count.defaultOptions.startCount = '300';

jQuery('#counter1').count();
jQuery('#counter2').count({startCount:'500'});
 
</script>

Oct 21, 2009

javascript reflection in jQuery

String: typeof object === "string"
Number: typeof object === "number"
Boolean: typeof object === "boolean"
Object: typeof object === "object"
Function: jQuery.isFunction(object)
Array: jQuery.isArray(object)
Element: object.nodeType
null: object === null
undefined: typeof variable === "undefined" or object.prop === undefined
null or undefined: object == null


 toString = Object.prototype.toString,
 
 //var x = { name :"fred" };
    //alert(x.hasOwnProperty("name"));
 hasOwn = Object.prototype.hasOwnProperty,

 push = Array.prototype.push,

 isFunction: function( obj ) {
  return toString.call(obj) === "[object Function]";
 },

 isArray: function( obj ) {
  return toString.call(obj) === "[object Array]";
 },

 isPlainObject: function( obj ) {
  // Must be an Object.
  // Because of IE, we also have to check the presence of the constructor property.
  // Make sure that DOM nodes and window objects don't pass through, as well
  if ( !obj || 
    toString.call(obj) !== "[object Object]" || 
    obj.nodeType || 
    obj.setInterval ) {
    
   return false;
  }
  
  // Not own constructor property must be Object
  if ( obj.constructor &&
   !hasOwn.call(obj, "constructor") &&
   !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
   return false;
  }
  
  // Own properties are enumerated firstly, so to speed up,
  // if last one is own, then all properties are own.
 
  var key;
  for ( key in obj ) {}
  
  return key === undefined || hasOwn.call( obj, key );
 },

 isEmptyObject: function( obj ) {
  for ( var name in obj ) {
   return false;
  }
  return true;
 },

isWindow: function( obj ) {
 return obj && typeof obj === "object" && "setInterval" in obj;
},

//new in jQuery 1.4
$.type(object);

Oct 17, 2009

it must be "new"ed.

The following is constructor which prevent not using "new" keyword


function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) 
    return new User(first, last); 
   
  this.name = first + " " + last; 
} 

Oct 16, 2009

javascript scope

In JavaScript, {blocks} do not have scope. Only functions have scope. Vars defined in a function are not visible outside of the function.

object literal

var myObject = { name: "Jack B. Nimble", 'goto': 'Jail', grade: 'A', level: 3, "3": "three" };
alert(myObject.name);
alert(myObject["name"]);
alert(myObject["goto"]);
alert(myObject.goto); //ok
alert(myObject["3"]);
//alert(myObject.3); //error

function overload

function.length can tell you the number of parameters defined, using this we can create overload functions

function addMethod(object, name, fn){ 
  // Save a reference to the old method 
  var old = object[ name ]; 
 
  // Overwrite the method with our new one 
  object[ name ] = function(){ 
    // Check the number of incoming arguments, 
    // compared to our overloaded function 
    if ( fn.length == arguments.length ) 
      // If there was a match, run the function 
      return fn.apply( this, arguments ); 
 
    // Otherwise, fallback to the old method 
    else if ( typeof old === "function" ) 
      return old.apply( this, arguments ); 
  }; 
} 
 
function Ninjas(){ 
  var ninjas = [ "Dean Edwards", "Sam Stephenson", "Alex Russell" ]; 
  addMethod(this, "find", function(){ 
    return ninjas; 
  }); 
  addMethod(this, "find", function(name){ 
    var ret = []; 
    for ( var i = 0; i < ninjas.length; i++ ) 
      if ( ninjas[i].indexOf(name) == 0 ) 
        ret.push( ninjas[i] ); 
    return ret; 
  }); 
  addMethod(this, "find", function(first, last){ 
    var ret = []; 
    for ( var i = 0; i < ninjas.length; i++ ) 
      if ( ninjas[i] == (first + " " + last) ) 
        ret.push( ninjas[i] ); 
    return ret; 
  }); 
} 
 
var ninjas = new Ninjas(); 
assert( ninjas.find().length == 3, "Finds all ninjas" ); 
assert( ninjas.find("Sam").length == 1, "Finds ninjas by first name" ); 
assert( ninjas.find("Dean", "Edwards").length == 1, "Finds ninjas by first and last name" ); 
assert( ninjas.find("Alex", "X", "Russell") == null, "Does nothing" );

later method

Object.prototype.later = function (msec, method) {
    var context = this,
    args = Array.prototype.slice.apply(arguments, [2]); 
    if (typeof method === 'string') { 
        method = context[method]; 
    } 
    setTimeout(function () { 
        method.apply(context, args); 
    }, msec); 
    return context; 
}; 

var o = {
  name: "fred",
  greet: function (msg) {
          alert("my name is " + this.name + "," + msg);
  }
}
    
o.later(1000, function () { alert("hello");});
o.later(1000, "greet", "how are you?");
​

fixed a closure bug

Closure refer the ability that a function can access and manipulate external variable from with a function. Sometimes, this is not good because the if a function depends on the external state. When the external state changes, the function may produce unexpected result.

//this is because the function inside setTimeout refers to the i only when it is 
//executed, by then i==4, the problem is that i is external variable

for (var i = 0; i < 4; i++) {
            setTimeout(function() {
                alert(i); //it is always 4
            }, i * 1000);
    }

//the solution is make the i as local variable, so parameter is a solution, and
//self-execution

var count = 0;
for (var i = 0; i < 4; i++) {
    (function(j) {
        setTimeout(function() {
            alert(j); //it will show 0, 1, 2, 3
        }, j * 1000);
    }) (i);
}

So sometimes it is good to remove the external dependencies. The follow code is anther example.

var ninja = {
    yell: function(n) {
        return n > 0 ? arguments.callee(n - 1) + "a" : "hiy";
    }
}

/*      
this is also ok
var ninja = { yell: function x(n) {
return n > 0 ? x(n - 1) + "a" : "hiy";
}
};
*/

/*this is has bug, because ninja.yell is inside of function which depends external state
var ninja = { yell: function(n) {
return n > 0 ? ninja.yell(n - 1) + "a" : "hiy";
}
};
*/

var sumurai = { yell: ninja.yell };
ninja = null;
ok(sumurai.yell(4) == "hiyaaaa", "argumnets.collee is the function itself");

efficency string operation

Because string is immutable in JavaScript, concatenation with array.join('') is much efficient. The following code use all the chinese characters. Click here to show

      var sb = [];
        sb[sb.length] = "

"; for (var i = 0x4e00; i <= 0x9fcf; i++) { sb[sb.length] = String.fromCharCode(i); } sb[sb.length] = "

"; $("#chinese").html(sb.join("")); return false;

the bind function

John Resig has page Learning Advanced JavaScript to explain how the following script works.

// The .bind method from Prototype.js 
Function.prototype.bind = function(){ 
  var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift(); 
  return function(){ 
    return fn.apply(object, 
      args.concat(Array.prototype.slice.call(arguments))); 
  }; 
};

His explanation is wonderful. And this piece of code is simple, powerful. But it maybe still hard for anyone to understand without any explanation. So I refactored it as follow, and add one use case.

Function.prototype.bind = function() {
    var function_to_be_bound = this;
    //convert argements into a real array
    var args = Array.prototype.slice.call(arguments); 
    //the first element in the array is the context_object to be bound to
    var context_object = args.shift();
    //the rest of elements in the array is the prefilled parameter
    var binding_parameters = args;

    return function() {
        var invoking_parameters = Array.prototype.slice.call(arguments);
        var combined_parameters = binding_parameters.concat(invoking_parameters);
        var result_from_function_run_in_new_context = function_to_be_bound.apply(context_object, combined_parameters);
        return result_from_function_run_in_new_context;
    };
}

function reply_greeting(your_name) {
    //"this" is the context object
    alert("my name is " + this.name + ", Nice to meet you, " + your_name);
}

var fred = { name: "fred" };

var reply_greeting_of_fred_to_you = reply_greeting.bind(fred); 
var reply_greeting_of_fred_to_john = reply_greeting.bind(fred, "john");

reply_greeting_of_fred_to_you("jeff"); //expect: "my name is fred, Nice to meet you, jeff"
reply_greeting_of_fred_to_john(); //expect: "my name is fred, Nice to meet you, john"

Another article may help you to understand is Functional Javascript

Oct 15, 2009

memoried function

 Function.prototype.memorized = function(key) {
     this._values = this._values || {};

     if (this._values[key] !== undefined) {
         return this._values[key]
     }
     else {
         //"this" is parent function object
         this._values[key] = this.apply(this, arguments); /* the "this" passed as context object is optional? */
         return this._values[key];
     }
 };


 function isPrime(num) {
     alert(this);
     var prime = num != 1;
     for (var i = 2; i < num; i++) {
         if (num % i == 0) {
             prime = false;
             break;
         }
     }
     return prime;
 }

 var a = isPrime.memorized(5);
 alert(a);
 var b = isPrime.memorized(5);
 alert(b);



curry function

Function.method('curry', function() {
    //arguments can not be passed in to closure function
    //var l = arguments.length;
    //var args = [];
    //for (var i = 0; i < l; i++) {
    //    args[i] = arguments[i];
    //}
    var args = Array.prototype.slice.apply(arguments);
    var original_function = this;

    return function() {
        //arguments is not the external arguments
        //for (var i = 0; i < arguments.length; i++) {
        //    args[args.length] = arguments[i];
        //}
        args = args.concat(Array.prototype.slice.call(arguments));
        return original_function.apply(null, args);
    };
});

function add() {
    var sum = 0;
    for (i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

var add1 = add.curry(1);
var s = add1(2);
alert(s);


Oct 13, 2009

Does it matter for an object to know its constructor?

We all knows that javascript inherit the functionality from its constructor. It is important to understand the concept every object has a secret link to constructor's prototype. How about constructor? Does a object know who is its constructor? Let's see a simple example

function Person() {
  alert(this.constructor.name); //Person
  this.constructor.count++; // reuse its constructor as a name space to prevent name polution
};

Person.count = 0;

var p = new Person();
alert(p.constructor == Person); //true
alert(Person.count); //1

We know that constructor is also an object, (an object is not necessarily a constructor), in our case we reuse the constructor as namespace or object. And the Id is this last count. Here Person's prototype is created by Person function automatically which is essentially an {}, an empty object, so the reflection shows that Person is the constructor of p. However, if we explicitly assign a prototype to Person function. Something strange happens

function Person() {
  alert(this.constructor.name); //Person
  this.constructor.count++; // reuse its constructor as a name space to prevent name polution
};

Person.count = 0;
Object.count = 0;
Person.prototype = {};

var p = new Person();
alert(p.constructor == Person); //false, it is Object
alert(Person.count); //0
alert(Object.count); //1

Suddenly the reflection shows, the object became constructor. Why? We need to have deeper understand how constructor works. When an object is created by using "new Person()", the Person function finds that it does has no explicit prototype, then it create one explicitly, so the refection shows that Person is the constructor. But if Person finds it has an explicit prototype, it use that prototype, and the constructor of that prototype in our case is Object, reflection mechanism report that constructor is the constructor of newly created object. According to ECMAScript Language Specification Edition 3

ECMAScript supports prototype-based inheritance. Every constructor has an associated prototype,and every object created by that constructor has an implicit reference to the prototype (called the object’s prototype) associated with its constructor.

If we don't reuse constructor as name space like Person, this is does not cause any problem. If we explicitly use Person as name space, it also solves the problem.

function Person() {
  alert(Person.name); //Person
  Person.count++;
};

However, using the syntax of p.constructor.xx give you more flexibility, we can solve the problem by just adding one line, it does not change other behaviors or add other functionality

Person.prototype.constructor = Person;