JS "this" question

Discussion and information relevant to creating special missions, new ships, skins etc.

Moderators: winston, another_commander

Post Reply
User avatar
Commander McLane
---- E L I T E ----
---- E L I T E ----
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

JS "this" question

Post 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?
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Re: JS "this" question

Post 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.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: JS "this" question

Post 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.
User avatar
Commander McLane
---- E L I T E ----
---- E L I T E ----
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

Re: JS "this" question

Post 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:
User avatar
Micha
Commodore
Commodore
Posts: 815
Joined: Tue Sep 02, 2008 2:01 pm
Location: London, UK
Contact:

Re: JS "this" question

Post 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.
The glass is twice as big as it needs to be.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: JS "this" question

Post 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.
User avatar
stevesims
Dangerous
Dangerous
Posts: 78
Joined: Wed Jun 23, 2004 4:07 am
Location: London, England

Re: JS "this" question

Post 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.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Re: JS "this" question

Post 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.
User avatar
stevesims
Dangerous
Dangerous
Posts: 78
Joined: Wed Jun 23, 2004 4:07 am
Location: London, England

Re: JS "this" question

Post 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.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: JS "this" question

Post 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. :-)
Post Reply