- Learning TypeScript 2.x
- Remo H. Jansen
- 981字
- 2025-04-04 17:02:05
Function scope
Low-level languages, such as C, have low-level memory management features. In programming languages with a higher level of abstraction, such as TypeScript, values are allocated when variables are created and automatically cleared from memory when they are not used anymore. The process that cleans the memory is known as garbage collection and is performed by the JavaScript runtime garbage collector.
The garbage collector does a great job, but it is a mistake to assume that it will always prevent us from facing a memory leak. The garbage collector will clear a variable from the memory whenever the variable is out of the scope. It is important to understand how the TypeScript scope works, so we will now look at the life cycle of the variables.
Some programming languages use the structure of the program source code to determine what variables we are referring to (lexical scoping), while others use the runtime state of the program stack to determine what variable we are referring to (dynamic scoping). The majority of modern programming languages use lexical scoping (including TypeScript). Lexical scoping tends to be dramatically easier to understand for both humans and analysis tools than dynamic scoping.
While in most lexical-scoped programming languages, variables are scoped to a block (a section of code delimited by curly braces {}), in TypeScript (and JavaScript), variables are scoped to a function, as demonstrated by the following code snippet:
function foo(): void { if (true) { var bar: number = 0; } console.log(bar); } foo(); // 0
The preceding function named foo contains an if structure. We have declared a numeric variable named bar inside the if structure, and later, we have attempted to show the value of the bar variable using the log function.
We might think that the preceding code sample would throw an error in the fifth line because the var variable should be out of the scope when the log function is invoked. However, if we invoke the foo function, the log function will be able to display the bar variable without errors because all the variables inside a function will be in the scope of the entire function body, even if they are inside another block of code (except a function block).
This might seem confusing, but it is easy to understand once we know that at runtime, all the variable declarations are moved to the top of a function before the function is executed. This behavior is known as hoisting.
Before the preceding code snippet is executed, the runtime will move the declaration of the bar variable to the top of our function:
function foo() { var bar; if (true) { bar = 0; } console.log(bar); } foo(); // 0
This explains why it is possible to use a variable before it is declared. Let's look at an example:
function foo(): void { bar = 0; var bar: number; console.log(bar); } foo(); // 0
In the preceding code snippet, we have declared a foo function, and in its body, we have assigned the value 0 to a variable named bar. At this point, the variable has not been declared. In the second line, we are declaring the variable bar and its type. In the last line, we are displaying the value of bar using the alert function.
Because declaring a variable anywhere inside a function (except another function) is equivalent to declaring it at the top of the function, the foo function is transformed into the following at runtime:
function foo(): void { var bar: number; bar = 0; console.log(bar); } foo(); // 0
Because developers with a background in programming languages with block scope, such as Java or C#, are not used to the function scope, it is one of the most criticized characteristics of JavaScript. The people in charge of the development of the ECMAScript 6 specification are aware of this, and as a result, they have introduced the keywords let and const.
The let keyword allows us to set the scope of a variable to a block (if, while, for, and so on) rather than a function. We can update the first example in this section to showcase how let works:
function foo(): void { if (true) { let bar: number = 0; bar = 1; } console.log(bar); // Error }
The bar variable is now declared using the let keyword, and as a result, it is only accessible inside the if block. The variable is not hoisted to the top of the foo function and cannot be accessed by the alert function outside the if statement.
While variables defined with const follow the same scope rules as variables declared with let, they can't be reassigned:
function foo(): void { if (true) { const bar: number = 0; bar = 1; // Error } alert(bar); // Error }
If we attempt to compile the preceding code snippet, we will get an error because the bar variable is not accessible outside the if statement (just like when we used the let keyword), and a new error will occur when we try to assign a new value to the bar variable. The second error occurs because it is not possible to assign a new value to a constant variable once the variable has already been initialized.