user736893
user736893

Reputation:

Unexpected values when passing object to function in settimeout()

I found this question, which helped me a lot and I was able to successfully utilize 2 integers with a settimeout(). However, when passing objects it doesn't seem to work.

Full fiddle here.

Code:

$(document).ready(function() {
    var me = {
        x: 0,
        y: 0
    }
    var child = {
        x: 0,
        y: 0
    }
    
    function showxydiff(obj1, obj2, when) {
        var xdiff = (obj1.x - obj2.x);
        var ydiff = (obj1.y - obj2.y);
        $('#' + when).append(xdiff + ':' + ydiff + '... ');
    };
    
    for (var i = 1; i <= 5; i++) {
        child.x = Math.floor((Math.random()*100)+1);
        child.y = Math.floor((Math.random()*100)+1);
        showxydiff(child, me, 'now');
        setTimeout(function (obj1, obj2) {
            return function () {
                showxydiff(obj1, obj2, 'later');
            }
        }(child,me), 500);
    }
});

And here are the results:

now: 57:37... 98:50... 72:87... 66:31... 28:30...

later: 28:30... 28:30... 28:30... 28:30... 28:30...

Upvotes: 0

Views: 105

Answers (2)

Mohit Pandey
Mohit Pandey

Reputation: 3813

Its because of closure. Try this:

$(document).ready(function () {
    var me = {
        x: 0,
        y: 0
    };
    var child = {
        x: 0,
        y: 0
    };

    function showxydiff(obj1, obj2, when) {
        var xdiff = (obj1.x - obj2.x);
        var ydiff = (obj1.y - obj2.y);
        $('#' + when).append(xdiff + ':' + ydiff + '... ');
    };

    function callTimeout(obj1, obj2) {
        setTimeout(function () {
            showxydiff(obj1, obj2, 'later');
        }, 500);
    }

    for (var i = 1; i <= 5; i++) {
        child.x = Math.floor((Math.random() * 100) + 1);
        child.y - Math.floor((Math.random() * 100) + 1);
        showxydiff(child, me, 'now');
        // create a new object for removing referencing
        callTimeout($.extend({}, child), $.extend({}, me));
    }
});

Results:

now:
2:59... 6:96... 34:6... 60:3... 71:54...
later:
2:59... 6:96... 34:6... 60:3... 71:54...

Working JSFiddle: http://jsfiddle.net/vTY8U/11/ (using extend of jquery to create a separate object)

http://jsfiddle.net/vTY8U/10/ (create a local child object)

For more info on closure, go through docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

Upvotes: 0

jfriend00
jfriend00

Reputation: 707258

Your problem is that the same child object is being used for every one of your setTimeout() calls thus the for loop modifies the object that all the setTimeout() callbacks will use. This means they all end up with the same value. This is because objects are passed by reference (no copy is made) so all your callbacks have a reference to the exact same object.

You need to either create a new child object for each setTimeout() or pass just the .x and .y values separately.

for (var i = 1; i <= 5; i++) {
    // create new child object for each time through the loop
    child = {};
    child.x = Math.floor((Math.random()*100)+1);
    child.y = Math.floor((Math.random()*100)+1);
    showxydiff(child, me, 'now');
    setTimeout(function (obj1, obj2) {
        return function () {
            showxydiff(obj1, obj2, 'later');
        }
    }(child,me), 500);
}

I also prefer this method of creating the closure (simpler for me to understand):

for (var i = 1; i <= 5; i++) {
    // create new child object for each time through the loop
    // use more compact javascript literal syntax
    child = {x: Math.floor((Math.random()*100)+1), y: Math.floor((Math.random()*100)+1)};
    showxydiff(child, me, 'now');
    // use an immediately invoking function expression
    // to create a closure to capture the current child object reference
    (function(obj1, obj2) {
        setTimeout(function() {
            showxydiff(obj1, obj2, 'later');
        }, 500);
    })(child, me);
}

Working demo: http://jsfiddle.net/jfriend00/Qbd6V/

Upvotes: 1

Related Questions