[WIP] javascript:this.is('mysterious'); //_that too


#1

… Collecting some notes/thoughts together to try and explain how JavaScript “this” works within scripts – and the best apparent ways to deal with its quirks. Starting to go cross-eyed myself with all the edge cases, but might be a useful reference point for anyone battling awkward this situations. Any feedback appreciated! thx -t

this

JavaScript this is a special keyword that represents the scope a function is being called with. That makes it in part a runtime thing – and means the source code for a function only provides one puzzle piece needed to anticipate this values.

There is nothing particularly fragile about JavaScript this. However, it can seem to behave mysteriously or even arbitrarily sometimes.

In reality that appears to just be the different calling conventions used between Qt, HiFi and various helper APIs – which require slightly-different strategies to make effective and consistent use of this with.

Common to all is that the caller – not the callee– determines what the natural value of this will be.

Therefore to anticipate the way this works – both the function and how it gets invoked have to be considered together (and per each invocation pattern).

Client scripting scenarios and this

Each of these examples refer to the value this has within the callback when triggered:

Script.setTimeout(
  function callback() { /* this === callback */ },
  ms
);

Script.update.connect(
  function callback() { /* this === [object global] */ }
);

Script.addEventHandler(
  uuid, event, 
  function callback() { /* this === [object global] */ }
);

var o = {
  callback: function() { /* this === [object global] */ }
};
Assets.uploadData(data, o.callback);

Because this is unlikely to be a useful value by default, with most of the APIs scoping hacks are needed to arrange for bound values instead. Some downhill paths are mention down below.

Entity scripting scenarios and this

Consider an Entity script where a custom object is returned – below is what to expect from within corresponding callbacks:

(function() {
  /* this === [empty object #1] */ // unused
  function Thing() {
    /* this === [empty object #2] === obj */
  }
  Thing.prototype = {
    preload: function(uuid) { /* this === obj */ },
    clickDownOnEntity: function(uuid, evt) { /* this === obj */ },
    customFunc: function(uuid, args) { /* this === obj */ }
  };
  // later: Entities.callEntityMethod(uuid, 'customFunc', [1,2,3]);
  var obj = new Thing();
  return obj;
})

So in general, no extra scoping hacks are needed to leverage this from simple Entity scripts. You can set a property on this within preload and access it again from another handler:

.preload = function(uuid) { this.name = "foo"; };
.clickDownOnEntity = function(uuid, evt) { print(this.name); }; // foo

But things do get a little more complicated when you start mixing other APIs that require callbacks with Entity handlers…

_this, thiz, that (closures)

The “_this closure” can be used to cut across component/abstraction boundaries and create a reference to this that can be used in the future to invoke a method in the right context. By definition anything that references _this will now be tightly-coupled to the closure. To avoid such tight couplings at the inner JS Class and component level, the technique should be applied sparingly and in the smallest context that can work:

var obj = {
  method: function(a, b) {
    print('a,b!', a, b);
  },
  delayMethod: function(a, b, ms) {
    var _this = this;
    Script.setTimeout(function callback() { _this.method(a,b); }, ms);
    // note: if we just did .setTimeout(this.method, ms)...
    // ... then `this` would be wrong inside method and [a,b] wouldn't get conveyed either
  }
};

Qt signals and this scope

Anytime a signal’s .connect or .disconnect methods are used from within a Client/Entity script… the two-argument variations are available and provide an opportunity to have the system itself address common this pains automatically.

var obj = {
  channel: 'pizza-delivery',
  onmessage: function(channel, message, sender, localOnly) {
    if (channel === this.channel)
      this.onpizza(JSON.parse(message));
  },
  onpizza: function(pizza) {
    print('one slice!', pizza.slices.shift());
  }
};

// here `this` will take on the global scope (which is probably not what we want)
Messages.messageReceived.connect(obj.onmessage); 

// but any of these two-arg variations will arrange for `this` to equal `obj` automatically:
Messages.messageReceived.connect(obj, 'onmessage');   // Qt will resolve obj['onmessage'] for you
Messages.messageReceived.connect(obj, obj.onmessage); // Or you can provide the raw function value

// note that you can leverage Qt signal scoping ad hoc --
//   (arranging for `this` to equal `obj` with any function value)
function callback(channel, message, sender, localOnly) {
  print('this.channel', this.channel); // 'pizza-delivery'
}
Messages.messageReceived.connect(obj, callback);

The only known place .connect will not work like that is on the HTML-side with browser JavaScript and that-side EventBridge…connect() (QWebChannel JS only “approximates” Qt signals and doesn’t support scoping). Not much of a problem though because there you get Function.prototype.bind built-in.

