Page 1 of 1

JS "this" question

Posted: Fri Mar 04, 2011 2:37 pm
by Commander McLane
I am experimenting with SystemInfo, and want to understand something.

I had the following code, which is supposed to randomly select a system and calculate the route to that system from the player's current system:

Code: Select all

var amnestySystem = Math.floor(Math.random() * 256);
        var amnestyRoute = system.info.routeToSystem(System.infoForSystem(galaxyNumber, amnestySystem), "OPTIMIZED_BY_TIME");
(Some people may guess what I'm working on.) It didn't work. amnestyRoute was always null.

I replaced it with a this.variable:

Code: Select all

this.amnestySystem = Math.floor(Math.random() * 256);
        this.amnestyRoute = system.info.routeToSystem(System.infoForSystem(galaxyNumber, this.amnestySystem), "OPTIMIZED_BY_TIME");
The result was the same. this.amnestyRoute was always null.

Then I thought: perhaps inside the SystemInfo object (not sure whether the outer or the inner, or both) this doesn't refer to my script anymore, so I tried:

Code: Select all

this.amnestySystem = Math.floor(Math.random() * 256);
        this.amnestyRoute = system.info.routeToSystem(System.infoForSystem(galaxyNumber, worldScripts.Anarchies.amnestySystem), "OPTIMIZED_BY_TIME");
And now it worked.

I haven't quite understood yet, however, why this is so. Can I get some more enlightenment about this? Where is it supposed to refer to what?

Re: JS "this" question

Posted: Fri Mar 04, 2011 3:28 pm
by Eric Walch
Commander McLane wrote:
I replaced it with a this.variable:

Code: Select all

this.amnestySystem = Math.floor(Math.random() * 256);
        this.amnestyRoute = system.info.routeToSystem(System.infoForSystem(galaxyNumber, this.amnestySystem), "OPTIMIZED_BY_TIME");
The result was the same. this.amnestyRoute was always null.
Are you sure there was no other change? When I use this code, copied from above, in the console, it works. I also used it in a worldScript. On logging this.amnestyRoute I get [object Object] (not null) and when logging this.amnestyRoute.distance I get a correct value.

Re: JS "this" question

Posted: Fri Mar 04, 2011 3:32 pm
by JensAyton
In all the code you presented, this is in the same scope and should work in the same way. This suggests you’re in a context where this is not worldScripts.Anarchies. This could happen, for instance, if your code is in a function that’s not being called as a method. If you need more help, post the code with more context.

Re: JS "this" question

Posted: Fri Mar 04, 2011 5:22 pm
by Commander McLane
I'm seeing it now. The very next line checks whether the randomly chosen system is disconnected, and stops the function if so.

Code: Select all

        if(this.amnestyRoute == null) return;
I got the error message after that line, and just understood why. At first I had accidentally only typed one '=', so I was actually setting my variable to null. No wonder that it was null afterwards.

Obviously I corrected the typo together with starting to use worldScripts.Anarchies, so I didn't see the correlation right away. :oops:

Re: JS "this" question

Posted: Fri Mar 04, 2011 7:29 pm
by Micha
Commander McLane wrote:
I got the error message after that line, and just understood why. At first I had accidentally only typed one '=', so I was actually setting my variable to null. No wonder that it was null afterwards.
This is actually quite a common programming error and, depending on circumstances, quite difficult to find.

That's why some people write comparisons with the const value first, ie:

Code: Select all

if(null == this.amnestyRoute) return;
That way you immediately get an error if you use '=' instead of '==', although it makes reading the code seem ass-backwards.

Re: JS "this" question

Posted: Fri Mar 04, 2011 9:38 pm
by JensAyton
Tools like JSLint and JSHint can also be useful, but they’re geared towards web scripting so they don’t know about Oolite’s objects and reject SpiderMonkey extensions.

Re: JS "this" question

Posted: Fri Mar 11, 2011 9:59 pm
by stevesims
Sorry for the slight lateness of this reply...

In JavaScript, it's generally good practice to use === for equality comparisons, rather than ==. That's because the == does type coercion... Similarly it's considered to be good practice to always wrap commands after an "if" statement in a block, to help avoid shooting yourself in the foot.

JavaScript does a few things that many would consider odd. For example, it has a feature called 'automatic semicolon insertion' whereby if you've forgotten to end a line with a semicolon it'll most likely work just fine and you'll not see an error. Another oddity is that variables are defined in function scope, rather than block scope - which means for example that code like the following will not work as one may expect:

Code: Select all

function test() {
  for (var i = 0; i < 5; i++) {
    for (var i = 0; i < 2; i++) {
      console.log("here is a message");
    }
  }
}
Coming from block-scope languages you'd expect the above code to produce 10 messages in the console, but in fact it's an infinite loop. Function-scope, combined with variable definition hoisting means that there's only actually one 'i' variable inside the test function, so the inner loop overwrites the value of the single 'i' variable when it executes. (You won't see an error or warning about 'i' getting redefined.) The easy way around this is to just remember to not re-use variables and replace the inner 'i' with a 'j' - a slightly more perverse solution would be this:

Code: Select all

function test() {
  for (var i = 0; i < 5; i++) {
    (function() {
      for (var i = 0; i < 2; i++) {
        console.log("here is a message");
      }
    }());
  }
}
This technique of wrapping up statements inside an anonymous function block is useful for creating local variables that don't pollute the global object, and is a pattern you'll often see adopted by JS libraries.

The best resource I've found to learn about this stuff is Douglas Crockford's writings. This page of his, for example, explains why it's often bad to use ==

Crockford works for Yahoo, and there's a series of 6 "Crockford on JavaScript" videos in the YUI Theatre that are well worth watching.

JSLint (written by Crockford) and the more recent friendlier variant JSHint are your friends - they'll point out bad practices and help keep you out of trouble.

Re: JS "this" question

Posted: Sat Mar 12, 2011 10:58 am
by Eric Walch
stevesims wrote:
Similarly it's considered to be good practice to always wrap commands after an "if" statement in a block, to help avoid shooting yourself in the foot.
The easiest way to shoot yourself in the foot is using:

Code: Select all

if (condition) i = true;
else i = false
The semicolon terminates here the if statement and sees the 'else' as a new one, resulting in 'i' to be always false. Using {} for wrapping largely avoids such errors, as you write. :P (edit: It seems the semicolon is allowed after all. I thought I had problems with it in the past but i probably had a different problem)

With the double 'i' declaration in your example it helps when using the 'let' statement. It is not an official JS statement. In the ecma 5 specifications it is listed as "future reserved word". But the JS variant used by Oolite knows it.

Re: JS "this" question

Posted: Mon Mar 14, 2011 5:00 pm
by stevesims
Yeah - 'let' is a proposal for ES6, adding in block-scope variables to JavaScript. Mozilla frequently prototype future extensions to JavaScript in their interpreters, releasing them to the public. There's an inevitable risk that if you try using such bleeding-edge language features you'll get bitten at some unspecified point in the future since they might get removed (or change in an incompatible way).

Whilst ES6 is quite a long way away from getting finalised, I suspect that 'let' is a fairly safe bet to make it in there.

Re: JS "this" question

Posted: Mon Mar 14, 2011 11:04 pm
by JensAyton
stevesims wrote:
Yeah - 'let' is a proposal for ES6, adding in block-scope variables to JavaScript.
Technically, it’s a leftover from ES4 prototyping. :-)