topical media & game development

talk show tell print

lib-present-learn-js-index.htm / htm



  <html>
  <head>
  <title>Learning Advanced JavaScript</title>
  <style>
  body { background: #222; color: #FFF; font-family: Helvetica, Arial; font-size: 16px; }
  textarea, #pre { font-size: 20px; width: 100%; height: 425px; font-family: Monaco, Courier New; background: #000; color: #FFF; overflow: auto; margin: 0; border: 0; padding: 0 0 0 5px; }
  #container, #pre { position: absolute; top: 30px; width: 100%; }
  textarea { z-index: 1; }
  #pre { z-index: 2; }
  #area { position: absolute; top: 30px; left: 1%; width: 98%; }
  h3 { font-size: 24px; }
  h3.large { position: absolute; top: 45%; left: 0; width: 100%; font-size: 48px; text-align: center; }
  div.buttons { position: absolute; top: 0px; right: 5px; }
  input.run { font-weight: bold; }
  a { color: #EEE; text-decoration: none; border-bottom: 1px solid #EEE; }
  .FAIL b, .ERROR b { color: red; /* #990066 */ }
  .PASS b { color: #73C836; }
  #results { line-height: 1.3em; font-size: 18px; }
  </style>
  <link rel="stylesheet" href="lib-present-learn-js-recipes.css"/>
  <script src="lib-present-learn-js-jquery.js"></script>
  <script src="lib-present-learn-js-jquery.chili.js"></script>
  <script src="lib-present-learn-js-recipes.js"></script>
  <script>
  jQuery(document).ready(function(){
          jQuery("dl").hide();
  
          jQuery("form").submit(function(){
                  jQuery("#results").empty();
  
                  try {
                          (new Function( jQuery("#code").val() ))();
                  } catch(e){
                          error(e.message);
                  }
  
                  for ( var item in window ) {
                          if ( !stasis[item] ) {
                                  window[item] = undefined;
                                  delete window[item];
                          }
                  }
                  return false;
          });
  
          jQuery("#code").keydown(function(e){
                  if ( this.setSelectionRange ) {
                          var start = this.selectionStart, val = this.value;
  
                          if ( e.keyCode == 13 ) {
                                  var match = val.substring(0, start).match(/(^|\n)([ \t]*)([^\n]*)/);
                                  if ( match ) {
                                          var spaces = match[2], length = spaces.length + 1;
                                          this.value = val.substring(0, start) + "\n" + spaces + val.substr(this.selectionEnd);
                                          this.setSelectionRange(start + length, start + length);
                                          this.focus();
                                          return false;
                                  }
                          } else if ( e.keyCode == 8 ) {
                                  if ( val.substring(start - 2, start) == "  " ) {
                                          this.value = val.substring(0, start - 2) + val.substr(this.selectionEnd);
                                          this.setSelectionRange(start - 2, start - 2);
                                          this.focus();
                                          return false;
                                  }
                          } else if ( e.keyCode == 9 ) {
                                  this.value = val.substring(0, start) + "  " + val.substr(this.selectionEnd);
                                  this.setSelectionRange(start + 2, start + 2);
                                  this.focus();
                                  return false;
                          }
                  }
          });
  
          jQuery("#pre").dblclick(function(){
                  jQuery("#pre").hide();
                  jQuery("#code").focus();
          });
  
          jQuery("#prev").click(function(){
                  pos--;
                  loadSample();
          });
  
          jQuery("#next").click(function(){
                  pos++;
                  loadSample();
          });
  
          var stasis = {};
          for ( var item in window ) {
                  stasis[item] = true;
          }
  
          var pos;
  
          if ( location.hash ) {
                  pos = parseInt(location.hash.substr(1)) - 1;
                  loadSample();
          } else {
                  showTOC();
          }
          
          function showTOC(){
                  jQuery("#pre").empty();
                  jQuery("h3").removeClass("large").html("Learning Advanced JavaScript");
                  jQuery("#pre, #code").height(425).show();
                  
                  jQuery("dd:empty").prev("dt").each(function(i){
                          var dt = jQuery("dt").index(this);
                          jQuery("<a href='#" + (dt+1) + "'>" + (i+1) + ") " + this.innerHTML + "\n</a>").click(function(){
                                  pos = dt;
                                  loadSample();
                                  return false;
                          }).appendTo("#pre");
                  });
                  
                  jQuery("div.buttons").hide();
          }
  
          function loadSample(){
                  jQuery("div.buttons").show();
                  
                  var code = jQuery("#code");
  
                  var source = (jQuery("dd").eq(pos).find("pre").html() || "")
                                          .replace(/(^|\n) /g, "$1").replace(/ (|\n)/g, "$1");
  
                  if ( !source ) {
                          jQuery("h3").addClass("large");
                          jQuery("#pre, #code, #run").hide();
                  } else {
                          jQuery("h3").removeClass("large");
                          jQuery("#pre, #code").show();
                          
                          if ( source.match(/assert\(|log\(|error\(/) )
                                  jQuery("#run").show();
                          else
                                  jQuery("#run").hide();
                  }
  
                  jQuery("h3").html( (source ? "#" + (pos + 1) + ": " : "") + jQuery("dt").eq(pos).html() );
                  code.val( source.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">") );
                  jQuery("#pre").html( source ).chili();
                  jQuery("#results").empty();
  
                  code.add("#pre").height(275)[0];
  
                  if ( code[0].scrollHeight > 275 )
                          code.add("#pre").height( code[0].scrollHeight + 5 );
  
                  var last = jQuery("dt").length - 1;
  
                  if ( pos == 0 )
                          jQuery("#prev").css("visibility", "hidden");
                  if ( pos > 0 )
                          jQuery("#prev").css("visibility", "visible");
  
                  if ( pos == last )
                          jQuery("#next").css("visibility", "hidden");
                  if ( pos < last )
                          jQuery("#next").css("visibility", "visible");
  
                  window.location.hash = pos + 1;
          }
          
          setInterval(function(){
                  if ( location.hash != ("#" + (pos + 1)) ) {
                          var num = parseInt(location.hash.substr(1)) - 1;
                          if ( isNaN(num) ) {
                                  if ( jQuery("div.buttons").is(":visible") ) {
                                          showTOC();
                                  }
                          } else {
                                  pos = num;
                                  loadSample();
                          }
                  }
          }, 100);
  });
  </script>
  <script>
  function assert(pass, msg){
    var type = pass ? "PASS" : "FAIL";
    jQuery("#results").append("<li class='" + type + "'><b>" + type + "</b> " + msg + "</li>");
  }
  function error(msg){
    jQuery("#results").append("<li class='ERROR'><b>ERROR</b> " + msg + "</li>");
  }
  function log(){
    var msg = "";
    for ( var i = 0; i < arguments.length; i++ ) {
      msg += " " + arguments[i];
    }
    jQuery("#results").append("<li class='LOG'><b>LOG</b> " + msg + "</li>");
  }
  </script>
  </head>
  <body>
  <h3>Learning Advanced JavaScript</h3>
  <div id="area">
  <pre id="pre" class="javascript"></pre>
  <form id="form">
  <div class="buttons">
  <input type="submit" value="Run" class="run" id="run"/>
  <input type="button" id="prev" value="&laquo; Prev"/>
  <input type="button" id="next" value="Next &raquo;"/>
  </div>
  <div id="container">
          <textarea id="code" wrap="off"></textarea>
          <ol id="results"></ol>
  </div>
  </form>
  </div>
  <dl>
  <dt>Our Goal</dt>
  <dd></dd>
  <dt>Goal: To be able to understand this function:</dt>
  <dd><pre>// 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))); 
     }; 
   }; </pre></dd>
  <dt>Some helper methods that we have:</dt>
  <dd><pre>assert( true, "I'll pass." );
  assert( "truey", "So will I." );
  assert( false, "I'll fail." );
  assert( null, "So will I." );
  log( "Just a simple log", "of", "values.", true );
  error( "I'm an error!" );</pre></dd>
  <dt>Defining Functions</dt>
  <dd></dd>
  <dt>What ways can we define functions?</dt>
  <dd><pre> function isNimble(){ return true; } 
   var canFly = function(){ return true; }; 
   window.isDeadly = function(){ return true; }; 
   log(isNimble, canFly, isDeadly); </pre></dd>
  <dt>Does the order of function definition matter?</dt>
  <dd><pre> var canFly = function(){ return true; }; 
   window.isDeadly = function(){ return true; }; 
   assert( isNimble() &amp;&amp; canFly() &amp;&amp; isDeadly(), "Still works, even though isNimble is moved." ); 
   function isNimble(){ return true; } </pre></dd>
  <dt>Where can assignments be accessed?</dt>
  <dd><pre> assert( typeof canFly == "undefined", "canFly doesn't get that benefit." ); 
   assert( typeof isDeadly == "undefined", "Nor does isDeadly." ); 
   var canFly = function(){ return true; }; 
   window.isDeadly = function(){ return true; }; </pre></dd>
  <dt>Can functions be defined below return statements?</dt>
  <dd><pre> function stealthCheck(){ 
     assert( stealth(), "We'll never get below the return, but that's OK!" ); 
  
     return stealth();
  
     function stealth(){ return true; } 
   } 
  
   stealthCheck(); </pre></dd>
  <dt>Named Functions</dt>
  <dd></dd>
  <dt>We can refer to a function, within itself, by its name.</dt>
  <dd><pre> function yell(n){ 
     return n &gt; 0 ? yell(n-1) + "a" : "hiy"; 
   } 
   assert( yell(4) == "hiyaaaa", "Calling the function by itself comes naturally." ); </pre></dd>
  <dt>What is the name of a function?</dt>
  <dd><pre> var ninja = function myNinja(){ 
     assert( ninja == myNinja, "This function is named two things - at once!" ); 
   }; 
   ninja(); 
   assert( typeof myNinja == "undefined", "But myNinja isn't defined outside of the function." ); 
   log( ninja );</pre></dd>
  <dt>We can even do it if we're an anonymous function that's an object property.
  <dd><pre> var ninja = { 
     yell: function(n){ 
       return n &gt; 0 ? ninja.yell(n-1) + "a" : "hiy"; 
     } 
   }; 
   assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." ); </pre></dd>
  <dt>But what happens when we remove the original object?</dt>
  <dd><pre> var ninja = { 
     yell: function(n){ 
       return n &gt; 0 ? ninja.yell(n-1) + "a" : "hiy"; 
     } 
   }; 
   assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." ); 
  
   var samurai = { yell: ninja.yell }; 
   var ninja = null; 
    
   try { 
     samurai.yell(4); 
   } catch(e){ 
     assert( false, "Uh, this isn't good! Where'd ninja.yell go?" ); 
   } </pre></dd>
  <dt>Let's give the anonymous function a name!</dt>
  <dd><pre> var ninja = { 
     yell: function yell(n){ 
       return n &gt; 0 ? yell(n-1) + "a" : "hiy"; 
     } 
   }; 
   assert( ninja.yell(4) == "hiyaaaa", "Works as we would expect it to!" ); 
    
   var samurai = { yell: ninja.yell }; 
   var ninja = {}; 
   assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." ); </pre></dd>
  <dt>What if we don't want to give the function a name?</dt>
  <dd><pre> var ninja = { 
     yell: function(n){ 
       return n &gt; 0 ? arguments.callee(n-1) + "a" : "hiy"; 
     } 
   }; 
   assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." ); </pre></dd>
  <dt>Functions as Objects</dt>
  <dd></dd>
  <dt>How similar are functions and objects?</dt>
  <dd><pre> var obj = {}; 
   var fn = function(){}; 
   assert( obj &amp;&amp; fn, "Both the object and function exist." ); </pre></dd>
  <dt>How similar are functions and objects?</dt>
  <dd><pre> var obj = {}; 
   var fn = function(){}; 
   obj.prop = "some value"; 
   fn.prop = "some value"; 
   assert( obj.prop == fn.prop, "Both are objects, both have the property." ); </pre></dd>
  <dt>Is it possible to cache the return results from a function?</dt>
  <dd><pre> function getElements( name ) {
     var results;
  
     if ( getElements.cache[name] ) {
       results = getElements.cache[name];
     } else {
       results = document.getElementsByTagName(name);
       getElements.cache[name] = results;
     }
  
     return results;
   } 
   getElements.cache = {};
  
   log( "Elements found: ", getElements("pre").length ); </pre></dd>
  <dt>QUIZ: Can you cache the results of this function?</dt>
  <dd><pre> function isPrime( num ) { 
     var prime = num != 1; // Everything but 1 can be prime 
     for ( var i = 2; i &lt; num; i++ ) { 
       if ( num % i == 0 ) { 
         prime = false; 
         break; 
       } 
     } 
     return prime; 
   } 
    
   assert( isPrime(5), "Make sure the function works, 5 is prime." ); 
   assert( isPrime.cache[5], "Is the answer cached?" );</pre></dd>
  <dt>One possible way to cache the results:</dt>
  <dd><pre> function isPrime( num ) { 
     if ( isPrime.cache[ num ] != null ) 
       return isPrime.cache[ num ]; 
      
     var prime = num != 1; // Everything but 1 can be prime 
     for ( var i = 2; i &lt; num; i++ ) { 
       if ( num % i == 0 ) { 
         prime = false; 
         break; 
       } 
     }
     
     isPrime.cache[ num ] = prime
     
     return prime; 
   } 
   
   isPrime.cache = {}; 
    
   assert( isPrime(5), "Make sure the function works, 5 is prime." ); 
   assert( isPrime.cache[5], "Make sure the answer is cached." ); </pre></dd>
  <dt>Context</dt>
  <dd></dd>
  <dt>What happens if a function is an object property?
  <dd><pre> var katana = { 
     isSharp: true, 
     use: function(){ 
       this.isSharp = !this.isSharp; 
     } 
   }; 
   katana.use() 
   assert( !katana.isSharp, "Verify the value of isSharp has been changed." );</pre></dd>
  <dt>What exactly does context represent?</dt>
  <dd><pre> function katana(){ 
     this.isSharp = true; 
   } 
   katana(); 
   assert( isSharp === true, "A global object now exists with that name and value." ); 
    
   var shuriken = { 
     toss: function(){ 
       this.isSharp = true; 
     } 
   }; 
   shuriken.toss(); 
   assert( shuriken.isSharp === true, "When it's an object property, the value is set within the object." ); </pre></dd>
  <dt>How can we change the context of a function?</dt>
  <dd><pre> var object = {}; 
   function fn(){ 
     return this; 
   } 
   assert( fn() == this, "The context is the global object." ); 
   assert( fn.call(object) == object, "The context is changed to a specific object." ); </pre></dd>
  <dt>Different ways of changing the context:</dt>
  <dd><pre> function add(a, b){ 
     return a + b; 
   } 
   assert( add.call(this, 1, 2) == 3, ".call() takes individual arguments" ); 
   assert( add.apply(this, [1, 2]) == 3, ".apply() takes an array of arguments" ); </pre></dd>
  <dt>QUIZ: How can we implement looping with a callback?</dt>
  <dd><pre> function loop(array, fn){ 
     for ( var i = 0; i &lt; array.length; i++ ) {
       // Implement me!
     }
   } 
   var num = 0; 
   loop([0, 1, 2], function(value, i){ 
     assert(value == num++, "Make sure the contents are as we expect it."); 
     assert(this instanceof Array, "The context should be the full array.");
   }); </pre></dd>
  <dt>A possible solution for function looping:</dt>
  <dd><pre> function loop(array, fn){ 
     for ( var i = 0; i &lt; array.length; i++ ) 
       fn.call( array, array[i], i );
   } 
   var num = 0; 
   loop([0, 1, 2], function(value, i){ 
     assert(value == num++, "Make sure the contents are as we expect it."); 
     assert(this instanceof Array, "The context should be the full array.");
   }); </pre></dd>
  <dt>Instantiation</dt>
  <dd></dd>
  <dt>What does the new operator do?</dt>
  <dd><pre> function Ninja(){
     this.name = "Ninja";
   } 
    
   var ninjaA = Ninja(); 
   assert( !ninjaA, "Is undefined, not an instance of Ninja." ); 
    
   var ninjaB = new Ninja(); 
   assert( ninjaB.name == "Ninja", "Property exists on the ninja instance." ); </pre></dd>
  <dt>We have a 'this' context that is a Ninja object.</dt>
  <dd><pre> function Ninja(){ 
     this.swung = false; 
      
     // Should return true 
     this.swingSword = function(){ 
       this.swung = !this.swung; 
       return this.swung;
     }; 
   } 
    
   var ninja = new Ninja(); 
   assert( ninja.swingSword(), "Calling the instance method." ); 
   assert( ninja.swung, "The ninja has swung the sword." );
   
   var ninjaB = new Ninja();
   assert( !ninjaB.swung, "Make sure that the ninja has not swung his sword." );</pre></dd>
  <dt>QUIZ: Add a method that gives a name to the ninja.</dt>
  <dd><pre> function Ninja(name){
     // Implement!
  }
  
  var ninja = new Ninja("John");
  assert( ninja.name == "John", "The name has been set on initialization" );
  
  ninja.changeName("Bob");
  assert( ninja.name == "Bob", "The name was successfully changed." );</pre></dd>
  <dt>Add a new property and method to the object.</dt>
  <dd><pre> function Ninja(name){
     this.changeName = function(name){
       this.name = name;
     };
  
     this.changeName( name );
  }
  
  var ninja = new Ninja("John");
  assert( ninja.name == "John", "The name has been set on initialization" );
  
  ninja.changeName("Bob");
  assert( ninja.name == "Bob", "The name was successfully changed." );</pre></dd>
  <dt>What happens when we forget to use the new operator?</dt>
  <dd><pre>function User(first, last){ 
     this.name = first + " " + last; 
   } 
    
   var user = User("John", "Resig"); 
   assert( typeof user == "undefined", "Since new wasn't used, the instance is undefined." ); </pre></dd>
  <dt>What happens when we forget to use the new operator? (cont.)</dt>
  <dd><pre> function User(first, last){ 
     this.name = first + " " + last; 
   } 
    
   window.name = "Resig"; 
   var user = User("John", name); 
    
   assert( name == "John Resig", "The name variable is accidentally overridden." ); </pre></dd>
  <dt>We need to make sure that the new operator is always used.</dt>
  <dd><pre> function User(first, last){ 
     if ( !(this instanceof User) ) 
       return new User(first, last); 
      
     this.name = first + " " + last; 
   } 
    
   var name = "Resig"; 
   var user = User("John", name); 
    
   assert( user, "This was defined correctly, even if it was by mistake." ); 
   assert( name == "Resig", "The right name was maintained." ); </pre></dd>
  <dt>QUIZ: Is there another, more generic, way of doing this?</dt>
  <dd><pre> function User(first, last){ 
     if ( !(this instanceof ___) ) 
       return new User(first, last); 
      
     this.name = first + " " + last; 
   } 
    
   var name = "Resig"; 
   var user = User("John", name); 
    
   assert( user, "This was defined correctly, even if it was by mistake." ); 
   assert( name == "Resig", "The right name was maintained." ); </pre></dd>
  <dt>A solution using arguments.callee.</dt>
  <dd><pre> function User(first, last){ 
     if ( !(this instanceof arguments.callee) ) 
       return new User(first, last); 
      
     this.name = first + " " + last; 
   } 
    
   var name = "Resig"; 
   var user = User("John", name); 
    
   assert( user, "This was defined correctly, even if it was by mistake." ); 
   assert( name == "Resig", "The right name was maintained." ); </pre></dd>
  <dt>Flexible Arguments</dt>
  <dd></dd>
  <dt>Using a variable number of arguments to our advantage.</dt>
  <dd><pre> function merge(root){ 
     for ( var i = 0; i &lt; arguments.length; i++ ) 
       for ( var key in arguments[i] ) 
         root[key] = arguments[i][key]; 
     return root; 
   } 
    
   var merged = merge({name: "John"}, {city: "Boston"}); 
   assert( merged.name == "John", "The original name is intact." ); 
   assert( merged.city == "Boston", "And the city has been copied over." ); </pre></dd>
  <dt>How can we find the Min/Max number in an array?</dt>
  <dd><pre> function smallest(array){ 
     return Math.min.apply( Math, array ); 
   } 
   function largest(array){ 
     return Math.max.apply( Math, array ); 
   } 
   assert(smallest([0, 1, 2, 3]) == 0, "Locate the smallest value."); 
   assert(largest([0, 1, 2, 3]) == 3, "Locate the largest value."); </pre></dd>
  <dt>Another possible solution:</dt>
  <dd><pre> function smallest(){ 
     return Math.min.apply( Math, arguments ); 
   } 
   function largest(){ 
     return Math.max.apply( Math, arguments ); 
   } 
   assert(smallest(0, 1, 2, 3) == 0, "Locate the smallest value."); 
   assert(largest(0, 1, 2, 3) == 3, "Locate the largest value."); </pre></dd>
  <dt>Uh oh, what's going wrong here?</dt>
  <dd><pre> function highest(base){ 
     return arguments.sort(function(a,b){
       return a - b;
     }).slice(0, base);
   } 
   assert(highest(1, 1, 2, 3).length == 1, "Get only the highest value."); 
   assert(highest(3, 1, 2, 3, 4, 5)[2] == 3, "Verify the results."); </pre></dd>
  <dt>QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help?</dt>
  <dd><pre> // Hint: Arrays have .slice and .splice methods which return new arrays.
  function highest(base){ 
     return makeArray(arguments).sort(function(a,b){
       return a - b;
     }).slice(0, base);
   } 
  
   function makeArray(array){
     // Implement me!
   }
  
   // Expecting: [3]
   assert(highest(1, 1, 2, 3).length == 1, "Get only the highest value."); 
   // Expecting: [5,4,3]
   assert(highest(3, 1, 2, 3, 4, 5)[2] == 3, "Verify the results."); </pre></dd>
  <dt>We can use built-in methods to our advantage.</dt>
  <dd><pre> function highest(base){ 
     return makeArray(arguments).sort(function(a,b){
       return a - b;
     }).slice(0, base);
   } 
  
   function makeArray(array){
     return Array().slice.call( array );
   }
  
   assert(highest(1, 1, 2, 3).length == 1, "Get only the highest value."); 
   assert(highest(3, 1, 2, 3, 4, 5)[2] == 3, "Verify the results."); </pre></dd>
  <dt>QUIZ: Implement a multiplication function (first argument by largest number).</dt>
  <dd><pre> function multiMax(multi){ 
     // Make an array of all but the first argument
     var allButFirst = ___;
  
     // Find the largest number in that array of arguments
     var largestAllButFirst = ___;
  
     // Return the multiplied result
     return multi * largestAllButFirst;
   } 
   assert( multiMax(3, 1, 2, 3) == 9, "3*3=9 (First arg, by largest.)" ); </pre></dd>
  <dt>We can use call and apply to build a solution.</dt>
  <dd><pre> function multiMax(multi){ 
     // Make an array of all but the first argument
     var allButFirst = Array().slice.call( arguments, 1 );
  
     // Find the largest number in that array of arguments
     var largestAllButFirst = Math.max.apply( Math, allButFirst );
  
     // Return the multiplied result
     return multi * largestAllButFirst;
   } 
   assert( multiMax(3, 1, 2, 3) == 9, "3*3=9 (First arg, by largest.)" ); </pre></dd>
  <dt>Closures</dt>
  <dd></dd>
  <dt>A basic closure.</dt>
  <dd><pre> var num = 10;
  
   function addNum(myNum){
     return num + myNum;
   }
  
   assert( addNum(5) == 15, "Add two numbers together, one from a closure." );</pre></dd>
  <dt>But why doesn't this work?</dt>
  <dd><pre> var num = 10;
  
   function addNum(myNum){
     return num + myNum;
   }
   
   num = 15;
  
   assert( addNum(5) == 15, "Add two numbers together, one from a closure." );</pre></dd>
  <dt>Closures are frequently used for callbacks.</dt>
  <dd><pre> var results = jQuery("#results").html("&lt;li&gt;Loading...&lt;/li&gt;"); 
  
   jQuery.get("test.html", function(html){ 
     results.html( html ); 
     assert( results, "The element to append to, via a closure." ); 
   }); </pre></dd>
  <dt>They're also useful for timers.</dt>
  <dd><pre> var count = 0; 
    
   var timer = setInterval(function(){ 
     if ( count &lt; 5 ) { 
       log( "Timer call: ", count );
       count++; 
     } else { 
       assert( count == 5, "Count came via a closure, accessed each step." ); 
       assert( timer, "The timer reference is also via a closure." ); 
       clearInterval( timer ); 
     } 
   }, 100); </pre></dd>
  <dt>and they're also frequently used when attaching event listeners.</dt>
  <dd><pre> var count = 1;
   var elem = document.createElement("li");
   elem.innerHTML = "Click me!";
   elem.onclick = function(){
     log( "Click #", count++ );
   };
   document.getElementById("results").appendChild( elem );
   assert( elem.parentNode, "Clickable element appended." );</pre></dd>
  <dt>Private properties, using closures.</dt>
  <dd><pre> function Ninja(){ 
     var slices = 0; 
      
     this.getSlices = function(){ 
       return slices; 
     }; 
     this.slice = function(){ 
       slices++; 
     }; 
   } 
    
   var ninja = new Ninja(); 
   ninja.slice(); 
   assert( ninja.getSlices() == 1, "We're able to access the internal slice data." ); 
   assert( ninja.slices === undefined, "And the private data is inaccessible to us." ); </pre></dd>
  <dt>QUIZ: What are the values of the variables?</dt>
  <dd><pre>var a = 5;
  function runMe(a){
    assert( a == ___, "Check the value of a." );
  
    function innerRun(){
      assert( b == ___, "Check the value of b." );
      assert( c == ___, "Check the value of c." );
    }
  
    var b = 7;
    innerRun();
    var c = 8;
  }
  runMe(6);
  
  for ( var d = 0; d &lt; 3; d++ ) {
    setTimeout(function(){
      assert( d == ___, "Check the value of d." );
    }, 100);
  }</pre></dd>
  <dt>The last one is quite tricky, we'll revisit it.</dt>
  <dd><pre>var a = 5;
  function runMe(a){
    assert( a == 6, "Check the value of a." );
  
    function innerRun(){
      assert( b == 7, "Check the value of b." );
      assert( c == undefined, "Check the value of c." );
    }
  
    var b = 7;
    innerRun();
    var c = 8;
  }
  runMe(6);
  
  for ( var d = 0; d &lt; 3; d++ ) {
    setTimeout(function(){
      assert( d == 3, "Check the value of d." );
    }, 100);
  }</pre></dd>
  <dt>Temporary Scope</dt>
  <dd></dd>
  <dt>Self-executing, temporary, function</dt>
  <dd><pre> (function(){
     var count = 0; 
   
     var timer = setInterval(function(){ 
       if ( count &lt; 5 ) { 
         log( "Timer call: ", count ); 
         count++; 
       } else { 
         assert( count == 5, "Count came via a closure, accessed each step." ); 
         assert( timer, "The timer reference is also via a closure." ); 
         clearInterval( timer ); 
       } 
     }, 100);
  })();
  
  assert( typeof count == "undefined", "count doesn't exist outside the wrapper" );
  assert( typeof timer == "undefined", "neither does timer" );</pre></dd>
  <dt>Now we can handle closures and looping.</dt>
  <dd><pre>for ( var d = 0; d &lt; 3; d++ ) (function(d){
    setTimeout(function(){
      log( "Value of d: ", d );
      assert( d == d, "Check the value of d." );
    }, d * 200);
  })(d);</pre></dd>
  <dt>The anonymous wrapper functions are also useful for wrapping libraries.</dt>
  <dd><pre> (function(){ 
     var myLib = window.myLib = function(){ 
       // Initialize 
     }; 
    
     // ... 
   })(); </pre></dd>
  <dt>Another way to wrap a library:</dt>
  <dd><pre> var myLib = (function(){ 
     function myLib(){ 
       // Initialize 
     } 
    
     // ... 
      
     return myLib; 
   })(); </pre></dd>
  <dt>QUIZ: Fix the broken closures in this loop!</dt>
  <dd><pre> var count = 0;
   for ( var i = 0; i &lt; 4; i++ ) {
     setTimeout(function(){
       assert( i == count++, "Check the value of i." );
     }, i * 200);
   }</pre></dd>
  <dt>A quick wrapper function will do the trick.</dt>
  <dd><pre> var count = 0;
   for ( var i = 0; i &lt; 4; i++ ) (function(i){
     setTimeout(function(){
       assert( i == count++, "Check the value of i." );
     }, i * 200);
   })(i);</pre></dd>
  <dt>Function Prototypes</dt>
  <dd></dd>
  <dt>Adding a prototyped method to a function.</dt>
  <dd><pre> function Ninja(){} 
    
   Ninja.prototype.swingSword = function(){ 
     return true; 
   }; 
    
   var ninjaA = Ninja(); 
   assert( !ninjaA, "Is undefined, not an instance of Ninja." ); 
    
   var ninjaB = new Ninja(); 
   assert( ninjaB.swingSword(), "Method exists and is callable." ); </pre></dd>
  <dt>Properties added in the constructor (or later) overwrite prototyped properties.</dt>
  <dd><pre> function Ninja(){ 
     this.swingSword = function(){ 
       return true; 
     }; 
   } 
    
   // Should return false, but will be overridden 
   Ninja.prototype.swingSword = function(){ 
     return false; 
   }; 
    
   var ninja = new Ninja(); 
   assert( ninja.swingSword(), "Calling the instance method, not the prototype method." );</pre></dd>
  <dt>Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist.</dt>
  <dd><pre> function Ninja(){ 
     this.swung = true; 
   } 
    
   var ninjaA = new Ninja(); 
   var ninjaB = new Ninja(); 
    
   Ninja.prototype.swingSword = function(){ 
     return this.swung; 
   }; 
    
   assert( ninjaA.swingSword(), "Method exists, even out of order." );
   assert( ninjaB.swingSword(), "and on all instantiated objects." ); </pre></dd>
  <dt>QUIZ: Make a chainable Ninja method.</dt>
  <dd><pre> function Ninja(){ 
     this.swung = true; 
   } 
    
   var ninjaA = new Ninja(); 
   var ninjaB = new Ninja(); 
    
   // Add a method to the Ninja prototype which
   // returns itself and modifies swung
    
   assert( !ninjaA.swing().swung, "Verify that the swing method exists and returns an instance." );
   assert( !ninjaB.swing().swung, "and that it works on all Ninja instances." ); </pre></dd>
  <dt>The chainable method must return this.</dt>
  <dd><pre> function Ninja(){ 
     this.swung = true; 
   } 
    
   var ninjaA = new Ninja(); 
   var ninjaB = new Ninja(); 
   
   Ninja.prototype.swing = function(){
     this.swung = false;
     return this;
   }; 
    
   assert( !ninjaA.swing().swung, "Verify that the swing method exists and returns an instance." );
   assert( !ninjaB.swing().swung, "and that it works on all Ninja instances." ); </pre></dd>
  <dt>Instance Type</dt>
  <dd></dd>
  <dt>Examining the basics of an object.</dt>
  <dd><pre> function Ninja(){} 
    
   var ninja = new Ninja(); 
  
   assert( typeof ninja == "object", "However the type of the instance is still an object." );   
   assert( ninja instanceof Ninja, "The object was instantiated properly." ); 
   assert( ninja.constructor == Ninja, "The ninja object was created by the Ninja function." ); </pre></dd>
  <dt>We can still use the constructor to build other instances.</dt>
  <dd><pre> function Ninja(){}
   var ninja = new Ninja(); 
   var ninjaB = new ninja.constructor(); 
    
   assert( ninjaB instanceof Ninja, "Still a ninja object." ); </pre></dd>
  <dt>QUIZ: Make another instance of a Ninja.</dt>
  <dd><pre>var ninja = (function(){
    function Ninja(){}
    return new Ninja();
  })();
  
  // Make another instance of Ninja
  var ninjaB = ___;
  
  assert( ninja.constructor == ninjaB.constructor, "The ninjas come from the same source." );</pre></dd>
  <dt>QUIZ: Use the .constructor property to dig in.</dt>
  <dd><pre>var ninja = (function(){
    function Ninja(){}
    return new Ninja();
  })();
  
  // Make another instance of Ninja
  var ninjaB = new ninja.constructor();
  
  assert( ninja.constructor == ninjaB.constructor, "The ninjas come from the same source." );</pre></dd>
  <dt>Inheritance</dt>
  <dd></dd>
  <dt>The basics of how prototypal inheritance works.</dt>
  <dd><pre> function Person(){} 
   Person.prototype.dance = function(){}; 
    
   function Ninja(){} 
    
   // Achieve similar, but non-inheritable, results 
   Ninja.prototype = Person.prototype; 
   Ninja.prototype = { dance: Person.prototype.dance }; 
  
   assert( (new Ninja()) instanceof Person, "Will fail with bad prototype chain." );
    
   // Only this maintains the prototype chain 
   Ninja.prototype = new Person(); 
    
   var ninja = new Ninja(); 
   assert( ninja instanceof Ninja, "ninja receives functionality from the Ninja prototype" ); 
   assert( ninja instanceof Person, "... and the Person prototype" ); 
   assert( ninja instanceof Object, "... and the Object prototype" ); </pre></dd>
  <dt>QUIZ: Let's try our hand at inheritance.</dt>
  <dd><pre> function Person(){}
   Person.prototype.getName = function(){
     return this.name;
   };
  
   // Implement a function that inherits from Person
   // and sets a name in the constructor
  
   var me = new Me();
   assert( me.getName(), "A name was set." );</pre></dd>
  <dt>The result is rather straight-forward.</dt>
  <dd><pre> function Person(){}
   Person.prototype.getName = function(){
     return this.name;
   };
  
   function Me(){
     this.name = "John Resig";
   }
   Me.prototype = new Person();
  
   var me = new Me();
   assert( me.getName(), "A name was set." );</pre></dd>
  <dt>Built-in Prototypes</dt>
  <dd></dd>
  <dt>We can also modify built-in object prototypes.
  <dd><pre> if (!Array.prototype.forEach) { 
     Array.prototype.forEach = function(fn){ 
       for ( var i = 0; i &lt; this.length; i++ ) { 
         fn( this[i], i, this ); 
       } 
     }; 
   } 
    
   ["a", "b", "c"].forEach(function(value, index, array){ 
     assert( value, "Is in position " + index + " out of " + (array.length - 1) ); 
   }); </pre></dd>
  <dt>Beware: Extending prototypes can be dangerous.</dt>
  <dd><pre> Object.prototype.keys = function(){ 
     var keys = []; 
     for ( var i in this ) 
       keys.push( i ); 
     return keys; 
   }; 
    
   var obj = { a: 1, b: 2, c: 3 }; 
  
   assert( obj.keys().length == 3, "We should only have 3 properties." );
  
   delete Object.prototype.keys;</pre></dd>
  <dt>Enforcing Function Context</dt>
  <dd></dd>
  <dt>What happens when we try to bind an object's method to a click handler?</dt>
  <dd><pre> var Button = { 
     click: function(){ 
       this.clicked = true; 
     } 
   }; 
    
   var elem = document.createElement("li");
   elem.innerHTML = "Click me!";
   elem.onclick = Button.click;
   document.getElementById("results").appendChild(elem);
  
   elem.onclick();
   assert( elem.clicked, "The clicked property was accidentally set on the element" ); </pre></dd>
  <dt>We need to keep its context as the original object.</dt>
  <dd><pre> function bind(context, name){ 
     return function(){ 
       return context[name].apply(context, arguments); 
     }; 
   } 
  
   var Button = { 
     click: function(){ 
       this.clicked = true; 
     } 
   }; 
    
   var elem = document.createElement("li");
   elem.innerHTML = "Click me!";
   elem.onclick = bind(Button, "click");
   document.getElementById("results").appendChild(elem);
  
   elem.onclick();
   assert( Button.clicked, "The clicked property was correctly set on the object" ); </pre></dd>
  <dt>Add a method to all functions to allow context enforcement.</dt>
  <dd><pre> Function.prototype.bind = function(object){ 
     var fn = this;
     return function(){ 
       return fn.apply(object, arguments);
     }; 
   }; 
  
   var Button = { 
     click: function(){ 
       this.clicked = true; 
     } 
   }; 
    
   var elem = document.createElement("li");
   elem.innerHTML = "Click me!";
   elem.onclick = Button.click.bind(Button);
   document.getElementById("results").appendChild(elem);
  
   elem.onclick();
   assert( Button.clicked, "The clicked property was correctly set on the object" ); </pre></dd>
  <dt>Our final target (the .bind method from Prototype.js).</dt>
  <dd><pre> 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))); 
     }; 
   }; 
  
   var Button = { 
     click: function(value){ 
       this.clicked = value; 
     } 
   }; 
    
   var elem = document.createElement("li");
   elem.innerHTML = "Click me!";
   elem.onclick = Button.click.bind(Button, false);
   document.getElementById("results").appendChild(elem);
  
   elem.onclick();
   assert( Button.clicked === false, "The clicked property was correctly set on the object" ); </pre></dd>
  <dt>Bonus: Function Length</dt>
  <dd></dd>
  <dt>How does a function's length property work?</dt>
  <dd><pre> function makeNinja(name){} 
   function makeSamurai(name, rank){} 
   assert( makeNinja.length == 1, "Only expecting a single argument" ); 
   assert( makeSamurai.length == 2, "Multiple arguments expected" ); </pre></dd>
  <dt>We can use it to implement method overloading.</dt>
  <dd><pre> 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 ); 
     }; 
   } </pre></dd>
  <dt>How method overloading might work, using the function length property.</dt>
  <dd><pre> 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 &lt; 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 &lt; 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" ); </pre></dd>
  </dl>
  </body>
  </html>
  


(C) Æliens 20/08/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.