0

I have a class in javascript with a property (is_initialized) and a function (isInitialized).

Class definition :

function Test()
{
    this.is_initialized = { obj: { isInitialized: 'notyet' } };
    this.isInitialized = function( ref )
    {
        if ( !ref.obj )
        {
            console.log( 'now: ' + JSON.stringify( this.is_initialized ) );
/*line 9*/  this.is_initialized.obj.isInitialized = ref.isInitialized.toString();
            console.log( ref );
        }
        else {
            bluetoothle.isInitialized( this.isInitialized );
/*line 14*/ ref.obj = this.is_initialized.obj;
        }
    };
}

bluetoothle.isInitialized is a function in a cordova plugin (no knowledge of cordova is required for answering this question), it returns an object { isInitialized : true/false } and will pass the first if-statement whilst my call to isInitialized() will execute the else.

Question :

Why does this.is_initialized on line 9 create a new property inside the function isInitialized while this.is_initialized on line 14 uses the property is_initialized in Test()?

Shouldn't it both use the property inside Test() or use a (new) variable inside isInitialized()?

And if it 'just' behaves like this, what can i do to deal with it?

Code i ran :

var t = new Test();
var r = {obj:{isInitialized:'nope'}};
t.isInitialized(r);

// console.log( 'now: ' + JSON.stringify( this.is_initialized ) ); on line 8:
// now: {"obj":{"isInitialized":"false"}}

// console.log( ref ); on line 10:
// Object {isInitialized: false}

console.log(JSON.stringify(r));
// {"obj":{"isInitialized":"notyet"}}

console.log(JSON.stringify(t));
// {"is_initialized":{"obj":{"isInitialized":"notyet"}}}

What just happened is this:

  • i made a new instance of Test() and named it t.
  • i made an object with matching structure of is_initialized in Test() but with a different value.
  • i called the function with r as parameter.
  • code in the else executes.
  • asynchronous function with isInitialized as callback is called.
  • the function created a reference between the existing is_initialized and r.
  • the async function calls isInitialized and executes the code in the if.
  • it logs the current value of this.is_initialized on line 8, somehow it gets this.is_initialized after line 9 is executed.
  • line 9 executes, creating a new variable named is_initialized inside isInitialized() while i want it to set is_initialized in Test() and not create a new variable that dies when the function is done executing.
  • it logs the object that was put into this.is_initialized.obj.isInitialized.
  • i log r and see that it contains the initial value of Test.is_initialized.
  • i log t and see that is_initialized's value is still initial.

Info :

If you want to test it yourself to answer my question of the why? and the how do i deal with it? but need some code for bluetoothle.isInitialized just use this:

var bluetoothle = {isInitialized:function(){setTimeout(function(){func({isInitialized:false});},20);}};
// to execute:
bluetoothle.isInitialized(/*callbackfunction*/);

I would like to thank you for reading this long question.

x13
  • 2,179
  • 1
  • 11
  • 27
  • 1
    What makes you think it would create a new "variable"? – Bergi Nov 18 '15 at 11:26
  • @Bergi the console.log on line 8. it outputs a different value than `Test.is_initialized` and i can't find it anywhere after the function is done executing. – x13 Nov 18 '15 at 11:26
  • 2
    This seems to depend on the implementation of your `bluetoothle.isInitialized` function. If it is really asynchronous, as you say, then line 14 will be executed before line 9 of course. – Bergi Nov 18 '15 at 11:33
  • @Bergi the assignment on line 14 is by reference, so it doesn't matter when it changes – x13 Nov 18 '15 at 11:42
  • 2
    When you pass `this.isInitialized`, you're only passing the function, not the object. Therefore, when it invokes the function (inside the `setTimeout` callback in your test code), it doesn't know what the original object was, so it sets the value of `this` to the `window` object. As such, the async invocation is referencing the global object when it uses `this`. So this shouldn't work at all once you reach `this.is_initialized.obj.isInitialized = ` unless you defined a global variable called `is_initialized`. –  Nov 18 '15 at 11:58
  • 2
    Did you mean `bluetoothle.isInitialized( this.isInitialized.bind(this) );` and `var bluetoothle = {isInitialized:function(func) {…`? Otherwise your code doesn't execute without exceptions at all. – Bergi Nov 18 '15 at 11:59
  • 1
    And by the way, there are no variables in your code except `t` and `r`. The rest are object properties. This makes your question harder to understand, not to mention having `is_initialized` and `isInitialized`. –  Nov 18 '15 at 12:01
  • @squint i am aware that it passes the function, `bluetoothle.isInitialized` requires a callback as parameter. Thanks for your comment, it is making more sense to me now, – x13 Nov 18 '15 at 12:27
  • @Bergi i haven't looked into using `bind(this)`, but i did forgot the add `func` as a parameter. – x13 Nov 18 '15 at 12:28
  • 2
    Yes, I wasn't telling you that you were passing a function. I was telling you that you're passing *only* the function and not the object. Glad to hear it's making sense. –  Nov 18 '15 at 12:29

1 Answers1

0

You're in a function. Inside a function (unless it's declared as Test.prototype.isInitialized), the scoping rules for 'this' are different. This is one of the gotchas that ES6 aims to eliminate. (If you added "use strict"; at the top of the function most browsers would tell you this.)

Declare var self = this in Test and use self inside your interior function and you should get the result you want. @squint has pretty much already said this.

user1329482
  • 569
  • 3
  • 8
  • "*Inside a function the scoping rules for 'this' are different*"??? The scoping rules for `this` are quite consistent. Different from what? – Bergi Nov 18 '15 at 12:31
  • @user1329482 Bergi is right, the rules are the same, but when i passed the function ass callback it executed in the scope of `bluetoothle`, not `Test()`. Using `bluetoothle.isInitialized( this.isInitialized.bind(this) );` (as Bergi said) fixed it for me. @Bergi go ahead and make an answer to claim your rep and mark it for future visitors – x13 Nov 18 '15 at 12:34
  • @ThisNameBetterBeAvailable: I've already linked a canonical duplicate, no answer from me :-) If you still want me to get rep you're free to upvote on any of my other answers, but honestly I don't need it – Bergi Nov 18 '15 at 12:51