Maverick
Maverick

Reputation: 2016

Clarification of JavaScript variable scope

I already know how to make this code work, but my question is more about why does it work like this, as well as am I doing stuff right.

The simplest example I can make to showcase my issue is this :

Lets say I have a function that increments the value of an input field by 10 on the press of a button.

var scopeTest = {

    parseValue : function( element, value ) {
        value = parseInt( element.val(), 10 );
        //Why does this not return the value?
        return value;
    },


    incrementValue : function( element, button, value ) {
        button.on('mousedown', function (e) {
            //Execute the parseValue function and get the value
            scopeTest.parseValue( element, value ); 
            //Use the parsed value      
            element.val( value + 10 );    
            e.preventDefault(); 
        });               
    },


    init : function () { 
        var element = $('#test-input'),
            button = $('#test-button'),
            value = ''; 

        this.incrementValue( element, button, value );
    }

};

scopeTest.init();

The above code doesnt work because the parseValue method doesn't properly return the value var when executed inside the incrementValue method.

To solve it apparently I have to set the scopeTest.parseValue( element, value ); parameter to the value variable like this:

value = scopeTest.parseValue( element, value );

Than the code works.

But my question is why? Why is this extra variable assignment step necessary, why the return statement is not enough? Also I am doing everything right with my functions/methods, or is this just the way JavaScript works?

Working example here => http://jsfiddle.net/Husar/zfh9Q/11/

Upvotes: 2

Views: 203

Answers (5)

Norbert Hartl
Norbert Hartl

Reputation: 10841

This is mostly a scope issue. The pass-by-* issue is strange to discuss because the sender variable and the called functions variable have the same name. I'll try anyway.

A variable has a scope in which it is visible. You can see it as a place to store something in. This scope is defined by the location of your function. Meaning where it is in your source code (in the global scope or inside a function scope). It is defined when you write the source code not how you call functions afterwards.

Scopes can nest. In your example there are four scopes. The global scope and each function has a scope. The scopes of your functions all have the global scope as a parent scope. Parent scope means that whenever you try to access a name/variable it is searched first in the function scope and if it isn't found the search proceeds to the parent scope until the name/variable is found or the global scope has been reached (in that case you get an error that it can't be found).

It is allowed to define the same name multiple times. I think that is the source of your confusion. The name "value" for your eyes is always the same but it exists three times in your script. Each function has defined it: parseValue and incrementValue as parameter and init as local var. This effect is called shadowing. It means that all variables with name 'value' are always there but if you lookup the name one is found earlier thus making the other invisible/shadowed.

In this case "value" is treated similar in all three functions because the scope of a local var and a parameter is the same. It means that as soon as you enter one of the methods you enter the function scope. By entering the scope the name "value" is added to the scope chain and will be found first while executing the function. And the opposite is true. If the function scope is left the "value" is removed from the scope chain and gets invisible and discarded.

It is very confusing here because you call a function that takes a parameter "value" with something that has the name "value" and still they mean different things. Being different there is a need to pass the value from one "value" to the other. What happens is that the value of the outer "value" is copied to the inner "value". That what is meant with pass-by-value. The value being copied can be a reference to an object which is what most people make believe it is pass-by-reference. I'm sorry if that sounds confusing but there is too much value naming in here.

The value is copied from the outer function to the called function and lives therefor only inside the called function. If the function ends every change you did to it will be discarded. The only possibility is the return your "side effect". It means your work will be copied back to a variable shortly before the function gets discarded

To other alternative is indeed leaving of parameters and work with the scope chain, e.g. the global scope. But I strongly advize you not to do that. It seems to be easy to use but it produces a lot of subtle errors which will make your life much harder. The best thing to do is to make sure variables have the most narrow scope (where they are used) and pass the values per function parameters and return values.

Upvotes: 1

vol7ron
vol7ron

Reputation: 42109

As others have said it's a pass-by-ref vs pass-by-val.

Given: function foo (){return 3+10;} foo();
What happens? The operation is performed, but you're not setting that value anywhere.

Whereas: result = foo();
The operation performs but you've stored that value for future use.


It is slightly a matter of scope

  • var param = 0;  
    function foo( param ) { 
       param = 1; 
    } 
    foo(param);
    console.log(param);                  // still retains value of 0
    

Why?

There is a param that is global, but inside the function the name of the argument is called param, so the function isn't going to use the global. Instead param only applies the local instance (this.param). Which, is completely different if you did this:

  • var param = 0;  
    function foo() {                     // notice no variable 
       param = 1;                        // references global
    } 
    foo(param);
    console.log(param);                  // new value of 1
    

Here there is no local variable called param, so it uses the global.

Upvotes: 1

DigitalRoss
DigitalRoss

Reputation: 146073

Because the value parameter to parseValue is just a reference. Yes, you can change the object, because you have a reference, but if you assign to the reference it now points at a different object.

The original version is unchanged. Yes, the return was "enough", but you saved the new object in a variable with a lifetime that ended at the next line of code.

People say that JavaScript passes objects by reference, but taking this too literally can be confusing. All object handles in JavaScript are references. This reference is not itself passed by reference, that is, you don't get a double-indirect pointer. So, you can change the object itself through a formal parameter but you cannot change the call site's reference itself.

Upvotes: 2

sarwar026
sarwar026

Reputation: 3821

You may have a look at it.

http://snook.ca/archives/javascript/javascript_pass

Upvotes: 0

user578895
user578895

Reputation:

This isn't a scoping issue, it's a confusion between pass-by-reference and pass-by-value.

In JavaScript, all numbers are passed by value, meaning this:

var value = 10;
scopeTest.parseValue( element, value );
// value still == 10

Objects, and arrays are passed by reference, meaning:

function foo( obj ){
    obj.val = 20;
}

var value = { val: 10 }
foo( value );
// value.val == 20;

Upvotes: 1

Related Questions