零基础鸿蒙应用开发第十五节:变量的作用域与生命周期

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 理解全局、局部(函数)、块级三类作用域的核心边界规则,能精准判断变量的可访问范围
  2. 掌握变量的完整生命周期(创建→存活→销毁),结合函数执行流程理解变量的“存活时长”
  3. 揭秘作用域链的底层查找逻辑,规避“变量找不到、重复定义、变量泄露”等新手高频坑
  4. 掌握ArkTS对作用域/生命周期的核心规范,写出规范且安全的代码
  5. 能结合函数场景,分析变量的作用域与生命周期,从源头避免运行时错误

【学习重点】

  • 作用域规则:全局→局部→块级的访问边界及优先级规则(同名变量时内层优先)
  • 生命周期:变量“创建(初始化)→存活(可访问)→销毁(释放内存)”的完整流程
  • 作用域链:变量查找的“从内到外”规则,及变量遮蔽(不同作用域同名变量覆盖)问题
  • 实战避坑:循环变量泄露、变量遮蔽、全局变量泛滥等常见问题

一、工程结构

本节我们将创建名为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 局部(函数)作用域:仅函数内部“可见”

  • 定义:在函数/箭头函数内部声明的变量,属于局部作用域
  • 生命周期:函数开始执行时创建,函数执行结束后销毁
  • 核心规则
    1. 函数外部无法访问函数内部的局部变量
    2. 不同作用域的同名变量互不影响(内层遮蔽外层)
    3. 同一函数作用域内禁止重复声明同名变量

局部作用域示例代码

// 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 作用域链核心规则

  1. 优先查找当前作用域的变量,找到则使用,未找到则向上一级作用域查找;
  2. 直到全局作用域,若仍未找到则编译报错;
  3. 不同作用域的同名变量会“遮蔽”上一级作用域的变量(优先访问内层变量);
  4. 同一作用域内重复声明同名变量会直接编译报错,不存在遮蔽问题。

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);
  }
}

六、核心总结

  1. 作用域核心:全局/局部/块级作用域边界清晰,同名变量遵循“内层遮蔽外层”规则,同一作用域内禁止重复声明变量;
  2. 生命周期核心:变量存活时长由作用域决定,全局变量随应用存活,局部/块级变量随函数/代码块执行结束销毁;
  3. 作用域链核心:变量查找“从内到外”,未找到则编译报错,命名表意化可避免变量遮蔽问题;
  4. 避坑核心:少用全局变量(加统一前缀)、循环变量用let声明、不同作用域变量命名差异化,从源头规避泄露和遮蔽问题。

七、代码仓库

八、下节预告

下一节将进入闭包的核心原理与基础应用,这是作用域/生命周期的进阶应用——我们将揭秘闭包“让局部变量突破函数生命周期”的底层逻辑,掌握闭包在状态管理、防抖节流等场景的核心应用,同时理解对闭包的内存管理规范,让你既能用好闭包,又能规避闭包导致的内存泄露问题!

posted @ 2026-01-19 15:25  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报