Search code examples
javascriptfunctionclosuresfunction-definitionfunction-expression

What is the difference between 'let' and 'this' in JavaScript?


I'm learning JavaScript, coming from Ruby, have done some stuff in C as well. One example in my reading is:

function letCounter() {
  let counter = 0;

  return () => ++counter;
}

and is compared to

function thisCounter() {
  this._count = 0;
}

thisCounter.prototype.fire = function() {
  this._count ++;
  return this._count;
}

In the first example, the count is NOT accessible on an instance of letCounter:

let let_counter_ins = letCounter();
let_counter_ins.counter // <- undefined

while in the second, count, ~I think~, is an attribute of all instances of the function itself?

let this_counter_ins = new thisCounter();
this_counter_ins.count  // <- 0

Seems like a function in JavaScript can have 'state' (calls to console.log(let_counter_ins()) will continuously increment a counter, as opposed to it starting over at 0). But also this 'state', set with let is somehow different than a 'state' set with this? Seems like instances of both letCounter and thisCounter are keeping track of some counter, but how it is accessible is different? Trying to understand what the difference is between setting function attributes with this vs let and why one of them is available externally.


Solution

  • In the first code, a local variable counter is initialized, and the returned function closes over the variable (in a "closure"). The value only exists in the local scope of the function - in a Lexical Environment that, once the function finishes, is only referenceable via the () => ++counter. There's no way to directly observe the contents of a closure, other than by the methods that the closure may (or may not) have returned or use that deliberately expose its contents.

    Every call of letCounter will result in a new binding for counter being created, a new Lexical Environment for it, and a new closure, so separate calls of letCounter will result in separate counts being kept track of.

    In the second code, with this, counter is not a local variable, but a normal property of the instance object. Object properties are completely public (with the exception of the extremely new # private member syntax I won't get into), so anything, including thisCounter.prototype.fire and external code can access this.counter (where this references the instance) or this_counter_ins.count (where this_counter_ins references the instance) and get the current value on the object.