Author Topic: JavaScript Closures - Part 2  (Read 1944 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
JavaScript Closures - Part 2
« on: October 25, 2017, 04:59:23 PM »
To extend on the JavaScript closure concept, here's an example of how it can be used to build classes with encapsulation. This example has multiple public member functions, and uses the widely accepted syntax common to many languages for accessing class members. The private state of this object is captured in the closure of the factory function, which provides the encapsulation for non-public data (and methods, if desired).

Code: Javascript [Select]

/* Counter with reset */
function generateCounter() {
  var count = 0;
 
  function reset() {
    count = 0;
  }
 
  function next() {
    return count++;
  }
 
  /* Return an object with two public member functions, which both have implicit access to the same closure for private data */
  return {
    reset: reset,
    next: next
  };
}

var counter1 = generateCounter();
var counter2 = generateCounter();

console.log(counter1.next(), counter1.next(), counter1.next());  /* 0 1 2 */
console.log(counter2.next(), counter2.next(), counter2.next());  /* 0 1 2 */

console.log();

/* Reset counter 1 */
counter1.reset();
console.log(counter1.next(), counter1.next());  /* 0 1 */
console.log(counter2.next(), counter2.next());  /* 3 4 */



So yes, closures are sufficient to implement classes and objects in much the way we would expect to use them. I'm not sure I would recommend this way. There are others. Though it is common enough that you might see this from time to time in JavaScript libraries.

I've seen three main ways that people implement class/object behaviour.
  • Factory Functions - often combined with the use of closures to capture private state
  • Constructor Functions - used with new, and often combined with prototype methods
  • ES6/ES2015 class keyword - nice syntax, result is very similar to constructor functions

Looks like I have a few future topics to cover :D
« Last Edit: October 25, 2017, 05:01:18 PM by Hooman »

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Re: JavaScript Closures - Part 2
« Reply #1 on: October 27, 2017, 06:28:18 PM »
This is interesting. It actually looks really simple to represent a class as nested functions. It even looks pretty normal to read over. The only real difference is that you use the word function instead of class. I suppose you sacrifice inheritance and interfaces though?

I wonder what the performance difference is representing a class as a traditional class vs using a closure? I guess that is hard to determine since I think JavaScript actually represents its classes as closures under the hood? Not sure about that.

-Brett

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Re: JavaScript Closures - Part 2
« Reply #2 on: October 28, 2017, 09:59:08 AM »
Hmm, I don't think interfaces are particularly relevant for JavaScript. It's all duck-typed and dynamically dispatched. There are no safety/speed issues that would be affected.

Inheritance can still work. You can add or replace methods on the returned object, allowing it to be extended. If one factory function calls another, you can wrap an additional closure around new methods, providing for more private data. (Without new/replaced methods, there would be no reason to capture additional variables). One limitation, is not being able to directly access variables in the old closure.

Code: Javascript [Select]

/* Counter with reset */
function generateCounter() {
  var count = 0;
 
  function reset() {
    count = 0;
  }
 
  function next() {
    return count++;
  }
 
  /* Return an object with two public member functions, which both have implicit access to the same closure for private data */
  return {
    reset: reset,
    next: next
  };
}

function generateEvenCounter() {
  var obj = generateCounter();
  var oldNext = obj.next;

  obj.next = function newNext() {
    /*return count += 2;*/  /* No access to old closure, 'count' is not in scope here */

    var retVal = oldNext();
    oldNext();
    return retVal;
  }
 
  return obj;
}

var counter = generateEvenCounter();

console.log(counter.next(), counter.next(), counter.next());  /* 0 2 4 */

console.log();

counter.reset();
console.log(counter.next(), counter.next());  /* 0 2 */



Though again, there are Constructor Functions, and the new Class syntax. Probably a better way to express your intent, and I suspect will result in more efficient runtime performance.
« Last Edit: October 28, 2017, 10:02:33 AM by Hooman »