1

Take Object3D base class as an example:

rotateOnAxis: function () {

    // rotate object on axis in object space
    // axis is assumed to be normalized

    var q1 = new Quaternion();

    return function rotateOnAxis( axis, angle ) {

        q1.setFromAxisAngle( axis, angle );

        this.quaternion.multiply( q1 );

        return this;

    };

}(),

... and this pattern is commonly seen in other classes, why not just write a normal function like: function rotateOnAxis(axis, angle) {...}?

Jim Tang
  • 92
  • 7
  • It creates a closure around the `q1` variable. – 4castle Feb 12 '18 at 14:15
  • 2
    Because this would create a new `Quaternion` on each call of `rotateOnAxis` -> [How do JavaScript closures work?](https://stackoverflow.com/questions/111102/how-do-javascript-closures-work) – Andreas Feb 12 '18 at 14:15
  • 2
    Possible duplicate of [How do JavaScript closures work?](https://stackoverflow.com/questions/111102/how-do-javascript-closures-work) – 4castle Feb 12 '18 at 14:16
  • 2
    Is it expensive (in memory or time) to create a `Quaternion`? That would be the most likely reason, to my mind. – Scott Sauyet Feb 12 '18 at 14:17
  • @Andreas that's the point! I'm reading that great post, thanks. – Jim Tang Feb 12 '18 at 14:29
  • Related: https://stackoverflow.com/q/43404555/1048572 – Bergi Feb 12 '18 at 14:33
  • @ScottSauyet Yes, Quaternion is a little bit complicated class. I think three.js is doing a detailed optimization here. – Jim Tang Feb 12 '18 at 14:33

2 Answers2

2

A closure is used in this case to avoid instantiating a temporary quaternion every call.

This coding pattern is used frequently in three.js -- especially in methods that may be called in tight loops, such as an animation loop.

three.js r.89

WestLangley
  • 102,557
  • 10
  • 276
  • 276
0

By referring to How do JavaScript closures work? and printing some variables on chrome console, I think "a closure is a stack frame which is allocated when a function starts its execution, and not freed after the function returns (as if a 'stack frame' were allocated on the heap rather than the stack!)" can perfectly explain this.

when I eval 'app._scene.scene.children[0].rotateOnAxis' on the console, it prints:

ƒ rotateOnAxis( axis, angle ) {

      q1.setFromAxisAngle( axis, angle ); <-- q1 is referenced but no declaration

      this.quaternion.multiply( q1 );

      return this;

  }

and eval 'app._scene.scene.children[0].rotateOnAxis.q1' prints:

undefined.

and eval 'app._scene.scene.children[0].rotateOnAxis.prototype' prints:

{constructor: ƒ}  
 + constructor:ƒ rotateOnAxis( axis, angle )
 | ... (ignored here)
 | [[Scopes]]:Scopes[3]
 ---+ 0:Closure {type: "closure", name: "", object: {…}}
    |--+ q1:Quaternion {_x: 0, _y: 0, _z: 0, _w: 1} <-- scope search here
    | 1:Closure {type: "closure", name: "", object: {…}}
    | 2:Global {type: "global", name: "", object: Window}

Seen above there's clearly a 3-layers stacking closure frame attached to this rotateOnAxis function's prototype.constructor property. And another important thing is the var(q1) saved in the closure will change whenever the outer function is invoked, so the content of var(q1) must be reset in its setFromAxisAngle interface.

Jim Tang
  • 92
  • 7