links: JS MOC
Limiting Scope Exposure
So far we learned how scope works, now let’s learn about the designs and patterns while creating scope
Least Exposure
Software Engineering articulates a fundamental discipline, typically applied to software security, called “The principle of least privilege (POLP)”, and the variation of this principle that applies to JS is the “Principle of least exposure (POLE)”
POLP expresses a defensive posture to software architecture: components of the system should be designed to function with least privilege, least access, least exposure. If each piece in the system is connected with minimum necessary capabilities, the overall system will be secure
if POLP focuses on system design POLE focuses on lower level; we will apply it to how scope interact with each other.
How do we minimize the exposure of scope by following POLE?
Simple by registering variables in each scope.
Consider what happens if you place all your variables in global scope?
When variables used by one part of the program are exposed to another part of the program, via scope these three hazards will happen
-
Naming Collision: If you use common variable/function name in two different programs, but the identifier comes from one scope, then naming collision happens, and it’s very likely that bugs will occur as one part of the program doesn’t uses the variable/function in a way the other program expects
-
Unexpected Behavior: If you expose variables/functions whose usage is private to a piece of program, it allows developers to use them in a way it’s not intended, which can violate expected behavior and cause bugs
For example, if your program only contains array of numbers, but someone else’s code modifies and accesses program to include strings and boolean, your code then misbehave in unintended ways
Worse, exposure of private details invites those with mal-intent to do things with your software that shouldn’t be allowed
-
Unintended Dependency: if you expose variables/functions unnecessarily, it invites other developers to use and depend on those private pieces. While it doesn’t break the program today, it will create the refactoring hazard in future, because you cannot easily refactor without breaking other parts of the software that you don’t control.
POLE, as applied to variable/function scoping, essentially says, default to exposing the bare minimum necessary. keep everything else as private as possible. Declare variables in as small and deeply nested as possible, rather than placing everything at global.
Hiding in plain (function) scope
Consider an example where a function is responsible for calculating factorial. If you can trade off memory for speed you can add cache.
var cache = {}
function factorial(x) {
if(x < 2) return 1
if(!(x in cache)) {
cache[x] = x * factorial(x - 1)
}
return cache[x]
}
factorial(6) // 720
cache
// {
// "2": 2,
// "3": 6,
// "4": 24,
// "5": 120,
// "6": 720
// }
factorial(7) // 5040One problem with this code is the cache variable is in global scope, where as it needs to be encapsulated and private to the factorial function.
This can be fixed by adding one more function
function hideTheCache() {
// middle scope where we hide the cache
var cache = {}
return factorial
function factorial(x) {
if(x < 2) return 1
if(!(x in cache)) {
cache[x] = x * factorial(x - 1)
}
return cache[x]
}
}
var factorial = hideTheCache()
factorial(6) // 720This is good but we have to call this extra function to initialize the cache. Also we might have naming collision
We can use another JS feature called IIFE (Immediately invoked function expression)
var factorial = (function hideTheCache() {
var cache = {}
function factorial(x) {
if(x < 2) return 1
if(!(x in cache)) {
cache[x] = x * factorial(x - 1)
}
return cache[x]
}
return factorial
})()
factorial(6) // 720Immediately invoked function expression
An IIFE is useful when we want to create a scope to hide variables/functions.
Syntax for IIFE
// outer scope
(function() {
// inner hidden scope
// ...
})()
// more outer scopeFunction Boundaries
Using an IIFE to define a scope can have unintended consequences, depending on the code around it. Because the IIFE is a full function, the function boundary alters the behaviour of certain statements/constructs
For example, return statement in some piece of code would change it’s meaning if IIFE is wrapped around it, because now the return would refer to the IIFE’s function. Non-arrow function IIFE’s can change the binding of this keyword. And statements like break and continue won’t operate on IIFE boundary to control an outer loop or block.
So, if the code you need to wrap a scope around has return, this, break and continue in it, IIFE probably won’t be the best approach. In that case you might need to create the scope with the block instead of a function.
Scoping with Blocks
Let’s now consider using let declarations with nested blocks. In general, any {..} curly brace pair which is a statement will act as a block, but not necessarily as a scope.
A block only becomes a scope if necessary, to contain its block-scoped declarations (i.e, let or const). Consider
{
// Not necessarily a scope yet
// ..
// Now the block needs to be a scope
let thisIsNowAScope = true
for(let i = 0; i < 5; i++) {
// this is also a scope activated each iteration
if(i % 2 == 0) {
// this is just a block not a scope
console.log(i)
}
}
}
// 0 2 4Not all { .. } curly-brace pairs create block (and thus are eligible to become scope)
- Object literals use
{ .. }curly brace pairs to delimit their key-value lists, but such object values are not scopes classuses{ .. }curly-braces around it’s body definition, but this is not a block or scope- A
functionuses{ .. }curly-braces around it’s body, but this is not technically a block — it’s a single statement for the function body. it is, however, a (function) scope
tags: javascript , ydkjs