Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 7 of 7
  1. #1
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Beginner Q: trying to understand closure

    I'm reading a book (Javascript the Good Parts) and came across this code example (page 39):

    Code:
         // When you click on a node, an alert box will display the ordinal of the node.
         var add_the_handlers = function (nodes) {
             var i;
             for (i = 0; i < nodes.length; i += 1) {
                 nodes[i].onclick = function (i) {
                     return function (e) {
                         alert(e);
                     }; 
                 }(i);
             } 
          };
    Can someone explain to me what happens in this? Here's where I get lost...

    OK so I see how it cycles through all the nodes in a for loop. And for each node, it assigns an onclick event handler to be this function... and immediately calls this function (because after the function definition there are a pair of parenthesis that calls it with the argument i).

    So the onclick is assigned the return value of this function which is:

    Code:
        function(e) {
          alert(e);
        }
    Now this is where I am stuck. Where does e get assigned? It seems like e was assigned to the i value passed in automatically, but it wasn't. How does this happen? Thanks.

  2. #2
    Senior Coder
    Join Date
    Aug 2010
    Posts
    1,123
    Thanks
    30
    Thanked 250 Times in 248 Posts
    Yes, when an event occurs on an element it
    invokes it's handler and any listeners,
    passing to them the event object.


    Welcome to the Forum.
    Cleverness is serviceable for
    everything,
    sufficient in nothing.

  3. #3
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Hi Davey, Thanks for your reply... but perhaps I didn't phrase my question clearly enough.

    I know that invoking a handler will also pass it the event object.

    However, in this case, the output is the number i itself: 0 1 2 3 etc.

    I understand that i is 0, 1, 2, 3, etc. but where does e get its value from? if e is the event object, then shouldn't the alert box say "Object Object Object" etc. instead of 0 1 2 3 etc.? I don't get how e = i here.

    Maybe I am staring at it too long and missing something obvious? Thanks in advance

  4. #4
    Regular Coder
    Join Date
    Feb 2016
    Posts
    128
    Thanks
    0
    Thanked 32 Times in 32 Posts
    if e is the event object, then shouldn't the alert box say "Object Object Object" etc. instead of 0 1 2 3 etc.
    When I enter this code into a test page it does exactly what you are describing: The alert displays [object MouseEvent]
    This example meets a problem being common when eventhandlers are added in a loop and the index is handed over to them. If you add an alert(i) you will see that the correct value is output. For clarity I prefer using different names like this:
    Code:
         // When you click on a node, an alert box will display the ordinal of the node.
         var add_the_handlers = function (nodes) {
             var i;
             for (i = 0; i < nodes.length; i += 1) {
                 nodes[i].onclick = function (idx) {
                     return function (e) {
                         alert(e);
                         alert(idx);
                     }; 
                 }(i);
             } 
          };
    Last edited by Sempervivum; 05-13-2016 at 10:18 PM.

  5. #5
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Oh! ok, so I guess the book's code was faulty. I just accepted what it said would happen without running/testing it (whoops!). Good lesson for the future haha. Yes, that makes much more sense to me, thanks for replying!

  6. #6
    Regular Coder
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    365
    Thanks
    1
    Thanked 50 Times in 47 Posts
    Also beware that legacy IE does not pass an event that way, only "modern" browsers as well as IE9/later do. You want legacy support for JScript (IE's flavor of JS that's different for a whole host of stupid legal reasons) you need to access window.event instead. Likewise there's event.target in JavaScript browsers that legacy IE lacks, and is instead on window.event.srcElement.

    An easy way to do this is to have your function handler be:

    Code:
    function callback(e) {
      e = e || window.event;
      if (!e.target) e.target = e.srcElement;
    }
    Now it will work in all flavors of browsers all the way back to IE 5. I usually have a function dedicated to normalizing "e" that way, as well as having an option for letting me cancel the event's bubbling and propagation... this means if you trap something like an anchor that has a href on it for scripting off behavior (something you should have before you even have a line of JS involved) you can cancel the normal event behavior. (like the browser actually following that href)

    Code:
    function eventProcess(e, prevent) {
    	/*
    		ACCEPTS
    			e - the event if present passed to the callback function
    			prevent -- boolean, prevent further event handlers from firing
    		RETURNS
    			normalized cross browser event
    	*/
    	e = e || window.event;
    	if (!e.target) e.target = e.srcElement;
    	if (prevent) {
    		// this MAY be overkill... Oh, who are we kidding, it is!
    		e.cancelBubble = true;
    		if (e.stopPropagation) e.stopPropagation();
    		if (e.preventDefault) e.preventDefault();
    		e.returnValue = false;
    	}
    	return e;
    }
    You can then inside your callback function just call:

    Code:
    function callback(e) {
    	e = eventProcess(e);
    ... and now E will work as expected across all JS supporting browsers past and present.

    It's also NOT a good idea to directly access .onclick unless you just created the element on the DOM yourself with document.createElement as you have NO idea if other scripts on the page are also (or have already) attached themselves to the element. While it's important to know that onclick exists, you should really be chaining the event onto the element instead... Sadly doing so is also inconsistent between "modern browsers" and legacy IE, so you need a helper function for that too so you can test for attachEvent (IE10/earlier) or addEventListener (everything else).

    Code:
    function eventAdd(e, eventName, callback) {
    	/*
    		ACCEPTS
    			e - element to add event handler to
    			eventName - name of the event to trap (click, mouseover, etc)
    			callback - function to be called when the event happens
    		RETURNS
    			nothing
    	*/
    	if (e.addEventListener) e.addEventListener(eventName, callback, false);
    		else e.attachEvent('on' + eventName, callback);
    }

    Either way that double-function "return a function" for nothing is gibberish, as idx doesn't exist and I wouldn't be passed... it's also bad practice to use "anonymous functions" -- aka the function declaration as a variable assignment -- when the same thing is being assigned more than once. It increases the overhead and parsing time as new instances of that function are uniquely created each and every time. That callback function should be created BEFORE the loop and then passed to eventAdd inside the loop.

    Likewise the var declaration before the loop would be slower and more code than declaring it on the loop, as does looking up the object property on "nodes" of "length" every loop...

    Assuming I'm following the intent of the original code, it should probably look something more like this:

    Code:
    // When you click on a node, an alert box will display the ordinal of the node.
    
    function eventProcess(e, prevent) {
    	/*
    		ACCEPTS:
    			e - the event if present passed to the callback function
    			prevent -- boolean, prevent further event handlers from firing
    		RETURNS:
    			normalized cross browser event
    	*/
    	e = e || window.event;
    	if (!e.target) e.target = e.srcElement;
    	if (prevent) {
    		// this MAY be overkill... Oh, who are we kidding, it is!
    		e.cancelBubble = true;
    		if (e.stopPropagation) e.stopPropagation();
    		if (e.preventDefault) e.preventDefault();
    		e.returnValue = false;
    	}
    	return e;
    }
    
    function eventAdd(e, eventName, callback) {
    	/*
    		ACCEPTS
    			e - element to add event handler to
    			eventName - name of the event to trap (click, mouseover, etc)
    			callback - function to be called when the event happens
    		RETURNS
    			nothing
    	*/
    	if (e.addEventListener) e.addEventListener(eventName, callback, false);
    		else e.attachEvent('on' + eventName, callback);
    }
    
    function nodeHandler(e) {
    	e = eventProcess(e);
    	alert("event : " + e + "\n\n  came from " + e.target);
    }
    	
    function addTheHandlers(nodes) {
    	for (var i = 0, iLen = nodes.length; i < iLen; i += 1)
    		eventAdd(nodes[i], 'click', nodeHandler);
    }
    Knowing about the target attribute is damned handy as it gives you what element actually sent you the event - which is often what you want your scripting to process, and is a place where you can store values unique to the event using data- attributes so you don't end up trying to screw around with globals.

    Oh, and it's generally sloppy to use underscores instead of camelCase -- JS does have a naming convention for function, method, variables, and objects after all. That's not so much of a geniune bug or screwup, as it is a good practice. Wait 'till you get into languages like PHP where even their own function library doesn't follow the language's own alleged naming conventions as it's a hodge-podge from different languages. Real hair puller.

    I realize that being a book example, they probably tried to dumb it down a bit for you removing things like the cross-browser woes... but these are things you should probably be aware of from the start as they will bite you in the backside sooner than later.
    From time to time the accessibility of websites must be refreshed with the blood of designers and owners; it is its natural manure.
    http://www.cutcodedown.com

  7. #7
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Thanks deathshadow! That is quite a lot to think about


 

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •