One of the core tenets of Object Oriented programming is encapsulation:
- state (properties) and behavior (methods) are bundled in a single object
- data is "hidden"
- access to state is provided only through predicatable methods
- getter methods return state values (or copies of state values if they are arrays/objects)
- setter methods update state values
Back in the day, to indicate a value in an object was "private" (and shouldn't be changed), we put a _
in front of the property name:
function makeVault(code) {
return {
_secretCode: code, // _ means that this is a "private value" and shouldn't be changeds
open: function(code) {
if (code === this._secretCode) {
console.log("access granted")
}
else {
console.log("access denied")
}
}
}
}
const myVault = makeVault(12345);
myVault.open(12345);
This idea of "privacy" is only an illusion... nothing stops me from modifying the private property:
myVault._secretCode = 54321;
console.log(myVault.open(12345)); // access granted!
Remember, Higher Order Functions are functions that...
Reveal the answer!
- Accept functions as arguments
- Return functions
Each time we create a function, we create a closure. A closure is the combination of a function bundled together (enclosed) with references to its surrounding state. In other words, a closure gives you access to an outer function's scope from an inner function.
To use a closure, we return an inner function that references variables in the outer function's scope.
function getRandomPrinter() { // outer function
// a closure is created around this variable
const randomValue = Math.random();
function printRandom() { // inner function
console.log(randomValue);
// this reference saves a reference to randomValue that would otherwise be lost when the function finishes
};
// the returned function will retain a reference to the closure variables
return printRandom;
}
const randomPrinter1 = getRandomPrinter();
Q: Try invoking the returned function multiple times. Create a second printer function using getRandomPrinter()
and invoke it. Observe what happens. How does the closure work to produce this result?
Answer
Once a printer function is returned from getRandomPrinter()
, the returned function will hold onto the random value generated by getRandomPrinter()
. That random value will NOT change throughout the lifetime of that returned function.
Each printer function returned from getRandomPrinter
will have its own unique random value.
When working with factory functions, if our object has methods, those methods count as "inner functions" that can access the factory function's closure.
We use this to create truly private variables.
Notice that the object returned does NOT have a count
property.
function makeCounter() {
// a closure is created "around" this variable
let count = 0;
// the returned object will retain a reference to count, even after it is returned from the function.
return {
getCount() {
return count;
},
increment() {
count++;
}
}
}
// each instance has its own count value
const counter1 = makeCounter();
const counter2 = makeCounter();
counter1.increment();
counter1.increment();
counter1.increment();
counter2.increment();
console.log(counter1.getCount()); // 3
console.log(counter2.getCount()); // 1
Create a factory function for an object that has
- A private (closure) variable
- A getter method (returns the current value of the private variable)
- A setter method (updates the value of the private variable).
Ideas:
- A
vault
object with a privatesecretCode
number - A
shoppingCart
object with a privateitems
array - A
user
object with a privatepassword
string