1

I'm using JQuery, latest version.

$(document).ready(function(){
    function ViewModel(){
        this.Impossible = "impossible!";
    }
    let vm = ViewModel();
});

$(window).on("beforeunload", function (e) {
    console.log("this: " + this.Impossible);
    console.log("window: " + window.Impossible);
    // return "Test";
});

https://jsfiddle.net/5wkdxvhq/1/

How is it possible that "impossible!" gets logged? Impossible is wrapped by at least 2 functions, it definitely shouldn't be a global variable, yet there it is.

Is this a behavior of JQuery or JavaScript?

Is this standard behavior that I can rely on? Or is it some quirk that won't work in other browsers/future Knockout updates or a bad design practice by me?

Saturnix
  • 8,648
  • 13
  • 50
  • 103
  • Possible duplicate of [How does the "this" keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Jared Smith Aug 15 '19 at 02:29
  • 1
    You probably want `let vm = new ViewModel();` instead. – Tyler Roper Aug 15 '19 at 02:30
  • 2
    When you call the function without `new` you get the global context. This is a pretty good argument for using the class syntax (your code would have thrown). – Jared Smith Aug 15 '19 at 02:31
  • This is why you should use arrow functions if you want a function to both be standalone, and have sane scoping/closure rules. If you had used an arrow function, "this" would be whatever it was outside of the function. – Andrew Koster Aug 15 '19 at 02:32
  • 1
    Not sure why this question got downvotes. It's a valid question, and a reasonable person would expect Javascript to work the way that it doesn't. It's not a bad question just because you're a Javascript expert and you've internalized the parts of it that are backward, deprecated scoping rules. – Andrew Koster Aug 15 '19 at 02:36

2 Answers2

2

Impossible isn't wrapped up by your functions declarative/lexical environments. It isn't local to your innermost function. If it was declared like this:

let Impossible = "impossible!"
//or
//var Impossible = "impossible!"

Then Impossible would have been local to that function and there wouldn't be any way to access it unless you use a closure.

What this does:

this.Impossible = "impossible!"

Is create a property definition on the this object. The value of this of course will change depending on the invocation of the surrounding function (ViewModel). In this case, you have called ViewModel() which means there's no object context and on scripts (non-strict mode) it will default to the global object (window).

Had you call it like this: new ViewModel() it would have created a new object and that would have been your this object which would have been assigned to your vm variable (and only accessible through it).

MinusFour
  • 11,617
  • 3
  • 23
  • 30
1

Firstly, $(document).ready(function (){}) apply global scope to the callback function function (){}. Try this, you will see console.log(this) is a Window object.

$(document).ready(function(){
    console.log(this);// this is window (global) object
});

Secondly, this is different when calling function and creating an object instance of a function, i.e let vm = ViewModel(); vs let vm = new ViewModel();.

function f1 () {
  function f2 () {
    console.log(this === window);
  }
  f2()
}
f1()

In your case, by creating instance of ViewModel, you will see the different values of window.Impossible

$(document).ready(function(){
    function ViewModel(){
        this.Impossible = "impossible!";
    }
    let vm = new ViewModel(); /// create instance instead of calling 
    console.log('vm:' + vm.Impossible);
    console.log("this: " + this.Impossible);
    console.log("window: " + window.Impossible);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Ponleu
  • 895
  • 7
  • 19