零基础鸿蒙应用开发第十五节:变量的作用域与生命周期
【学习目标】
- 理解全局、局部(函数)、块级三类作用域的核心边界规则,能精准判断变量的可访问范围
- 掌握变量的完整生命周期(创建→存活→销毁),结合函数执行流程理解变量的“存活时长”
- 揭秘作用域链的底层查找逻辑,规避“变量找不到、重复定义、变量泄露”等新手高频坑
- 掌握ArkTS对作用域/生命周期的核心规范,写出规范且安全的代码
- 能结合函数场景,分析变量的作用域与生命周期,从源头避免运行时错误
【学习重点】
- 作用域规则:全局→局部→块级的访问边界及优先级规则(同名变量时内层优先)
- 生命周期:变量“创建(初始化)→存活(可访问)→销毁(释放内存)”的完整流程
- 作用域链:变量查找的“从内到外”规则,及变量遮蔽(不同作用域同名变量覆盖)问题
- 实战避坑:循环变量泄露、变量遮蔽、全局变量泛滥等常见问题
一、工程结构
本节我们将创建名为ScopeLifecycleDemo的工程,基于鸿蒙5.0(API12)开发,使用DevEco Studio 6.0+工具,项目结构目录如下:
ScopeLifecycleDemo/
├── entry/
│ ├── src/
│ │ ├── main/
│ │ │ ├── ets/
│ │ │ │ ├── entryability/ # 应用入口(默认生成,无需修改)
│ │ │ │ │ └── EntryAbility.ets
│ │ │ │ ├── pages/ # 页面代码(含UI+逻辑调用)
│ │ │ │ │ └── Index.ets # 核心页面(作用域测试)
│ │ │ │ └── utils/ # 工具函数目录
│ │ │ │ └── ScopeTest.ets # 作用域/生命周期核心代码
│ │ │ ├── resources/ # 资源文件(默认生成,无需修改)
│ │ │ └── module.json5 # 模块配置(默认生成,无需修改)
二、作用域:变量的“访问边界”
作用域(Scope)定义了变量的可访问范围,简单说就是“变量能在哪些代码区域被读取/修改”。ArkTS中核心分为三类作用域,优先级(同名变量时内层优先):块级作用域 > 局部(函数)作用域 > 全局作用域。
2.1 全局作用域:整个应用“可见”
- 定义:在所有函数、代码块外部声明的变量,属于全局作用域
- 生命周期:应用启动时创建,应用退出时销毁(存活时间最长)
- 核心规范:尽量少用全局变量(易导致命名冲突、内存泄露),必须使用时加统一前缀(如
APP_)
全局作用域示例代码
// utils/ScopeTest.ets
// 全局常量:不可修改,全应用可见
const APP_VERSION: string = "1.0.0";
// 全局变量:可修改,全应用可见(尽量少用)
let APP_USER_COUNT: number = 0;
// 测试函数1:访问并修改全局变量
function testGlobalVariable(): void {
console.log(`[全局作用域] 应用版本:${APP_VERSION}`); // 输出:[全局作用域] 应用版本:1.0.0
APP_USER_COUNT += 1;
console.log(`[全局作用域] 当前用户数:${APP_USER_COUNT}`); // 输出:[全局作用域] 当前用户数:1
}
// 测试函数2:验证全局变量的跨函数访问
function testGlobalCrossFunction(): void {
console.log(`[全局作用域] 跨函数访问用户数:${APP_USER_COUNT}`); // 输出:[全局作用域] 跨函数访问用户数:1
}
// ========== 错误示例(注释掉,避免编译报错) ==========
// 错误:同一全局作用域重复声明同名变量(编译报错:标识符已声明)
// const APP_VERSION: string = "2.0.0";
// 不推荐:全局变量命名无前缀,易冲突(不报错,但规范不建议)
// let userCount: number = 0; // 建议改为 APP_USER_COUNT
2.2 局部(函数)作用域:仅函数内部“可见”
- 定义:在函数/箭头函数内部声明的变量,属于局部作用域
- 生命周期:函数开始执行时创建,函数执行结束后销毁
- 核心规则:
- 函数外部无法访问函数内部的局部变量
- 不同作用域的同名变量互不影响(内层遮蔽外层)
- 同一函数作用域内禁止重复声明同名变量
局部作用域示例代码
// utils/ScopeTest.ets
// 全局变量(用于对比)
let globalNum: number = 100;
// 局部作用域核心逻辑
function testLocalScope(): void {
// 局部变量:仅当前函数内可见
let localUserName: string = "张三";
const localUserAge: number = 25;
console.log(`[局部作用域] 用户名:${localUserName},年龄:${localUserAge}`);
// 输出:[局部作用域] 用户名:张三,年龄:25
localUserName = "李四";
console.log(`[局部作用域] 修改后用户名:${localUserName}`);
// 输出:[局部作用域] 修改后用户名:李四
// 局部变量遮蔽全局变量
let globalNum: number = 200; // 局部变量,和全局变量同名
console.log(`[局部作用域] 局部变量globalNum:${globalNum}`);
// 输出:[局部作用域] 局部变量globalNum:200(优先访问局部变量)
}
// 验证局部变量外部不可访问
function testLocalAccessOutside(): void {
// 错误:函数外部访问其他函数的局部变量(编译报错:找不到名称)
// console.log(localUserName);
// 读取全局变量(未被局部变量影响)
console.log(`[局部作用域] 全局变量globalNum:${globalNum}`);
// 输出:[局部作用域] 全局变量globalNum:100
}
// 局部作用域错误示例
function testLocalError(): void {
// 错误:同一函数作用域内重复声明同名变量(ArkTS编译报错:标识符已声明)
// let localNum: number = 10;
// let localNum: number = 20;
// 错误:尝试访问箭头函数内部的局部变量(编译报错:找不到名称arrowVar)
const arrowFunc = () => {
let arrowVar: string = "箭头函数变量";
};
// console.log(arrowVar);
}
2.3 块级作用域:仅代码块内部“可见”
- 定义:在
{}包裹的代码块内声明的变量(if/for/while/switch等),属于块级作用域 - 生命周期:代码块开始执行时创建,代码块执行结束后销毁
块级作用域示例代码(if+for综合)
// utils/ScopeTest.ets
function testBlockScope(): void {
let outerVar: string = "外部变量";
// 1. if块级作用域
if (true) {
let innerVar: string = "if内部变量";
console.log(`[块级作用域] if内部:${outerVar} + ${innerVar}`);
// 输出:[块级作用域] if内部:外部变量 + if内部变量
outerVar = "外部变量已修改";
}
console.log(`[块级作用域] if外部:${outerVar}`);
// 输出:[块级作用域] if外部:外部变量已修改
// 错误:访问if块内的变量(编译报错:找不到名称)
// console.log(innerVar);
// 2. for循环块级作用域
for (let i: number = 0; i < 3; i++) {
// 延迟执行,验证每次循环的i是独立的
setTimeout(() => {
console.log(`[块级作用域] 循环i:${i}`);
// 输出:0 → 1 → 2(let声明的块级变量特性)
}, 100);
}
// 错误:访问循环块内的变量(编译报错:找不到名称)
// console.log(i);
// ========== var导致变量泄露的原因示例TypeScript场景==========
// 说明:ArkTS中直接禁用var(编译报错),此处仅演示var的问题
// for (var j: number = 0; j < 3; j++) {}
// console.log(j); // var无块级作用域,变量泄露到外部(输出3),易引发逻辑错误
}
2.4 作用域核心规则总结
| 作用域类型 | 声明位置 | 可访问范围 | 生命周期 | 核心规范 |
|---|---|---|---|---|
| 全局作用域 | 所有函数/块外部 | 整个应用 | 应用启动→退出 | 1. 少用,加统一前缀;2. 禁止重复声明 |
| 局部作用域 | 函数/箭头函数内部 | 仅函数内部 | 函数执行→结束 | 1. 禁止重复声明;2. 推荐显式声明类型 |
| 块级作用域 | {}代码块内部 |
仅代码块内部 | 代码块执行→结束 | 1. 禁止重复声明;2. var无块级作用域易泄露(ArkTS直接禁用) |
三、生命周期:变量的“存活时长”
变量的生命周期(Lifecycle)是指变量从“创建(分配内存)→存活(可访问)→销毁(释放内存)”的完整过程,其时长完全由作用域决定。
3.1 生命周期三阶段
graph LR
A["创建阶段<br/>变量声明并初始化,分配内存"]
--> B["存活阶段<br/>变量可被读取/修改,处于作用域内"]
--> C["销毁阶段<br/>变量超出作用域,内存被释放"]
3.2 生命周期综合示例(局部+块级)
// utils/ScopeTest.ets
// 全局变量:生命周期 = 应用生命周期
const GLOBAL_LIFETIME: string = "全局变量-永久存活";
function testLifecycleComprehensive(): void {
console.log("\n[生命周期] 函数执行开始 → 局部变量创建");
// 1. 局部变量生命周期(函数级)
let localVar: string = "函数内局部变量";
console.log(`[生命周期] 局部变量存活:${localVar}`);
localVar = "局部变量-已修改";
console.log(`[生命周期] 局部变量存活(修改后):${localVar}`);
// 2. 块级变量生命周期(循环块)
for (let k: number = 0; k < 2; k++) {
console.log(`\n[生命周期] 循环${k}开始 → 块级变量创建`);
let blockVar: string = `循环块变量-${k}`;
console.log(`[生命周期] 块级变量存活:${blockVar}`);
console.log(`[生命周期] 循环${k}结束 → 块级变量销毁`);
}
console.log("\n[生命周期] 函数执行结束 → 局部变量销毁");
}
// 验证全局变量生命周期
function testGlobalLifecycle(): void {
console.log(`\n[生命周期] 全局变量仍存活:${GLOBAL_LIFETIME}`);
// 输出:[生命周期] 全局变量仍存活:全局变量-永久存活
}
// 生命周期错误示例
function testLifecycleErrors(): void {
// 错误1:访问已销毁的局部变量(函数执行完毕后,内部变量被销毁,编译报错)
const tempFunc = () => {
let tempVar: number = 10;
};
tempFunc(); // 执行后tempVar已销毁
// console.log(tempVar);
// 错误2:块级作用域变量泄露访问(编译报错:找不到名称blockVar)
if (true) {
let blockVar: string = "块级变量";
}
// console.log(blockVar);
}
四、作用域链:变量的“查找规则”
当代码访问一个变量时,ArkTS会按照“从内到外”的顺序查找变量,这个查找链条就是作用域链。
4.1 作用域链核心规则
- 优先查找当前作用域的变量,找到则使用,未找到则向上一级作用域查找;
- 直到全局作用域,若仍未找到则编译报错;
- 不同作用域的同名变量会“遮蔽”上一级作用域的变量(优先访问内层变量);
- 同一作用域内重复声明同名变量会直接编译报错,不存在遮蔽问题。
4.2 作用域链示例
// utils/ScopeTest.ets
// 全局作用域变量
let chainVar: string = "全局层变量";
// 外层函数(第一层作用域)
function outerFunc(): void {
// 外层函数变量,遮蔽全局变量
let chainVar: string = "外层函数变量";
// 内层箭头函数(第二层作用域)
const innerFunc = (): void => {
// 内层函数变量,遮蔽外层函数变量
let chainVar: string = "内层函数变量";
console.log(`[作用域链] 内层访问:${chainVar}`);
// 输出:[作用域链] 内层访问:内层函数变量
};
innerFunc();
console.log(`[作用域链] 外层访问:${chainVar}`);
// 输出:[作用域链] 外层访问:外层函数变量
}
// 验证全局变量未被遮蔽影响
function testGlobalChain(): void {
console.log(`[作用域链] 全局访问:${chainVar}`);
// 输出:[作用域链] 全局访问:全局层变量
}
// 变量遮蔽避坑示例
function testShadowingBestPractice(): void {
// 推荐:不同作用域变量命名表意化,避免遮蔽
let globalUserCount: number = 100;
const funcUserCount = (): void => {
let funcUserCount: number = 200; // 命名区分全局变量
if (true) {
let blockUserCount: number = 300; // 命名区分函数变量
console.log(`[避坑] 块级变量:${blockUserCount}`);
// 输出:[避坑] 块级变量:300
}
console.log(`[避坑] 函数变量:${funcUserCount}`);
// 输出:[避坑] 函数变量:200
};
funcUserCount();
console.log(`[避坑] 全局变量:${globalUserCount}`);
// 输出:[避坑] 全局变量:100
}
五、统一测试调用代码
5.1 测试入口函数
// utils/ScopeTest.ets
// ========== 统一测试入口(仅调用,不整合函数逻辑) ==========
export function allScopeTests(): void {
console.log("========== 全局作用域测试 ==========");
testGlobalVariable();
testGlobalCrossFunction();
console.log("\n========== 局部作用域测试 ==========");
testLocalScope();
testLocalAccessOutside();
testLocalError();
console.log("\n========== 块级作用域测试 ==========");
testBlockScope();
console.log("\n========== 生命周期测试 ==========");
testLifecycleComprehensive();
testGlobalLifecycle();
testLifecycleErrors();
console.log("\n========== 作用域链测试 ==========");
outerFunc();
testGlobalChain();
testShadowingBestPractice();
console.log("\n✅ 所有测试执行完成");
}
5.2 页面调用代码(Index.ets)
// pages/Index.ets
import { allScopeTests } from '../utils/ScopeTest';
@Entry
@Component
struct Index {
aboutToAppear() {
allScopeTests(); // 调用统一测试函数
}
build() {
Column({ space: 20 }) {
// 标题
Text("变量作用域与生命周期测试")
.fontSize(32)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
.width('100%');
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
六、核心总结
- 作用域核心:全局/局部/块级作用域边界清晰,同名变量遵循“内层遮蔽外层”规则,同一作用域内禁止重复声明变量;
- 生命周期核心:变量存活时长由作用域决定,全局变量随应用存活,局部/块级变量随函数/代码块执行结束销毁;
- 作用域链核心:变量查找“从内到外”,未找到则编译报错,命名表意化可避免变量遮蔽问题;
- 避坑核心:少用全局变量(加统一前缀)、循环变量用let声明、不同作用域变量命名差异化,从源头规避泄露和遮蔽问题。
七、代码仓库
- 工程名称:ScopeLifecycleDemo
- 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
八、下节预告
下一节将进入闭包的核心原理与基础应用,这是作用域/生命周期的进阶应用——我们将揭秘闭包“让局部变量突破函数生命周期”的底层逻辑,掌握闭包在状态管理、防抖节流等场景的核心应用,同时理解对闭包的内存管理规范,让你既能用好闭包,又能规避闭包导致的内存泄露问题!
浙公网安备 33010602011771号