• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 201
  • Last Modified:

JavaScript scope question

My question is based on the following code snippet:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
    $(function() {
        function Test() {

            function innerTest() {
                sayHello("Saying hello from innerTest!");
            }
            
            // I put a 'this' on sayHello() to make it a public method outside of this constructor
            // function, without it, I won't be able to access it outside. But by using 'this', 
            // it no longer becomes accessible to innerTest() above. If take out the 'this', 
            // it will be accessible.
            this.sayHello = function(s) {
                console.log(s);
            }
            
            innerTest();
        }
        
        var t = new Test();
        
        t.sayHello("I'm the sayHello() function.");
        
    });

</script>
<style>
</style>
</head>
<body>    
</body>
</html>

Open in new window


I guess what is confusing me is that I always thought that the sayHello() and this.sayHello() would still be identical in scope, i.e, function scope. Apparently not. So can someone differentiate to me the scope differences between this.sayHello() and simply sayHello()?
0
elepil
Asked:
elepil
2 Solutions
 
Bob LearnedCommented:
I find "this" in JavaScript, to be confusing.

this Operator
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.

I would think that you would need "this" in the call:

this.sayHello("Saying hello from innerTest!");

Open in new window

0
 
elepilAuthor Commented:
Bob Learned, thanks for responding.

If you tried to run the code, you will know using this.sayHello() inside InnerTest() would generate an error "Uncaught TypeError: this.sayHello is not a function".

Besides, what I was really after was an explanation why this is happening the way it is.
0
 
Slick812Commented:
greetings  elepil, , OK, the way that javascript organizes  the levels of "Scope", are based on the containers, that are "around" the "variables" , which can be several layers ( containers ) deep. Unlike most any other code language, in javascript all functions ARE VARIABLES, (changeable, not fixed-permanent),  and use variable names to access that function, so if you have a function, to can access it by variable name, and completely change it -
<script>
function showString(str) {
alert("string to show is - "+str);
}
// you can just change the old function to new operations
showString = function(s, n) {
n *= 2;
alert("string and number to show is - "+s+", number is - "+n);
}
</script>

 So, if you do a function, as you do, with a function inside of it, and use the "this" reference, then you seem to think that inner functions are the same as a "this" reference, , But in javascript, this is not true, as by nessesity, JS needs to limit the scope and has any thing on the "this" reference, be scope-wise separate, from any internal function variables, like -

function junk(init) {
var limit = init;
this.limit = init+5;

this showLimit = function() {
  alert("two different Limits, inter L = "+limit+", this L = "+this.limit);
  }
}

although there are Two separate and not related Limit, in this function, the Scope of each is different. You need to explicitly declare any "this" variable, with that operator, unlike many other languages.
0
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

 
elepilAuthor Commented:
Slick812, thank you for responding.

I'm not sure I made my question clear, so let me restate, and I hope you will have the patience to copy/paste my very brief code snippet and try out what I'm going to say so you will know exactly what I'm talking about. Look at this excerpt of code:

function innerTest() {
                // This will work, innerTest() will be able to see the sayHello function definition
                sayHello("Saying hello from innerTest!");
            }
            
            sayHello = function(s) {
                console.log(s);
            }

Open in new window


Now look at this:

function innerTest() {
                // We put a 'this' in front of sayHello() definition, now this WON'T WORK anymore.
                sayHello("Saying hello from innerTest!");
            }
            
            // Because I added 'this', innerTest() above can't find it anymore
            this.sayHello = function(s) {
                console.log(s);
            }

Open in new window


The way I see it, there are only THREE scopes we are talking about here:

Global Scope (the scope of the constructor function Test())
        Function Scope (the scope of sayHello()=function.... )
            Inner function scope (the scope of innerTest()) 

Open in new window


It puzzles me why innerTest() could see sayHello() when it didn't have 'this', and then would totally fail to see it when it has a 'this'. There are only 3 levels of scopes that I can see in my example, but it is behaving as if there is a fourth scope that I'm not seeing.

So Slick812, do you see what I'm asking now?
0
 
Steve BinkCommented:
You are conflating two different scope concepts.  Here's some reading for you:

http://javascript.crockford.com/private.html
http://philipwalton.com/articles/implementing-private-and-protected-members-in-javascript/

The basics are that you are defining a "private" function inside your prototype.  In OOP terms, you are creating a static method - a function without the first-glance-intuition of "this".  Your code follows this pattern:
function a() {
  function b() {}
  this.c = function() {}
}

Open in new window


In that example, a() is a function, which creates a scope, and limits the available scope to the "big global", and anything in a()'s top-level scope - the "small global".  Anything created inside a()'s scope will be placed in the "small global", and also have access to everything in the "small global".  Creating a() also creates a reference to "this" pointing at the definition of a().  "this", in the context of a(), is the function object a().  

When you create b(), you are creating ANOTHER scope, and another reference to "this".  Inside b(), "this" points to the function object b().  

When you create this.c(), you are explicitly setting property c of "this".  In the context in which it is being set, "this" points to the function object a(), so it becomes a().c().  An additional point here is that c(), when called, will be given a binding telling it that "this" points to its owning a() object.  That extra step does not happen when b() is called, since b() is just one more standard function definition...its "this" will point to itself.

There are a couple ways to work around this problem.  

First and foremost, if b() needs access to the a() object, then perhaps it should not be created inside a() as a standard function (pronounced: private member).  Just as in OOP languages, where static methods are designed to be utility methods without access to the *instanced object*, so should private members be in Javascript.  The first solution is to refactor your code until b() either no longer needs access to a(), or it is created as a privileged/public member (i.e., this.b = function(){} ).  I believe this is the correct answer.

Another alternative is to leverage b()'s ability to see the inner scope of a() by creating a reference for it to use.  This strategy is commonly used in loop closures:
function MyObject() {
  // right now, this points to where you think it does...the called MyObject instance
  // let's save it into the private scope
  var $this = this;
  // now some variables.  first, a private one
  var a = [1, 2, 3, 4, 5];
  // then a public one
  this.b = array();
  a.forEach(function(v,k) {
    // right here, "this" does *not* point to MyObject, but rather the closure implementing the forEach
    // but, since it was created inside MyObject's scope, it should have access to everything within it:
    $this.b[v] = true;
  });
}

Open in new window


I'm not convinced the second option is correct in the context of your basic example, but I'd have to know more about it in order to come up with an argument.  Technically, it will work, though it seems messy organizationally.

Finally, you could call b() by using the apply, bind, or call functions.  Those functions can create new function, or call existing ones, with a "this" reference pointing to an arbitrary object.  This is very handy for manipulating scopes.
0
 
elepilAuthor Commented:
Steve Bink, your explanation and the links you provided gave me an epiphany on how JavaScript handles scoping.

I now realize private functions can only access private properties and private methods. They CANNOT access public properties and public methods by default. In order for private functions to access public properties, a closure has to be created to bridge the gap between private and public, as in your example when you created var $this = this.

Thanks for your help.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now