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.