0

I'm trying to pass the function behappy() of the Particle Class, as a parameter of the applyBehavior function of the ParticleSystem class. Am I missing some syntax ? I don't understand what is wrong with my calls.

Minimal reproducible code is here and here.

class ParticleSystem{
  constructor(){
    this.particles = [];
    this.numberOfParticles = 10;
    for(let i = 0; i < this.numberOfParticles; i++){
      this.particles.push(new Particle());
    }
  }
  
  applyBehavior(behavior, message){
    for(let i = 0; i < this.numberOfParticles; i++){
      this.particles[i].behavior(message);
    }
  }
}

class Particle{
  constructor(){}
  behappy(message){
    print(message);
  }
}

let particlesys = new ParticleSystem();
particlesys.applyBehavior(behappy, "i am happy now");

expected output should be :

"i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now" "i am happy now"

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Kaspie
  • 187
  • 11
  • The Particle system related code which is supposed to *bulk apply* the behavior to each particle **does not assign** the behavior but tries to invoke it immediately. On the other hand. Any `Particle` instance already features a (prototypal) `behappy` method. Thus, in case `applyBehavior` is supposed to invoke a particle's method, one firstly should rename this method to `triggerBehavior` or `callBehavior` or `executeBehavior` and secondly needs to provide the particle's **method/behavior name** as first argument instead of (as of now) the function reference. – Peter Seliger Dec 29 '20 at 14:09
  • `execute(behaviorName, message) { ... this.particles[i][behaviorName](message); ...}` – Peter Seliger Dec 29 '20 at 14:13
  • The variable `behappy` that you pass to `applyBehaviour` is not defined. You need to pass a string: `particlesys.applyBehavior("behappy", "i am happy now");` – Bergi Dec 29 '20 at 15:13

1 Answers1

1

In case a particle system's applyBehavior is supposed to invoke a particle's method, one firstly should rename this method to callBehavior, executeBehavior or, even better, executeParticles and secondly needs to provide the particle's method/behavior name as first argument instead of a function reference.

Edit

As Bergi already did point to, one of cause has to assume that any method of a Particle instance most probably operates within a Particle instance's this context.

A ParticleSystem's executeParticles method then has to be implemented in a way which explicitly provides such a context to any accessible Particle method. Since functions are object itself, they come with two methods which achieve exactly that ... call and apply. A target object can be provided to both methods. This target object does become the context of any method/function which gets invoked via call/apply.

The example code got changed accordingly, in order to show this use case ...

class ParticleSystem{
  constructor(){
    this.particles = [];
    this.numberOfParticles = 10;
    for(let i = 0; i < this.numberOfParticles; i++){
      this.particles.push(new Particle(i));
    }
  }
  executeParticles(behaviorName, message){
    // for(let i = 0; i < this.numberOfParticles; i++){
    //  this.particles[i][behaviorName](message);
    // }
    this.particles.forEach(particle => {
      const behavior = particle && particle[behaviorName];
      if (typeof behavior === 'function') {

        // MDN: "Function.prototype.call"
        // see: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call]

        behavior.call(particle, message);
      }
    });
  }
}

class Particle{
  constructor(idx){
    this.id = `particle_${ idx }`;
  }
  behappy(message){
    console.log(`${ this.id } logs ... "${ message }"`);
  }
}

let particlesys = new ParticleSystem();
particlesys.executeParticles('behappy', "I'am happy now.");
.as-console-wrapper { min-height: 100%!important; top: 0; }

The most generic implementation of executeParticles which makes no assumptions about a particle method's arguments would utilize apply ...

class ParticleSystem{
  constructor(){
    this.particles = [];
    this.numberOfParticles = 10;
    for(let i = 0; i < this.numberOfParticles; i++){
      this.particles.push(new Particle(i));
    }
  }
  executeParticles(behaviorName, ...args){
    this.particles.forEach(particle => {
      const behavior = particle && particle[behaviorName];
      if (typeof behavior === 'function') {

        behavior.apply(particle, args);
      }
    });
  }
}

class Particle{
  constructor(idx){
    this.id = `particle_${ idx }`;
  }
  behappy(message){
    console.log(`${ this.id } logs ... "${ message }"`);
  }
}

let particlesys = new ParticleSystem();
particlesys.executeParticles('behappy', "I'am happy now.");
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • This does not work for methods that use the `this` keyword – Bergi Dec 29 '20 at 15:14
  • To me, this requirement is implicit in "invoke a method on an instance". No matter what the method does. – Bergi Dec 29 '20 at 15:24
  • 1
    Thanks! Using `call` should not be necessary though, `particle[behaviorName]()` would be much simpler – Bergi Dec 29 '20 at 15:51