文章目录
- 一、作用域介绍
-
- [1、全局作用域(Global Scope)](#1、全局作用域(Global Scope))
- [2、函数作用域(Function Scope)](#2、函数作用域(Function Scope))
- [3、块级作用域(Block Scope)](#3、块级作用域(Block Scope))
- [4、模块作用域(Module Scope)](#4、模块作用域(Module Scope))
- [5、类作用域(Class Scope)](#5、类作用域(Class Scope))
- [6、词法作用域(Lexical Scope,也称为静态作用域)](#6、词法作用域(Lexical Scope,也称为静态作用域))
- 二、闭包
一、作用域介绍
1、全局作用域(Global Scope)
全局作用域是程序中最外层的作用域,在全局作用域中声明的标识符(变量、函数、类等)在整个程序的任何地方都可访问。
- 特点:
- 声明在函数、类、模块之外的标识符属于全局作用域。
JavaScript
// 全局变量
const globalVar: string = "全局变量";
// 全局函数
function globalFunc(): void {
console.log(globalVar); // 可访问全局变量
}
class GlobalClass {
// 全局类
}
// 任何地方都可访问
console.log(globalVar);
globalFunc();
const obj = new GlobalClass();
2、函数作用域(Function Scope)
函数作用域指在函数内部声明的标识符,仅在该函数内部可访问,外部无法直接访问。
- 特点:
- 函数的参数、内部变量、内部函数均属于函数作用域。
- 函数作用域内的标识符会 "遮蔽"(shadow)外部作用域的同名标识符。
TypeScript
const outerVar: string = "外部变量";
function funcScope() {
// 函数内部变量(仅在funcScope内可访问)
const innerVar: number = 10;
// 函数参数(属于函数作用域)
console.log(innerVar); // 正确:10
// 遮蔽外部同名变量
const outerVar: string = "内部变量";
console.log(outerVar); // 正确:"内部变量"
}
funcScope();
console.log(innerVar); // 错误:innerVar未定义(函数外部不可访问)
3、块级作用域(Block Scope)
块级作用域由{}
(大括号)界定,如if
、for
、while
、switch
语句或单独的代码块。let
和const
声明的变量属于块级作用域,var
声明的变量不具备块级作用域(仅函数作用域)。
TypeScript
if (true) {
var name = "Bob"; // 函数作用域(此处无函数,实际为全局变量)
console.log(name); // 正常输出:Bob
}
console.log(name); // 正常输出:Bob(块外可访问,因 var 无块级作用域)
- 特点:
- 块级作用域内的变量仅在当前块及嵌套块中可访问。
- 支持 "块级遮蔽",即内部块的变量可遮蔽外部块的同名变量。
TypeScript
// 块级作用域示例(if语句块)
if (true) {
let blockVar: string = "块内变量"; // let声明的块级变量
const blockConst: number = 20; // const声明的块级变量
console.log(blockVar); // 正确:"块内变量"
}
console.log(blockVar); // 错误:blockVar未定义(块外部不可访问)
// var声明无块级作用域(仅函数作用域)
for (var i = 0; i < 3; i++) {}
console.log(i); // 正确:3(var变量泄露到外部作用域)
// 块级遮蔽
const x: number = 100;
{
const x: number = 200; // 遮蔽外部x
console.log(x); // 正确:200
}
console.log(x); // 正确:100
4、模块作用域(Module Scope)
在 TS 中,一个文件默认就是一个模块(Module),模块内部的顶层声明(变量、函数、类等)属于模块作用域,仅在模块内部可访问,外部需通过export
导出后才能访问。
- 特点:
- 模块作用域隔离了不同文件的代码,避免全局污染。
- 模块内的声明需显式
export
才能被其他模块import
。
JavaScript
// moduleA.ts(模块文件)
const moduleVar: string = "模块内变量"; // 模块作用域变量
export function moduleFunc(): void {
console.log(moduleVar); // 模块内可访问
}
// 其他文件中使用
import { moduleFunc } from './moduleA';
moduleFunc(); // 正确:可访问导出的函数
console.log(moduleVar); // 错误:moduleVar未导出,不可访问
5、类作用域(Class Scope)
类作用域指类内部的成员(属性、方法)的可访问范围,受访问修饰符(public
、private
、protected
)控制。
- 特点:
public
:默认修饰符,类内部、实例、子类均可访问。private
:仅类内部可访问,实例和子类不可访问。protected
:类内部和子类可访问,实例不可访问。
TypeScript
class ClassScope {
public publicProp: string = "公共属性"; // 类作用域(public)
private privateProp: number = 100; // 类作用域(private)
protected protectedProp: boolean = true; // 类作用域(protected)
public getPrivate(): number {
return this.privateProp; // 类内部可访问private成员
}
}
class SubClass extends ClassScope {
getProtected(): boolean {
return this.protectedProp; // 子类可访问protected成员
}
}
const instance = new ClassScope();
console.log(instance.publicProp); // 正确:public成员可访问
console.log(instance.privateProp); // 错误:private成员不可访问(实例)
console.log(instance.protectedProp); // 错误:protected成员不可访问(实例)
6、词法作用域(Lexical Scope,也称为静态作用域)
是指变量的可访问范围由其声明时的位置决定,而非运行时的执行位置。这是 JavaScript/TypeScript 等语言遵循的作用域规则,也是理解变量访问权限的核心概念。
-
特点:
-
静态确定性:作用域在代码 "定义阶段" 就已固定,与函数 / 变量的 "调用位置" 无关。
TypeScriptconst a = 10; function outer() { const b = 20; function inner() { console.log(b); // inner定义时处于outer作用域内,因此能访问b } return inner; } const func = outer(); func(); // 输出20(即使在全局调用,仍访问outer中的b)
-
嵌套作用域链:作用域按代码的嵌套关系形成 "由内向外" 的链条(作用域链)。访问变量时,引擎会先在当前作用域查找,若未找到则逐层向上搜索外层作用域,直到全局作用域。
JavaScriptconst global = "全局"; function f1() { const f1Var = "f1"; function f2() { const f2Var = "f2"; console.log(f2Var); // 当前作用域 console.log(f1Var); // 外层f1作用域 console.log(global); // 全局作用域 } f2(); } f1();
-
二、闭包
在 TypeScript 中,闭包(Closure) 是一个核心概念,它允许函数访问并记住其词法****作用域内的变量,即使该函数在其原始作用域之外执行。
1、定义
闭包是指有权访问另一个函数作用域中变量的函数。即使该外部函数已执行完毕,其作用域内的变量也不会被销毁,而是会被闭包 "捕获" 并保留引用。
2、特性
- 捕获词法环境:闭包会记住其定义时所在的词法环境(即外部函数的作用域),包括该作用域内的所有变量。
- 延长变量生命周期:即使外部函数执行完毕,其作用域内的变量也不会被垃圾回收,因为闭包仍然引用它们。
- 动态访问变量 :闭包捕获的是变量的引用,而非值的副本,因此变量的变化会反映在闭包中。
JavaScript
function outer() {
//outer 函数内部创建了变量 count 和函数 inner
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const closure = outer();
closure(); // 输出: 1
closure(); // 输出: 2
- 词法****作用域 :
inner
函数在定义时(即在outer
内部),其作用域链包含outer
的变量(如count
)。 - 闭包的形成 :当
inner
函数被返回并在其他地方调用时,它携带了整个词法环境 (即outer
的作用域),即使outer
已执行完毕。
在上述代码中,inner
函数形成了一个闭包。inner
函数可以访问 outer
函数作用域内的 count
变量,即使 outer
函数已经执行完毕,count
变量的值仍然被 inner
函数记住。
3、作用
3.1 数据私有化
闭包可以用来实现数据的私有化,外部无法直接访问闭包内部的变量。
JavaScript
function createCounter() {
let value = 0;
return {
increment() {
value++;
},
getValue() {
return value;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 输出: 1
在这个例子中,value
变量被封装在闭包中,外部只能通过返回的对象方法来操作 value
,实现了数据的私有化。
3.2 记忆函数
闭包可以用来创建记忆函数,记住之前的计算结果。
TypeScript
function memoize(func: (n: number) => number): (n: number) => number {
const cache = new Map<number, number>(); // 用于存储已计算的结果
return function(arg: number): number { // 返回一个闭包函数
if (cache.has(arg)) { // 如果缓存中已有结果
return cache.get(arg) as number; // 直接从缓存中返回
}
// 否则计算结果并缓存
const result: number = func(arg);
cache.set(arg, result);
return result;
};
}
function factorial(n: number): number {
return n === 0 ? 1 : n * factorial(n - 1);
}
const memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5));
console.log(memoizedFactorial(5)); // 从缓存中获取结果,提高性能
memoize
函数返回的新函数形成了闭包,记住了之前的计算结果,避免了重复计算。
3.3 闭包与箭头函数
箭头函数也可以形成闭包,但箭头函数本身不创建自己的 this
,它捕获其定义时所在作用域的 this
。
TypeScript
class Example {
private value = 42;
getValue = () => {
setTimeout(() => {
console.log(this.value); // 箭头函数捕获了 Example 实例的 this
}, 1000);
};
}
const instance = new Example();
instance.getValue(); // 输出: 42
在这个例子中,getValue
箭头函数形成了闭包,并且捕获了 Example
实例的 this
,即使在 setTimeout
回调中也能正确访问 value
。
3.4 闭包与作用域链
闭包依赖于作用域链,当函数访问一个变量时,会先在自己的作用域中查找,如果找不到则沿着作用域链向上查找。
JavaScript
function outer() {
let outerVar = "外部变量";
function inner() {
let innerVar = "内部变量";
console.log(innerVar); // 找到内部变量
console.log(outerVar); // 沿着作用域链找到外部变量
}
return inner;
}
const closure = outer();
closure();
在这个例子中,inner
函数可以访问其自身作用域内的 innerVar
和外部作用域的 outerVar
,这是通过作用域链实现的。