You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dart同步生成器惰性生成的含义及与普通函数的差异

Dart同步生成器的「惰性」到底指什么?

核心本质很简单:生成器的值计算逻辑,不会在函数调用时执行,只会延后到元素被实际访问时按需执行,且每次只计算当前需要的值,算完就暂停,和普通函数的执行逻辑有本质差异。


两者的执行行为差异

你写的两个函数最终拿到的列表结果一致,但执行时机、执行流程完全不一样:

普通返回List的函数(naturalsTo2)

只要你一调用函数,它的函数体就会从头到尾立刻执行完毕:初始化变量、循环跑完所有轮次、把所有元素塞进列表、最后把完整的列表返回。不管你之后会不会用这些元素、用几个,所有计算、内存分配的工作在调用瞬间就全部做完了。

sync*修饰的同步生成器(naturalsTo)

你调用它的时候,函数内部的代码一行都不会跑,只会立刻返回一个Iterable对象。只有当你实际迭代这个Iterable的时候(比如调用toList()、for-in遍历、取first/skip这类操作),函数体才会开始执行,而且是走一步停一步:

  • 第一次取元素:函数执行到第一个yield,算出当前值返回给迭代器,同时暂停执行,局部变量的值、当前执行到的代码位置全部保留
  • 下一次取元素:函数从上次暂停的位置恢复,继续往下执行到下一个yield,返回新值后再次暂停
  • 直到函数执行到末尾,才会给迭代器发「遍历完成」的信号,整个流程结束

一眼看明白差异的测试代码

你可以跑下面这段加了日志的代码,执行顺序的区别会非常直观:

Iterable<int> naturalsTo(int n) sync* {
  print('生成器开始执行');
  int k = 0;
  while (k < n) {
    print('即将产出$k');
    yield k++;
  }
  print('生成器执行结束');
}

Iterable<int> naturalsTo2(int n){
  print('普通函数开始执行');
  int k=0;
  List<int> resultList = [];
  for(k;k<n;k++){
    print('即将添加$k到列表');
    resultList.add(k);
  }
  print('普通函数执行结束');
  return resultList;
}

void main() {
  print('--- 测试普通函数 ---');
  final list2 = naturalsTo2(3);
  print('普通函数调用完成,准备转列表');
  print(list2.toList());

  print('\n--- 测试生成器函数 ---');
  final list1 = naturalsTo(3);
  print('生成器调用完成,准备转列表');
  print(list1.toList());
}

运行输出对比如下:

// 普通函数的执行顺序:调用就全跑完
--- 测试普通函数 ---
普通函数开始执行
即将添加0到列表
即将添加1到列表
即将添加2到列表
普通函数执行结束
普通函数调用完成,准备转列表
[0, 1, 2]

// 生成器的执行顺序:调用时啥也不干,迭代时才按需跑
--- 测试生成器函数 ---
生成器调用完成,准备转列表
生成器开始执行
即将产出0
即将产出1
即将产出2
生成器执行结束
[0, 1, 2]

惰性设计的实际价值

  • 省计算资源:如果你只需要迭代前N个元素,生成器根本不会计算N之后的所有值。比如写一个生成100万个数字的生成器,你只取first拿第一个值,它只会执行到第一个yield就停,不会跑满100万次循环。
  • 支持无限序列:你完全可以写一个while(true)持续yield递增数字的生成器,调用时不会死循环,你取多少它就算多少,普通函数写无限循环加列表会直接卡死。
  • 内存开销更低:不需要一次性把所有元素都存在内存里,迭代过的元素如果没有其他引用可以随时回收,处理超大规模序列的时候内存压力小很多。

内容的提问来源于stack exchange,提问作者森口万太郎

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 18:18:38