--- created_at: '2016-12-11T04:47:14.000Z' title: Learning Advanced JavaScript (2008) url: http://ejohn.org/apps/learn/ author: kparaju points: 192 story_text: comment_text: num_comments: 55 story_id: story_title: story_url: parent_id: created_at_i: 1481431634 _tags: - story - author_kparaju - story_13149635 objectID: '13149635' --- [Source](https://johnresig.com/apps/learn/ "Permalink to Learning Advanced JavaScript") # Learning Advanced JavaScript ### Learning Advanced JavaScript Double-click the code to edit the tutorial and try your own code. This tutorial contains code and discussion from the upcoming book [Secrets of the JavaScript Ninja][1] by [John Resig][2]. Our Goal : Goal: To be able to understand this function: : // 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))); }; }; Some helper methods that we have: : 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!" ); Defining Functions : What ways can we define functions? : function isNimble(){ return true; } var canFly = function(){ return true; }; window.isDeadly = function(){ return true; }; log(isNimble, canFly, isDeadly); Does the order of function definition matter? : var canFly = function(){ return true; }; window.isDeadly = function(){ return true; }; assert( isNimble() && canFly() && isDeadly(), "Still works, even though isNimble is moved." ); function isNimble(){ return true; } Where can assignments be accessed? : 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; }; Can functions be defined below return statements? : function stealthCheck(){ assert( stealth(), "We'll never get below the return, but that's OK!" ); return stealth(); function stealth(){ return true; } } stealthCheck(); Named Functions : We can refer to a function, within itself, by its name. : function yell(n){ return n > 0 ? yell(n-1) + "a" : "hiy"; } assert( yell(4) == "hiyaaaa", "Calling the function by itself comes naturally." ); What is the name of a function? : 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 ); We can even do it if we're an anonymous function that's an object property. : var ninja = { yell: function(n){ return n > 0 ? ninja.yell(n-1) + "a" : "hiy"; } }; assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." ); But what happens when we remove the original object? : var ninja = { yell: function(n){ return n > 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?" ); } Let's give the anonymous function a name! : var ninja = { yell: function yell(n){ return n > 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." ); What if we don't want to give the function a name? : var ninja = { yell: function(n){ return n > 0 ? arguments.callee(n-1) + "a" : "hiy"; } }; assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." ); Functions as Objects : How similar are functions and objects? : var obj = {}; var fn = function(){}; assert( obj && fn, "Both the object and function exist." ); How similar are functions and objects? : 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." ); Is it possible to cache the return results from a function? : 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 ); log( "Cache found: ", getElements.cache.pre.length ); QUIZ: Can you cache the results of this function? : function isPrime( num ) { var prime = num != 1; // Everything but 1 can be prime for ( var i = 2; i < 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?" ); One possible way to cache the results: : 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 < 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." ); Context : What happens if a function is an object property? : var katana = { isSharp: true, use: function(){ this.isSharp = !this.isSharp; } }; katana.use(); assert( !katana.isSharp, "Verify the value of isSharp has been changed." ); What exactly does context represent? : 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." ); How can we change the context of a function? : 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." ); Different ways of changing the context: : 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" ); QUIZ: How can we implement looping with a callback? : function loop(array, fn){ for ( var i = 0; i < array.length; i++ ) { // Implement me! } } var num = 0; loop([0, 1, 2], function(value){ assert(value == num++, "Make sure the contents are as we expect it."); assert(this instanceof Array, "The context should be the full array."); }); A possible solution for function looping: : function loop(array, fn){ for ( var i = 0; i < 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."); }); Instantiation : What does the new operator do? : 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." ); We have a 'this' context that is a Ninja object. : 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." ); QUIZ: Add a method that gives a name to the ninja. : 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." ); Add a new property and method to the object. : 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." ); What happens when we forget to use the new operator? : 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." ); What happens when we forget to use the new operator? (cont.) : 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." ); We need to make sure that the new operator is always used. : 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." ); QUIZ: Is there another, more generic, way of doing this? : 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." ); A solution using arguments.callee. : 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." ); Flexible Arguments : Using a variable number of arguments to our advantage. : function merge(root){ for ( var i = 1; i < 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." ); How can we find the Min/Max number in an array? : 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."); Another possible solution: : 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."); Uh oh, what's going wrong here? : function highest(){ return arguments.sort(function(a,b){ return b - a; }); } assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value."); assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results."); QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help? : // Hint: Arrays have .slice and .splice methods which return new arrays. function highest(){ return makeArray(arguments).slice(1).sort(function(a,b){ return b - a; }); } function makeArray(array){ // Implement me! } // Expecting: [3,2,1] assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value."); // Expecting: [5,4,3,2,1] assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results."); We can use built-in methods to our advantage. : function highest(){ return makeArray(arguments).sort(function(a,b){ return b - a; }); } function makeArray(array){ return Array().slice.call( array ); } assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value."); assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results."); QUIZ: Implement a multiplication function (first argument by largest number). : 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.)" ); We can use call and apply to build a solution. : 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.)" ); Closures : A basic closure. : var num = 10; function addNum(myNum){ return num + myNum; } assert( addNum(5) == 15, "Add two numbers together, one from a closure." ); But why doesn't this work? : var num = 10; function addNum(myNum){ return num + myNum; } num = 15; assert( addNum(5) == 15, "Add two numbers together, one from a closure." ); Closures are frequently used for callbacks. : var results = jQuery("#results").html("