The scope of a method is the set of variables that are available for use within the method. The scope of a function includes:
- the function's arguments;
- any local variables declared inside the function;
- any variables that were already declared when the function was defined.
Consider this example:
function sayHelloNTimes(name, n) {
function greet() {
console.log( `Hi, ${name}!`);
}
for (let i = 0; i < n; i++) {
greet();
}
}
sayHelloNTimes("Bob", 3); // logs 'Hi, Bob!' x3
sayHelloNTimes("Sally", 6); // logs 'Hi, Sally!' x6In the example above, the variable name is referenced by greet, even though it was never declared within greet. This is possible because a nested function's scope includes variables declared in the scope where the function was nested.
Functions such as greet that use (ie. capture) such variables (ie. free variables) are called closures.
Free variables can be modified by closures. Consider this function:
function sum(nums) {
let count = 0;
function addNum(num) {
count += num;
}
for (let i = 0; i < nums.length; i++){
addNum(nums[i]);
}
return count;
}
sum([1, 3, 5]) // => 9We can use closures to pass down arguments to helper functions without explicitly listing them as arguments.
function isPalindrome(string) {
function reverse() {
return string.split('').reverse().join('');
}
return string === reverse();
}Another major use of closures is to create private states. For example:
function Counter() {
let count = 1;
return () => count++;
}
let counter = new Counter();
console.log(counter()); // => 1
console.log(counter()); // => 2
counter.count; // undefinedBy closing over (or capturing) the count variable, each Counter function has private, mutable state that cannot be accessed externally.
Compare that implementation against this one:
function Counter () {
this._count = 0;
}
Counter.prototype.fire = function () {
this._count += 1;
return this._count;
}
let counter = new Counter();
counter.fire(); // 1
counter.fire(); // 2
counter._count // 2
counter._count = 0 // 0 (this works);One advantage of the closure way is that the count is truly
private. In the first example, there is no way any method beside the closure itself can access the count state. In the second example, a foolish user might set counter._count inadvertently.
JavaScript has global scope, represented by the 'window' object in the browser and the 'global' object in Node.js. Adding attributes to these objects makes them available throughout a program.
function theBest() {
window.realMVP = 'you';
}
theBest(); // initializes realMVP on the global scope
window.realMVP; // 'you'
function whoDaBest() {
return realMVP; // 'you'
}
whoDaBest(); // 'you'While useful on occasion, global variables are usually best avoided, as they give too much code access to their values, increasing the likelihood of bugs.
A common mistake new JS developers commit is to unintentionally create
global variables. This happens if you declare a variable without the var, let, or const keywords anywhere in your code, and can lead to strange behavior. Consider:
window.local; // undefined
function subroutine(){
local = 'oops';
}
subroutine();
window.local // 'oops';Thankfully, modern JS runtimes support strict mode, which prohibits variable declaration without var, let, or const.
"use strict";
window.local; // undefined
function subRoutine(){
local = 'oops';
}
subRoutine(); // ReferenceError: 'local' is not definedNote: "use strict" does not work in the Node CLI or the Dev Tools console.
Read these!
- JavaScript Closures with Ease
- SO: How Do JS Closures Work (the first answer)