user2731105
user2731105

Reputation: 53

JavaScript scope and closure: value property does not return incremented value

Confused about how increment can change value and that changed value is accessible to getValue, but not to a property of the same object.

Does this have to do with the fact that increment and getValue methods are inner functions of the anonymous function that value is defined in?

var myObject = function() {
    var value = 0;

    return {
        increment: function(inc){
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function(){
            return value;
        },
        value: value,
    };
}();

console.log(myObject.getValue()); // 0
myObject.increment(2);
console.log(myObject.getValue()); // 2
console.log(myObject.value); // 0
myObject.increment(2); 
console.log(myObject.getValue()); // 4

Upvotes: 0

Views: 381

Answers (2)

Quentin
Quentin

Reputation: 943801

var foo creates a variable that is scoped to the function it is declared with in. It can be accessed as foo anywhere inside that function or another function declared inside the first function.

myObject.foo creates a property that is attached to the object. It can be accessed as ???.foo where ??? is a reference to the object that myObject is a reference to. You can do this anywhere you can find a reference to the object.

Properties are not variables. Variables are not properties. (The exception is that global variables are properties of the global (window in a browser) object.


When you create the object you said:

value: value,

But that copies the then current value of the value variable to the value property.

That is a number, which isn't a reference.

When you update the value variable, the value property is unchanged.

Upvotes: 3

Patrick Roberts
Patrick Roberts

Reputation: 51916

Other answers like @Quentin's have explained what the issue is pretty thoroughly. Here's a proper way by minimally modifying your current approach to correct the problem though:

var myObject = function() {
  return {
    increment: function(inc) {
      this.value += typeof inc === 'number' ? inc : 1;
    },
    getValue: function() {
      return this.value;
    },
    value: 0
  };
}();

console.log(myObject.getValue()); // 0
myObject.increment(2);
console.log(myObject.getValue()); // 2
console.log(myObject.value); // 2
myObject.increment(2);
console.log(myObject.getValue()); // 4
console.log(myObject.value); // 4

However, this functionality would be a great example to make use of an ES6 class:

class Counter {
  constructor (value = 0) {
    this.value = value
  }
  
  increment (amount = 1) {
    this.value += amount
  }
  
  getValue () {
    return this.value
  }
}

let myObject = new Counter()

console.log(myObject.getValue()) // 0

myObject.increment(2)

console.log(myObject.getValue()) // 2
console.log(myObject.value) // 2

myObject.increment(2)

console.log(myObject.getValue()) // 4
console.log(myObject.value) // 4

As was pointed out in comments, the closure and getValue() seem rather pointless. If you want to allow value to access a scoped variable, and disable modifying it, you could implement it this way:

var myObject = function() {
  var value = 0;

  return {
    increment: function(inc) {
      value += typeof inc === 'number' ? inc : 1;
    },
    getValue: function() {
      return value;
    },
    get value() {
      return value;
    },
    // disable setting it without throwing
    set value(newValue) {
      return value;
    }
  };
}();

console.log(myObject.getValue()); // 0

myObject.increment(2);

console.log(myObject.getValue()); // 2
console.log(myObject.value); // 2

// won't work
myObject.value = 4

// still 2
console.log(myObject.getValue()); // 2
console.log(myObject.value); // 2

Upvotes: 2

Related Questions