Jump to content

Managing scope for function calls without bind()


QLNEO
 Share

Recommended Posts

Two threads in less than 30 minutes. I hope I'm not tiring you guys!

This is more a Javascript-related question than anything. In short, I'm utilizing this.game in my state functions. However, when invoking functions inside these functions, their scope isn't the Phaser state anymore, so this becomes undefined. Sure enough, I can simply use functionName.bind(this)(parameters), or functionName.call(this, parameters), and that's what I'm doing right now, but when I do that for every function internally called, it starts becoming an anti-pattern, doesn't it?

Anyone has an idea on how can I make sure this refers to Phaser state without having to assign bind() to every function my state funcions call? Thanks!

Link to comment
Share on other sites


var game = {
  foo: 'bar',
  log: function () {
    console.log(this.foo)
  },
  deepLog: function () {
    function internalLog () {
      console.log(this.foo)
    }
    internalLog()
  },
  arrowLog: function () {
    var log = () => {
      console.log(this.foo)
    }
    log()
  },
  bindLog: function () {
    var log = function () {
      console.log(this.foo)
    }.bind(this)
    log()
  }
}

game.log()
game.deepLog()
game.arrowLog()
game.bindLog()
// bar
// undefined
// bar
// bar

How are you defining your functions?

Presumably like `deepLog` above, in which case the internalLog function there get scoped to the root (window in the browser) as its not attached to any other object so can't guess at what scoping a developer thinks it should have. Arrow functions have a different functionality, whereby they have no scope and usually inherit the parent scope, which in this case is probably what you're expecting. Of course, you're working around this using the bind method which tells the JS engine how you want it scoped, note that it is standard JS behaviour to muck with scope (rightly or wrongly, e.g. calling event handlers such as attaching to HTMLDomElement.addEventListener) and .bind will stop this mechanism from working, that isn't always a problem though.

Question is why are you using internal functions at all? Beyond that, why use them when you need to access scope? If they are accessing scope why can't they be methods on game? or be utility functions where you explicitly pass an object to work with?

Link to comment
Share on other sites

Thank you for your answers.

@mattstyles my code looks more like this:

function preload() {...}
function create() { ... fun1.bind(this)() }
function update() { ... fun2.bind(this)(); fun3.bind(this)() }
// Yeah I could use call() instead, but I find this easier to understand the function parameters

function fun1() {...}
function fun2() {...}
function fun3() {...}

module.exports = { // Calling this file as state object later
    preload, create, update
}

In short, I want to use the state functions, and I also define some other functions they'll keep using throughout the state.

Link to comment
Share on other sites

Adding a parameter where needed[0]:

function preload() {...}
function create() { ... fun1(this.game, a, b, etc) }
function update() { ... fun2(this.game, a, b, etc); fun3(this.game, a, b, etc) }

function fun1(game, etc) {...}
function fun2(game, etc) {...}
function fun3(game, etc) {...}

module.exports = { // Calling this file as state object later
    preload, create, update
}

Or using IIFE


var App = (function(api, game){
  api.game = game;

  function preload() {...}
  function create() { 
    /* use api.game to access game instead of this.game */
    /* read the docs,
       and see how you can get away with cleaner code that doesn't rely on binding */ 
  }
  function update() { ... fun2(); fun3() }

  function fun1() {...}
  function fun2() {...}
  function fun3() {...}

  //expose stuff
  api.preload = preload;
  api.create = create;
  api.update = update;

  return api;
})(App || {}, this.game /* or other stuff that I can't remember*/)

In fact, I'd recommend going this route if you want to stick to ES5 because it simplifies your life, AND you are very free to use multiple files to separate concearns, only expose what you want,etc.

 

Lastly, you may consider ES6 and Webpack (which makes everything butter smooth)

import 'pixi';
import Phaser from 'phaser';

import BootState from './states/Boot';
import SplashState from './states/Splash';
import MenuState from './states/Menu';
import PlayState from './states/Play';

/** Class representing a game.
 * @extends Phaser.Game
 */
class Game extends Phaser.Game {

    /**
     * @constructs Create a game. When done, go to the {@link Boot} state.
     */
    constructor() {
        let width = document.documentElement.clientWidth;
        let height = document.documentElement.clientHeight;
        let config = {/*[REDACTED for brevity] */};
        super(config);

        // Add all necessary game states
        this.state.add('Boot', BootState, false);
        this.state.add('Splash', SplashState, false);
        this.state.add('Menu', MenuState, false);
        this.state.add('Play', PlayState, false);

        this.state.start('Boot');
    }

}

new Game();

[0] http://pmuellr.blogspot.ca/2010/06/bind-considered-harmful.html

Anyways, some suggested readings on the topic:

[1] https://github.com/getify/You-Dont-Know-JS/blob/master/scope %26 closures/apC.md

[2] https://github.com/getify/You-Dont-Know-JS/blob/master/this & object prototypes/README.md#you-dont-know-js-this--object-prototypes

[3] https://codereview.stackexchange.com/questions/49872/using-var-self-this-or-bindthis

 

Link to comment
Share on other sites

2 hours ago, QLNEO said:

function create() { ... fun1.bind(this)() } 
function update() { ... fun2.bind(this)(); fun3.bind(this)() } 

// Yeah I could use call() instead, but I find this easier to understand the function parameters
function fun1() {...} 
function fun2() {...} 
function fun3() {...} 

module.exports = { // Calling this file as state object later 
  preload, create, update }

 

Yeah, this isn't a great idea. fun1, fun2 and fun3 aren't functions attached to objects so they should not be touching `this` at all, as it makes no sense.

Either pass in a context to work against or declare them as part of the object they reference in order to work, @ldd has shown a couple of ways of doing that.

Link to comment
Share on other sites

Put them on the state object?

{
  create: function () {
    this.createThings();
  },
  update: function () {
    this.updateThings();
  },
  createThings: function () {
    this.add.sprite(/**/);
  },
  updateThings: function () {
    this.physics.arcade.collide(/**/);
  },
}

 

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...