Beyond that, this approach works very reliably and the two-argument variation is always there to use with actual signals, even as an incremental improvement when updating existing scripts. Just be sure to match parity with whatever version you go with when disconnecting:

Messages.messageReceived.disconnect(obj, 'onmessage');
Messages.messageReceived.disconnect(obj, obj.onmessage);
Messages.messageReceived.disconnect(obj, callback);

Function.prototype.bind

More concise than one-off closures (and more consistent with modern JavaScript patterns) – callback.bind(scope, [arg1, arg2, ...]) returns a bound function value that always invokes callback using the specified scope and optional arguments – even when that bound function value itself is called with a different scope (or no scope at all).

// (next line is a compact polyfill for .bind that lets you use it immediately within HiFi)
Function.prototype.bind=function(){var fn=this,a=[].slice.call(arguments),o=a.shift();return function(){return fn.apply(o,a.concat([].slice.call(arguments)));};};

// .bind can be used to simplify deferred invocations and `this` at same time:
var obj = {
  name: 'myobj',
  ontimeout: function() { print('timeout!', this.name); },
  method: function(a, b) { print('a,b!', a, b); },
  delayMethod: function(a, b, ms) {
    //Script.setTimeout(function callback() { _this.method(a,b); }, ms);
    Script.setTimeout(this.method.bind(this, a, b), ms);
  }
};

obj.delayMethod(5, 6, 1000); // ~1s later prints "a,b! 5 6"

// .bind lets you arrange for an exact `this` value inline
Script.setTimeout(obj.ontimeout.bind(obj), 2000);
// ~2s later prints "timeout! myobj"

If unfamiliar with .bind then it might look a little strange at first, but unlike closure hacks it is inherently expressive and concise (it says what it does, does just that one thing well, and gives you everything needed in one spot to anticipate the value of this used). It might even be useful enough to consider patching into all scripting contexts by default.

References

//wip


#2

Sometimes it seems to me that technical debt applies to all code that has been written and looked at again later.

Thanks for the overview. This kind of stuff helps point me in the right direction when my crappy code doesn’t give the results I expect.


#3

Thanks for documenting this.

Puns aside, this is actually quite damn annoying. Hifi/Qt doesnt have yet the ES6 bind definition, to make the context of this the this that is where the method is defined.

Instead of _this I tend to use the closure variable self in naming conventions.


#4

Still hate this, that and now bind and now the prototype stuff. I not get my head around it. until i find or understand it.

Working the pascal way is much more readable. Programming fr high fidelity is complex because this , bind and pototypes. Same reason why i hate php. never got to understand the this stuff.

Need to read tnis topic later again.


#5

Yeah that’s certainly true too.

The technical debt I was referring became an unwanted choice this year… I had code from last year using _this like a global variable, but more recently wanted to extract reusable parts that were being incubated there. Except first I had to pay off my technical debt to free them (several _this references had leaked into my otherwise-standalone components – and were non-trivial to reverse given the layers-on-layers I had stacked on top since).

(anyhow, in OP I have now tried removing the blurb on technical debt in favor of attempting to convey the apparent cause-effect relationship and a mitigation strategy)


#6

The “pascal way?” Not sure I am familiar with that way… could you elaborate / provide links to read up on it?

Also I would be curious to hear your first blush reaction to below kind of shorthand factoring:

box = Domain.upsert({
  $id: '@zone #box',
  type: 'Box',
  color: 'blue',
  position: '@user',
  orientation: '@user',
  scale: .5,
});

box.states = {
  active: {
    0: { color: 'green', scale: 1 },
    1: { color: 'blue', scale: .5 },
  }
};

box.ontrigger = function(evt) {
  console.info('trigger', evt.json);
};

box.on = {
  click: function(evt) {
    box.states.active = !box.states.active;
  },
  step: function(evt) {
    // simple up-down animation
    box.position.y += Math.sin(evt.timeStamp / (100 * box.states.active.scale) );
  },
  [...]
};

// pseudo-code for mapping to lower-level constructs:
//
// $id        => kept uniq within zone; if found entity is updated else inserted
// ontrigger  => grab API boilerplate (widdled-down to simplest use cases)
// click      => mousePressOnEntity/clickDownOnEntity boilerplate
// step       => Script.update boilerplate
// position.y => entity.position = Vec3.add(entity.position, {x:0, y: dy, z: 0})
// scale      => entity.dimensions = Vec3.multiply(scale, entity.naturalDimensions)
// 'blue'     => { red: 0, green: 0, blue: 255 }
// '#f0f'     => { red: 255, green: 0, blue: 255 }
// '@user'    => position: the boilerplate Quat.getFront/Vec3/MyAvatar stuff
// '@user'    => orientation: ditto

#7

