Junlong Wang
Junlong Wang

Reputation: 456

How to access the outer caller in nested forEach

I'm learning this from MDN. There is a function 'loop'

function loop(){
    ctx.fillStyle='rgb(255,200,124,.35)';
    ctx.fillRect(0,0,width,height);

    while(balls.length<15){
        var ball = new Ball();
        balls.push(ball);
    }

    balls.forEach(function(b){
        b.draw();
        b.update();
        b.collisionDetect();
    });

    requestAnimationFrame(loop);
}

balls is an array, and in collisionDetect() there is another use of forEach as below. I want to make this in collisionDetect() refer to the caller b in loop()

Ball.prototype.collisionDetect = function(){

    balls.forEach(function(bl){

        if(!(this.x===bl.x&&this.y==bl.y
            &&this.velX===bl.velX&&this.velY===bl.velY)){
            var dx = this.x-bl.x;
            var dy = this.y-bl.y;
            var distance = Math.sqrt(dx*dx+dy*dy);

            if(distance<this.size+bl.size){
                bl.color = this.color = "rbg("
                 + random(0,255)
                 +","
                 +random(0,255)
                 +","
                 +random(0,255)
                 +")";
            }
        }
    });
}

I tried passing b as parameter of collisionDetect(). It does work but not what is expected.

I know two forEach can be simply replaced by original for loop, but I just wonder if I could make it work with forEach.

Thank you all very much.

Upvotes: 1

Views: 1167

Answers (3)

Barmar
Barmar

Reputation: 780843

You can bind a local variable to this.

Ball.prototype.collisionDetect = function(){
    var self = this;
    balls.forEach(function(bl){

        if(!(self.x===bl.x&&self.y==bl.y
            &&self.velX===bl.velX&&self.velY===bl.velY)){
            var dx = self.x-bl.x;
            var dy = self.y-bl.y;
            var distance = Math.sqrt(dx*dx+dy*dy);

            if(distance<self.size+bl.size){
                bl.color = this.color = "rgb(" + random(0,255) +"," +random(0,255) +"," +random(0,255) +")";
            }
        }
    });
}

Another way would be to use ES6 arrow functions, because they preserve this.

Ball.prototype.collisionDetect = function(){

    balls.forEach(b1 => {

        if(!(this.x===bl.x&&this.y==bl.y
            &&this.velX===bl.velX&&this.velY===bl.velY)){
            var dx = this.x-bl.x;
            var dy = this.y-bl.y;
            var distance = Math.sqrt(dx*dx+dy*dy);

            if(distance<this.size+bl.size){
                bl.color = this.color = "rbg(" + random(0,255) +"," +random(0,255) +"," +random(0,255) +")";
            }
        }
    });
}

Upvotes: 3

Ori Drori
Ori Drori

Reputation: 191976

This is the signature of Array#forEach:

arr.forEach(callback[, thisArg])

As you can see, Array#forEach accepts a 2nd param, thisArgs, which sets the this of the callback when it's executed.

So, pass the current this as the 2nd param, and it will be assigned to the callback:

Ball.prototype.collisionDetect = function(){

    balls.forEach(function(bl){

        if(!(this.x===bl.x&&this.y==bl.y
            &&this.velX===bl.velX&&this.velY===bl.velY)){
            var dx = this.x-bl.x;
            var dy = this.y-bl.y;
            var distance = Math.sqrt(dx*dx+dy*dy);

            if(distance<this.size+bl.size){
                bl.color = this.color = "rbg("
                 + random(0,255)
                 +","
                 +random(0,255)
                 +","
                 +random(0,255)
                 +")";
            }
        }
    }, this); // set thisArg to this
}

Upvotes: 1

abeyaz
abeyaz

Reputation: 3164

You can bind the function to whatever context/scope you want. This way it will work:

Ball.prototype.collisionDetect = function(){

balls.forEach(function(bl){

    if(!(this.x===bl.x&&this.y==bl.y
        &&this.velX===bl.velX&&this.velY===bl.velY)){
        var dx = this.x-bl.x;
        var dy = this.y-bl.y;
        var distance = Math.sqrt(dx*dx+dy*dy);

        if(distance<this.size+bl.size){
            bl.color = this.color = "rbg("
             + random(0,255)
             +","
             +random(0,255)
             +","
             +random(0,255)
             +")";
        }
    }
 }.bind(this));
}

Upvotes: 0

Related Questions