What you just did show above is fine, and close to how it feel and look for me in pascal. I like how hifi works with javascript. compared to lsl it’s heaven.

What i only wish is that the this and that. where removed.
Especially this is a headbreaker. But in pascal pointers where a headbreaker, and i think still is for me.

A very good basic simple clear tutorial about THIS and PROTOTYPES is possible doing the trick to. with very simple tiny examples. THIS got me stuck in php. and with javascript it’s more trail and error. if it works fine. but why it works i would not know.

THIS a disaster :slight_smile:


#8

Smith & Dale:

SMITH: Doctor, it hurts when I do this.
DALE: Don’t do that.

To me the pain of this is a lot like getting a flat tire. Tutorials might not help with the pain of either case.

In my estimation the best bet is to have Something that can be used in a complementary way that sits on top of the current APIs:

x86 -> C++ -->> W3C DOM APIs -> jQuery -->> WYSIWYG -> SkyNet
                                ......  

x86 -> C++ -->> HIFI APIs -> *Something* -->> WYSIWYG -> Metaverse-Next -> SkyNet
                             ...........  

#9

Pascal is very old, later it changed ito delphi. i used delphi up to version 2005. After that Microsoft broke it and not did spend money anymore on new delphi after that i really did not do much hobby programming anymore. Also after delphi 2010 the terrible THIS started to sneak in delphi to. and i hate THIS :grin: Lazarus is the open source version that is created by users. Delphi and lazarus are slightly different with some commands, that’s not strange.

Here is me favorite page with all commands and help delphi use.
http://www.delphibasics.co.uk/

On this page is small example of delphi program that is working with forms.
http://www.delphibasics.co.uk/Article.asp?Name=FirstPgm

My biggest nightmare of programming languages are PHP and Javascript for webpages. The are terrible to debug. Lucky javascript in high fidleity is better.
Also my biggest speedbump are THIS, CONSTRUCTORS and DECONSTRUCTORS. I made a few years ago in C# a small dos console program. Sometimes i get the idea that C# is more easy and close to have delphi feels compared to Javascript.

But i like JSON !

I cannot help it, but pascal / delphi always seems to create more readable code if done right. But i also used pascal delphi most as language.


#10

Hey @humbletim would you mind showing the Chrome JS Console Debugger here? I will try to make a video about it, but integrating it with HF Interface requires a few hours of setup using your instructions.

@Richardus.Raymaker,
I too used to think debugging JS was an absolute nightmare until someone took the time to show me the proper tools. With the JS debugger in Chrome you can pause a script and step through line by line, and also change a running script AT RUNTIME to perform a different action. It is extremely powerful stuff that was never discussed during the JS Office Hours. Speaking of, @Jess perhaps a proper lesson plan could be constructed and then committed to. Speaking only for myself, I would be able to host these sessions anytime after 6pm EST.


#11

… Do you maybe mean the Qt Script Debugger?

For Client scripts you can add the magic string #debug to have a script loaded in “debug mode.”

Each script loaded that way appears in a new menu called “Script Debug” – which lets you access the Qt Script Debugging options for interrupting / stepping through code. Also if any uncaught exceptions occur the script will be paused and Qt Script Debugger opened automatically to the location.

Once in the debugger you can inspect variables, interactively set breakpoints, etc.

A useful trick when troubleshooting is to code virtual breakpoints (in the script itself) using JavaScript’s debugger statement:

// HiFi-specific way to enable "debug mode" for the script
"#debug";

"# debug";// <-- would have no effect...

// opens Qt Script Debugger paused as-if a breakpoint had been set
debugger; 

// `debugger` only affects debug mode (and is a no-op otherwise)
//  you can use it anywhere JS statements can be used:

// see: https://xkcd.com/221/
function getRandomNumber() {
  debugger;
  return 4; // chosen by fair dice roll.
            // guaranteed to be random.
}



#12

I see one problem with this. It’s using the build in script editor.
Just the one i never use and can use as long it’s so terrible readable and small font size and bad colors. Still waiting from day 1 for a better script editor.

I write scripts with Notepad++ Does the debugger work with that to ?


#13

Thanks, I didn’t know you could set breakpoints like that, will be handy.

Mental note: Don’t play cards with @humbletim


#14

… that’s just a coincidence – nothing above is specific to the (HiFi) Script Editor. You can add #debug and debugger; statements from Notepad++ and when loaded/reloaded the script will pick up debug mode still.

Script Editor does, however, make for a powerful rapid iteration tool. I don’t use the editing aspect… Just the big Stop/Start icons (making it easy to alt-tab and reload the script), the debug pane (which shows me just that script’s print(...) output) and the command prompt (which lets me interact with top-level variables / functions while the script is running).