2070 lines
92 KiB
JSON
2070 lines
92 KiB
JSON
{
|
||
"sets": [
|
||
{
|
||
"id": 1,
|
||
"name": "Dart & Flutter 学习测验",
|
||
"questions": [
|
||
{
|
||
"id": 1,
|
||
"lv": 1,
|
||
"q": "Dart 中声明\"类型自动推断\"的变量,用哪个关键字?",
|
||
"opts": [
|
||
"var",
|
||
"let",
|
||
"const",
|
||
"dynamic"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Dart 用 <b>var</b> 由编译器自动推断类型;<b>let</b> 是 JS 的;<b>const</b> 表示编译期常量。"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"lv": 1,
|
||
"q": "Dart 中表示 64 位整数的数据类型是?",
|
||
"opts": [
|
||
"integer",
|
||
"int",
|
||
"number",
|
||
"long"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 用 <b>int</b> 表示整数(64位),<b>double</b> 表示浮点数,两者父类是 <b>num</b>。"
|
||
},
|
||
{
|
||
"id": 3,
|
||
"lv": 1,
|
||
"q": "以下哪个是 Dart 中正确的单行注释写法?",
|
||
"opts": [
|
||
"# 注释",
|
||
"/* 注释 */",
|
||
"// 注释",
|
||
"<!-- 注释 -->"
|
||
],
|
||
"ans": 2,
|
||
"exp": "Dart 单行注释用 <b>//</b>,多行用 <b>/* */</b>。<b>#</b> 是 Python,#! 在 Dart 中只是 shebang 声明。"
|
||
},
|
||
{
|
||
"id": 4,
|
||
"lv": 1,
|
||
"q": "Dart 程序的入口函数是?",
|
||
"opts": [
|
||
"runApp()",
|
||
"main()",
|
||
"init()",
|
||
"start()"
|
||
],
|
||
"ans": 1,
|
||
"exp": "入口是 <b>main()</b>。Flutter 中通常写成 <code>void main() => runApp(MyApp());</code>"
|
||
},
|
||
{
|
||
"id": 5,
|
||
"lv": 1,
|
||
"q": "要在控制台输出文字,应调用哪个函数?",
|
||
"opts": [
|
||
"console.log()",
|
||
"System.out.println()",
|
||
"echo",
|
||
"print()"
|
||
],
|
||
"ans": 3,
|
||
"exp": "Dart 用 <b>print()</b> 输出到控制台,比如 <code>print(\"Hello Dart\");</code>"
|
||
},
|
||
{
|
||
"id": 6,
|
||
"lv": 1,
|
||
"q": "<code>int x = 10;</code> 要打印出 <code>x=10</code>,以下哪种写法正确?",
|
||
"opts": [
|
||
"print(\"x=\" + x);",
|
||
"print(\"x=$x\");",
|
||
"print('x=${x}');",
|
||
"以上都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "Dart 中 <b>+</b> 拼接字符串、<b>$x</b> 插值、<b>${x}</b> 表达式插值都合法,三种写法均能输出 <code>x=10</code>。"
|
||
},
|
||
{
|
||
"id": 7,
|
||
"lv": 2,
|
||
"q": "以下哪个 <b>不是</b> Dart 的循环写法?",
|
||
"opts": [
|
||
"for (var i=0; i<5; i++)",
|
||
"while (ok)",
|
||
"foreach (x in list)",
|
||
"do { } while (ok)"
|
||
],
|
||
"ans": 2,
|
||
"exp": "Dart <b>没有</b> <code>foreach(x in list)</code> 语法,应写 <code>for (var x in list)</code> 或 <code>list.forEach(...)</code>。"
|
||
},
|
||
{
|
||
"id": 8,
|
||
"lv": 2,
|
||
"q": "执行 <code>for (var i=0; i<5; i++) print(i);</code> 会输出?",
|
||
"opts": [
|
||
"1 2 3 4 5",
|
||
"0 1 2 3 4",
|
||
"0 1 2 3 4 5",
|
||
"5 4 3 2 1"
|
||
],
|
||
"ans": 1,
|
||
"exp": "i 从 0 开始,条件 <code>i<5</code>,所以输出 <b>0 1 2 3 4</b>。"
|
||
},
|
||
{
|
||
"id": 9,
|
||
"lv": 2,
|
||
"q": "Dart 的 switch 语句中,每个 case 结束必须以什么结尾(否则编译报错)?",
|
||
"opts": [
|
||
"end",
|
||
"return / break / throw",
|
||
"continue",
|
||
"逗号"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 每个非空 case 必须以 <b>break、return 或 throw</b> 等结束,不能像 C 语言那样自动穿透。"
|
||
},
|
||
{
|
||
"id": 10,
|
||
"lv": 2,
|
||
"q": "定义一个返回两数之和的函数,哪个写法正确?",
|
||
"opts": [
|
||
"int add(int a,int b){ return a+b; }",
|
||
"add(int a,int b) => a+b;",
|
||
"void add(int a,int b){ return a+b; }",
|
||
"A 和 B 都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "A 是传统写法,B 是<b>箭头函数</b>简写(返回表达式),两者都合法;C 返回 void 却 return 值,编译错误。"
|
||
},
|
||
{
|
||
"id": 11,
|
||
"lv": 2,
|
||
"q": "以下哪个是<b>可选命名参数</b>的正确声明?",
|
||
"opts": [
|
||
"void f({String name}) {}",
|
||
"void f([String name]) {}",
|
||
"void f(String? name) {}",
|
||
"void f(String name=\"\") {}"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>{}</b> 包裹的是命名参数(调用用 <code>f(name: x)</code>);<b>[]</b> 是可选位置参数;<b>?</b> 是空安全;最后一个是默认值写法但不属于命名参数。"
|
||
},
|
||
{
|
||
"id": 12,
|
||
"lv": 2,
|
||
"q": "函数 <code>void f({int a = 1}) {}</code>,以下调用哪个正确?",
|
||
"opts": [
|
||
"f()",
|
||
"f(1)",
|
||
"f(a: 1)",
|
||
"A 和 C 都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "命名参数有默认值时可省略(A 合法);必须<b>按名调用</b> <code>f(a: 1)</code>(C 合法);<code>f(1)</code> 用位置传参给命名参数会报错。"
|
||
},
|
||
{
|
||
"id": 13,
|
||
"lv": 3,
|
||
"q": "Dart 中表示\"数组/列表\"的类型是?",
|
||
"opts": [
|
||
"Array",
|
||
"ArrayList",
|
||
"List",
|
||
"Vector"
|
||
],
|
||
"ans": 2,
|
||
"exp": "Dart 用 <b>List</b>(可增长数组),字面量写法 <code>[1,2,3]</code>。"
|
||
},
|
||
{
|
||
"id": 14,
|
||
"lv": 3,
|
||
"q": "创建包含 1、2、3 的列表,正确的是?",
|
||
"opts": [
|
||
"{1, 2, 3}",
|
||
"[1, 2, 3]",
|
||
"(1, 2, 3)",
|
||
"List(1,2,3)"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>[]</b> 是 List;<b>{}</b> 是 Set/Map;<b>()</b> 不是集合字面量。"
|
||
},
|
||
{
|
||
"id": 15,
|
||
"lv": 3,
|
||
"q": "<code>var list = [10, 20, 30]; print(list[1]);</code> 输出?",
|
||
"opts": [
|
||
"10",
|
||
"20",
|
||
"30",
|
||
"报错"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 索引从 <b>0</b> 开始,list[1] 是第二个元素 <b>20</b>。"
|
||
},
|
||
{
|
||
"id": 16,
|
||
"lv": 3,
|
||
"q": "对 List 每个元素做处理并<b>返回一个新集合</b>的方法是?",
|
||
"opts": [
|
||
"forEach()",
|
||
"map()",
|
||
"where()",
|
||
"add()"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>map()</b> 对每个元素变换并返回 Iterable;<b>forEach()</b> 只遍历返回 void;<b>where()</b> 是过滤。"
|
||
},
|
||
{
|
||
"id": 17,
|
||
"lv": 3,
|
||
"q": "<code>var a=[1,2,3]; var b=[0, ...a];</code> 那么 <code>b.length</code> 是?",
|
||
"opts": [
|
||
"3",
|
||
"4",
|
||
"7",
|
||
"报错"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>...a</b> 是扩展运算符,把 a 展开成 1,2,3,加上开头的 0,b = [0,1,2,3],长度 <b>4</b>。"
|
||
},
|
||
{
|
||
"id": 18,
|
||
"lv": 3,
|
||
"q": "以下哪种集合类型<b>保证元素不重复</b>?",
|
||
"opts": [
|
||
"List",
|
||
"Map",
|
||
"Set",
|
||
"Queue"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>Set</b> 存储唯一元素,重复添加会被忽略;Map 存储键值对,键也不重复。"
|
||
},
|
||
{
|
||
"id": 19,
|
||
"lv": 4,
|
||
"q": "定义类 <code>class Dog { String name; }</code> 后,创建一个实例哪个正确?",
|
||
"opts": [
|
||
"Dog d = Dog();",
|
||
"var d = new Dog;",
|
||
"Dog d = new Dog();",
|
||
"以上都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "Dart 2 之后 <b>new 关键字可选</b>,且省略括号的 <code>Dog()</code> 也合法,三种写法都能创建实例。"
|
||
},
|
||
{
|
||
"id": 20,
|
||
"lv": 4,
|
||
"q": "子类继承父类使用哪个关键字?",
|
||
"opts": [
|
||
"with",
|
||
"implements",
|
||
"extends",
|
||
"inherits"
|
||
],
|
||
"ans": 2,
|
||
"exp": "继承用 <b>extends</b>;<b>implements</b> 是实现接口;<b>with</b> 用于混入 Mixin。"
|
||
},
|
||
{
|
||
"id": 21,
|
||
"lv": 4,
|
||
"q": "包含未实现方法、<b>不能被直接实例化</b>的类,用什么关键字声明?",
|
||
"opts": [
|
||
"abstract",
|
||
"interface",
|
||
"base",
|
||
"pure"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>abstract</b> 抽象类可含未实现方法,不能直接 new,需子类实现后实例化。"
|
||
},
|
||
{
|
||
"id": 22,
|
||
"lv": 4,
|
||
"q": "在构造函数/实例方法中,<code>this</code> 指向?",
|
||
"opts": [
|
||
"当前实例",
|
||
"父类对象",
|
||
"类本身",
|
||
"静态成员"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>this</b> 指向当前实例对象,用于区分参数与成员变量,如 <code>this.name = name</code>。"
|
||
},
|
||
{
|
||
"id": 23,
|
||
"lv": 4,
|
||
"q": "创建一个只能装 String 的 List,哪个写法正确?",
|
||
"opts": [
|
||
"List<String> names = [];",
|
||
"List names = <String>[];",
|
||
"var names = <String>[];",
|
||
"以上都是"
|
||
],
|
||
"ans": 3,
|
||
"exp": "Dart 泛型语法多样:类型标注式、泛型字面量 <code><String>[]</code> 都合法,三种写法结果相同。"
|
||
},
|
||
{
|
||
"id": 24,
|
||
"lv": 4,
|
||
"q": "抽象类 Animal 有抽象方法 <code>void speak()</code>,子类 Dog 必须?",
|
||
"opts": [
|
||
"重写 speak() 提供实现",
|
||
"调用父类构造",
|
||
"删除 speak",
|
||
"什么也不用做"
|
||
],
|
||
"ans": 0,
|
||
"exp": "子类必须用 <code>@override</code> 实现所有抽象方法,否则 Dog 也是抽象类无法实例化。"
|
||
},
|
||
{
|
||
"id": 25,
|
||
"lv": 5,
|
||
"q": "Dart 空安全中,声明一个<b>可能为 null</b> 的字符串类型是?",
|
||
"opts": [
|
||
"String",
|
||
"String?",
|
||
"String!",
|
||
"nullable String"
|
||
],
|
||
"ans": 1,
|
||
"exp": "在类型后加 <b>?</b> 表示可空:<code>String? s = null;</code>;不加 ? 的 String 保证非空。"
|
||
},
|
||
{
|
||
"id": 26,
|
||
"lv": 5,
|
||
"q": "空值合并运算符 <code>??</code> 的作用是?",
|
||
"opts": [
|
||
"若左边为 null 则返回右边",
|
||
"强制把 null 变为非空",
|
||
"判断对象是否相等",
|
||
"抛出空异常"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>a ?? b</code>:a 不为 null 用 a,否则用 b。常配合 <code>??=</code> 设置默认值。"
|
||
},
|
||
{
|
||
"id": 27,
|
||
"lv": 5,
|
||
"q": "使用 <code>async/await</code> 时,异步函数一般返回什么类型?",
|
||
"opts": [
|
||
"Stream<T>",
|
||
"Future<T>",
|
||
"void",
|
||
"T"
|
||
],
|
||
"ans": 1,
|
||
"exp": "async 函数返回 <b>Future<T></b>,await 会挂起等待该 Future 完成后再继续执行。"
|
||
},
|
||
{
|
||
"id": 28,
|
||
"lv": 5,
|
||
"q": "执行以下代码,输出顺序是?<br><code>Future.delayed(Duration(seconds:1), () => print(\"A\"));<br>print(\"B\");</code>",
|
||
"opts": [
|
||
"A 后 B",
|
||
"B 后 A",
|
||
"同时输出",
|
||
"只输出 A"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Future.delayed 是异步任务,先执行同步的 <code>print(\"B\")</code>,1 秒后再输出 A。"
|
||
},
|
||
{
|
||
"id": 29,
|
||
"lv": 6,
|
||
"q": "Flutter 中用于<b>监听持续不断的数据流</b>(如多次事件)的是?",
|
||
"opts": [
|
||
"Future",
|
||
"Stream",
|
||
"List",
|
||
"Map"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>Stream</b> 可监听多次异步事件;Future 只代表一次性的结果。"
|
||
},
|
||
{
|
||
"id": 30,
|
||
"lv": 6,
|
||
"q": "Flutter 中构建界面的<b>基本单位</b>是?",
|
||
"opts": [
|
||
"Activity",
|
||
"View",
|
||
"Widget",
|
||
"Component"
|
||
],
|
||
"ans": 2,
|
||
"exp": "Flutter 一切皆 <b>Widget</b>,UI 由 Widget 树组合而成(如 Text、Container、Column)。"
|
||
},
|
||
{
|
||
"id": 31,
|
||
"lv": 6,
|
||
"q": "界面状态需要<b>随数据变化而更新</b>时,应使用哪类组件?",
|
||
"opts": [
|
||
"StatelessWidget",
|
||
"StatefulWidget",
|
||
"Container",
|
||
"Scaffold"
|
||
],
|
||
"ans": 1,
|
||
"exp": "有动态状态用 <b>StatefulWidget</b>(配合 State 里的 setState 刷新);无状态用 StatelessWidget。"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": 9,
|
||
"name": "Dart 开发实战",
|
||
"questions": [
|
||
{
|
||
"id": 98,
|
||
"lv": 1,
|
||
"q": "开发注册页时,用户输入的年龄是字符串,需要转成 <code>int</code> 保存。已知 <code>String ageText = \"18\";</code>,正确的转换写法是?",
|
||
"opts": [
|
||
"<code>int(ageText)</code>",
|
||
"<code>toInt(ageText)</code>",
|
||
"<code>int.parse(ageText)</code>",
|
||
"<code>ageText.parseInt()</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<code>int.parse()</code> 把字符串解析成整数,解析失败会抛 FormatException,更稳妥的是 <code>int.tryParse()</code>(失败返回 null)。注意 Dart 没有 <code>\"18\".parseInt()</code> 这种写法,<code>int(...)</code> 也不是转换函数。"
|
||
},
|
||
{
|
||
"id": 99,
|
||
"lv": 1,
|
||
"q": "项目里要定义一个\"永远不允许修改\"的 API 地址常量,以下哪种声明最合适?",
|
||
"opts": [
|
||
"<code>final String api = \"https://api.example.com\";</code>",
|
||
"<code>const String api = \"https://api.example.com\";</code>",
|
||
"<code>String api = \"https://api.example.com\";</code>",
|
||
"<code>var api = \"https://api.example.com\";</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>const</b> 是编译期常量:值在编译时就确定,且不可变,编译器可直接内联替换,还能配合 const 构造函数做实例复用。<b>final</b> 是运行期赋值一次后不可变。给字面量常量用 const 是更优选择。"
|
||
},
|
||
{
|
||
"id": 100,
|
||
"lv": 1,
|
||
"q": "你在重构代码时写下:<code>var x = 10; x = \"hello\";</code> 会发生什么?",
|
||
"opts": [
|
||
"正常运行,x 变成字符串",
|
||
"编译报错:x 已被推断为 int 类型",
|
||
"运行时报错",
|
||
"x 自动变为 null"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>var</b> 声明时由初始值自动推断类型,推断后类型就<b>固定</b>了:x 是 int,再赋 String 在编译期直接报错。想存任意类型,应显式声明 <code>dynamic</code> 或 <code>Object?</code>。"
|
||
},
|
||
{
|
||
"id": 101,
|
||
"lv": 1,
|
||
"q": "给变量起名时,以下哪个<b>不是</b>合法的 Dart 标识符?",
|
||
"opts": [
|
||
"<code>_score</code>",
|
||
"<code>2x</code>",
|
||
"<code>中文名</code>",
|
||
"<code>name_2</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 标识符<b>不能以数字开头</b>。<code>_score</code> 合法(下划线开头表示库内私有,是 Dart 的约定俗成);Dart 支持 Unicode 标识符,中文变量名同样合法。"
|
||
},
|
||
{
|
||
"id": 102,
|
||
"lv": 1,
|
||
"q": "结算功能计算人均消费:<code>int total = 10; int people = 4; double avg = total / people;</code> 打印 <code>avg</code> 得到?",
|
||
"opts": [
|
||
"2",
|
||
"2.5",
|
||
"2.0",
|
||
"报错"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 中 <b>int 除以 int 的结果一定是 double</b>:10 / 4 = 2.5。想得到整除结果用 <code>~/</code>:<code>10 ~/ 4 == 2</code>,取余用 <code>%</code>。"
|
||
},
|
||
{
|
||
"id": 103,
|
||
"lv": 2,
|
||
"q": "写优惠逻辑:订单数 > 10 打 9 折,> 50 打 8 折。对 <code>orders = 60</code>,以下代码会得到什么折扣?<br><code>double d; if (orders > 10) { d = 0.9; } else if (orders > 50) { d = 0.8; }</code>",
|
||
"opts": [
|
||
"0.8",
|
||
"0.9",
|
||
"1.0",
|
||
"0.7"
|
||
],
|
||
"ans": 1,
|
||
"exp": "if-else 链<b>从上到下匹配,命中即止</b>:60 > 10 先成立就直接取 0.9,后面的 else if 不再执行。正确写法应把范围大的条件放前面(先判断 > 50)。判断顺序是 if-else 链最经典的坑。"
|
||
},
|
||
{
|
||
"id": 104,
|
||
"lv": 2,
|
||
"q": "接口返回 <code>Map<String, dynamic> data</code>,想遍历所有 key 打印出来,以下哪种写法正确?",
|
||
"opts": [
|
||
"<code>for (var k in data) print(k);</code>",
|
||
"<code>for (var k in data.keys) print(k);</code>",
|
||
"<code>data.keys.forEach(print);</code>",
|
||
"B 和 C 都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "Map 本身<b>不能</b>直接 for-in 遍历,要先取 <code>keys</code> / <code>values</code> / <code>entries</code>。B 是标准写法;C 中 <code>forEach(print)</code> 利用了函数引用,同样正确。两种都行,按团队规范选择。"
|
||
},
|
||
{
|
||
"id": 105,
|
||
"lv": 2,
|
||
"q": "写一个返回用户名的函数:<code>String getUserName() { }</code>,忘了写 <code>return</code>,会怎样?",
|
||
"opts": [
|
||
"返回 null",
|
||
"编译报错",
|
||
"运行时崩溃",
|
||
"自动返回空字符串"
|
||
],
|
||
"ans": 1,
|
||
"exp": "空安全下,声明了非空返回类型 <code>String</code> 的函数<b>必须有 return</b>,漏写直接编译报错,把隐患消灭在编译期。如果确实允许无返回值,应把返回类型声明为 <code>String?</code>。"
|
||
},
|
||
{
|
||
"id": 106,
|
||
"lv": 2,
|
||
"q": "问候函数:<code>void greet(String name, {String greeting = \"你好\"})</code>,调用 <code>greet(\"小明\", \"早上好\")</code> 会发生什么?",
|
||
"opts": [
|
||
"正常执行,greeting 为 \"早上好\"",
|
||
"编译报错:命名参数不能按位置传参",
|
||
"greeting 变为 null",
|
||
"编译警告但能运行"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<code>{}</code> 包裹的是<b>命名参数</b>,必须写成 <code>greet(\"小明\", greeting: \"早上好\")</code>;<code>[]</code> 才是可选位置参数。命名参数让调用意图清晰,是团队协作中常用的做法。"
|
||
},
|
||
{
|
||
"id": 107,
|
||
"lv": 2,
|
||
"q": "写\"重试发送验证码\"逻辑:要求<b>至少发送一次</b>,失败则继续重试。哪种循环语义最贴切?",
|
||
"opts": [
|
||
"<code>while</code>",
|
||
"<code>do-while</code>",
|
||
"<code>for</code>",
|
||
"<code>repeat</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>do-while</b> 先执行循环体再判断条件,保证至少执行一次,正是\"先发一次、不行再重试\"的语义。while 先判断后执行,可能一次都不执行。"
|
||
},
|
||
{
|
||
"id": 108,
|
||
"lv": 3,
|
||
"q": "实现\"删除待办\"功能,<code>list.remove(3)</code> 和 <code>list.removeAt(3)</code> 的区别是?",
|
||
"opts": [
|
||
"remove 按值删除,removeAt 按下标删除",
|
||
"两者完全相同",
|
||
"remove 删下标,removeAt 按值删除",
|
||
"remove 会删除所有等于 3 的元素"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>remove(value)</code> 删除<b>第一个</b>值相等的元素并返回是否成功;<code>removeAt(index)</code> 按下标删除并返回被删的元素,越界抛 RangeError。写代码前想清楚你要删的是\"值\"还是\"位置\"。"
|
||
},
|
||
{
|
||
"id": 109,
|
||
"lv": 3,
|
||
"q": "解析接口数据:用 <code>data[\"name\"]</code> 取一个<b>不存在的 key</b> 时,返回什么?",
|
||
"opts": [
|
||
"抛异常",
|
||
"null",
|
||
"空字符串",
|
||
"抛出 NoSuchMethodError"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Map 用 <code>[]</code> 取值时,key 不存在<b>返回 null</b> 而不是抛异常。所以 <code>data[\"name\"]</code> 可能为 null,取值后判空再使用是常规操作(配合 <code>??</code> 兜底)。"
|
||
},
|
||
{
|
||
"id": 110,
|
||
"lv": 3,
|
||
"q": "配置列表按条件插入元素:<code>var l = [1, 2, if (isVip) 3];</code> 当 <code>isVip</code> 为 false 时 l 是?",
|
||
"opts": [
|
||
"<code>[1, 2]</code>",
|
||
"<code>[1, 2, 3]</code>",
|
||
"<code>[1, 2, false]</code>",
|
||
"编译错误"
|
||
],
|
||
"ans": 0,
|
||
"exp": "这是 <b>collection-if</b> 语法:条件不成立时该元素直接不生成。Flutter 里常用于条件渲染组件列表,配合 <code>...</code> 展开和 collection-for 一起使用。"
|
||
},
|
||
{
|
||
"id": 111,
|
||
"lv": 3,
|
||
"q": "两个用户各自关注了一批标签,要找出<b>共同关注</b>的标签,用哪种写法?",
|
||
"opts": [
|
||
"<code>a & b</code>",
|
||
"<code>a.intersect(b)</code>",
|
||
"<code>a.toSet().intersection(b.toSet())</code>",
|
||
"<code>a.union(b)</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "List 没有交集方法,要先转 Set。<code>intersection()</code> 返回两个集合的共同元素;<code>union()</code> 是并集。注意方法名是 <code>intersection()</code>,不是其它语言的 <code>intersect()</code>。"
|
||
},
|
||
{
|
||
"id": 112,
|
||
"lv": 3,
|
||
"q": "计算购物车总价:<code>List<double> prices = [];</code> 空列表调用 <code>prices.reduce((a, b) => a + b)</code> 会?",
|
||
"opts": [
|
||
"返回 0",
|
||
"抛出 StateError 异常",
|
||
"返回 null",
|
||
"返回空列表"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<code>reduce</code> 要求列表<b>至少有一个元素</b>,空列表直接抛 \"Bad state: No element\"。更稳的写法是 <code>fold(0, (a, b) => a + b)</code>:带初始值,空列表返回 0。"
|
||
},
|
||
{
|
||
"id": 113,
|
||
"lv": 4,
|
||
"q": "写日期工具类,希望 <code>DateUtil.parse(\"2024-01-01\")</code> 在字符串不合法时返回 null 而不是抛异常,用哪种构造函数?",
|
||
"opts": [
|
||
"factory 构造函数",
|
||
"普通构造函数",
|
||
"命名构造函数",
|
||
"const 构造函数"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>factory</b> 构造函数可以不返回新实例——可以返回缓存实例、子类实例,甚至返回 null。它把\"按需创建/校验\"的逻辑封装在构造入口,调用方写法统一且安全。"
|
||
},
|
||
{
|
||
"id": 114,
|
||
"lv": 4,
|
||
"q": "日志打印、权限检查这类能力要被多个<b>互不相关</b>的类复用,又不想强制继承关系,Dart 的推荐做法是?",
|
||
"opts": [
|
||
"<code>extends</code> 继承",
|
||
"<code>implements</code> 实现",
|
||
"<code>with</code> 混入 Mixin",
|
||
"<code>abstract</code> 抽象类"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>Mixin</b>(<code>with</code>)把可复用逻辑抽出来按需混入,避免\"为了复用而强行继承\"的长继承链。Flutter 源码大量使用,如 <code>class _X with RouteAware</code>。"
|
||
},
|
||
{
|
||
"id": 115,
|
||
"lv": 4,
|
||
"q": "类内部要声明一个\"仅供本文件使用\"的私有字段,正确写法是?",
|
||
"opts": [
|
||
"<code>private String _token;</code>",
|
||
"<code>String _token;</code>",
|
||
"<code>-String token;</code>",
|
||
"<code>#token</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "Dart 没有 <code>private</code> 关键字,以<b>下划线开头</b>表示库内私有:<code>_token</code>。注意这是\"库\"级别私有(同文件可见),不是类级别。"
|
||
},
|
||
{
|
||
"id": 116,
|
||
"lv": 4,
|
||
"q": "用户类的 <code>name</code> 字段希望<b>赋值时自动去掉首尾空格</b>,最佳做法是?",
|
||
"opts": [
|
||
"声明 getter/setter,在 setter 里 trim",
|
||
"直接写公共字段,调用方自己 trim",
|
||
"用 final 禁止修改",
|
||
"用 static 修饰"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>setter</b> 在赋值入口统一处理,所有调用方(包括构造函数)都自动享受规范化,不用每个地方都记得 trim。写法:<code>String get name</code> 与 <code>set name(String v)</code>。"
|
||
},
|
||
{
|
||
"id": 117,
|
||
"lv": 4,
|
||
"q": "<code>class Dog implements Animal</code> 与 <code>extends</code> 继承的关键区别是?",
|
||
"opts": [
|
||
"implements 要求实现 Animal 的<b>全部</b>成员(含字段),extends 可复用已有实现",
|
||
"两者完全一样",
|
||
"implements 只能用于抽象类",
|
||
"extends 要求实现全部成员"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>implements</code> 把 Animal 当<b>接口</b>:所有成员都要自己重写,即使父类已有实现;<code>extends</code> 继承实现,只覆写需要改的部分。implements 用于\"规定能力\",extends 用于\"复用实现\"。"
|
||
},
|
||
{
|
||
"id": 118,
|
||
"lv": 5,
|
||
"q": "用户对象可能为 null:<code>User? u = await fetchUser();</code>,以下哪种使用方式更安全?",
|
||
"opts": [
|
||
"<code>if (u != null) { print(u.name); }</code> 判空后直接用 u",
|
||
"<code>if (u != null) { print(u!.name); }</code> 必须加 !",
|
||
"<code>print(u?.name ?? \"\");</code>",
|
||
"A 和 C 都可以"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<b>A</b>:局部变量判空后编译器会<b>自动提升</b>为非空类型,直接使用即可;<b>C</b>:<code>?.</code> 安全调用 + <code>??</code> 兜底,一行搞定。只有被闭包捕获的变量才需要 <code>!</code>。"
|
||
},
|
||
{
|
||
"id": 119,
|
||
"lv": 5,
|
||
"q": "详情页要同时请求\"用户信息\"和\"订单列表\"两个接口,都拿到再渲染,用哪个方法?",
|
||
"opts": [
|
||
"<code>Future.wait([fetchUser(), fetchOrders()])</code>",
|
||
"串行写两个 await",
|
||
"<code>Future.any</code>",
|
||
"<code>Future.delayed</code> 等待"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>Future.wait</b> 并发启动多个 Future,全部完成后统一返回结果列表,总耗时约等于最慢的那个;串行 await 则是两个请求耗时相加。<code>Future.any</code> 只等第一个完成的。"
|
||
},
|
||
{
|
||
"id": 120,
|
||
"lv": 5,
|
||
"q": "某个字段要等登录成功后才能赋值,但又想声明成非空类型,正确声明方式是?",
|
||
"opts": [
|
||
"<code>late String token;</code>",
|
||
"<code>String? token = \"\";</code>",
|
||
"<code>dynamic token;</code>",
|
||
"<code>final token = null;</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>late</b> 表示\"推迟初始化,首次访问时才赋值\",适合依赖异步结果的字段或计算代价高的属性。注意:late 变量在赋值前被访问会抛 LateInitializationError。"
|
||
},
|
||
{
|
||
"id": 121,
|
||
"lv": 5,
|
||
"q": "把解析结果转成目标类型:<code>var age = data[\"age\"] as int;</code>,当 age 实际是 String 时会发生?",
|
||
"opts": [
|
||
"返回 null",
|
||
"抛出 TypeError(类型转换失败)",
|
||
"静默忽略",
|
||
"自动转成 int"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<code>as</code> 是<b>强制类型转换</b>,类型不符立即抛异常。安全做法:先用 <code>is</code> 判断类型,或先统一规范数据(如 num 一律 <code>.toInt()</code>)。"
|
||
},
|
||
{
|
||
"id": 122,
|
||
"lv": 5,
|
||
"q": "对可能为 null 的 <code>String? s</code> 使用 <code>s!</code>,若 s 确实为 null 会?",
|
||
"opts": [
|
||
"返回 null",
|
||
"抛出 TypeError(空检查失败)",
|
||
"静默继续",
|
||
"编译错误"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<code>!</code> 是<b>强制解包</b>运算符,向编译器承诺\"这里不可能为 null\",承诺落空时运行时抛 TypeError。能用判空提升或 <code>??</code> 的地方尽量别用 <code>!</code>,减少运行时崩溃点。"
|
||
},
|
||
{
|
||
"id": 123,
|
||
"lv": 6,
|
||
"q": "Flutter 中常见 <code>ButtonStyle()..backgroundColor = ... ..shape = ...</code> 写法,<code>..</code> 级联操作符的核心行为是?",
|
||
"opts": [
|
||
"对同一对象连续操作且返回对象本身",
|
||
"链式调用并返回最后一次结果",
|
||
"只能用于 List",
|
||
"与 <code>...</code> 展开等价"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>级联操作符</b> <code>..</code> 对同一对象连续执行多个操作,<b>不改变返回值</b>(始终是原对象),适合集中配置对象。<code>.</code> 返回的是方法调用的返回值,两者行为不同。"
|
||
},
|
||
{
|
||
"id": 124,
|
||
"lv": 6,
|
||
"q": "Dart 3 中,函数想直接返回\"用户名 + 年龄\"这组小数据,又不想专门建一个类,推荐用?",
|
||
"opts": [
|
||
"<code>Map<String, dynamic></code>",
|
||
"Record 记录类型:<code>(String name, int age)</code>",
|
||
"<code>List<dynamic></code>",
|
||
"用全局变量"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>Record</b> 是 Dart 3 的轻量聚合类型:<code>var r = (\"张三\", 18);</code> 或命名字段 <code>(name: \"张三\", age: 18)</code>,用 <code>r.$1</code> / <code>r.name</code> 取值,还能配合模式匹配解构,比 Map 更类型安全。"
|
||
},
|
||
{
|
||
"id": 125,
|
||
"lv": 6,
|
||
"q": "Dart 3 的 switch 可以直接当表达式赋值:<code>var s = switch (status) { \"ok\" => \"成功\", _ => \"未知\" };</code> 这种写法?",
|
||
"opts": [
|
||
"合法:switch 表达式返回一个值",
|
||
"非法:switch 不能作表达式",
|
||
"必须每个分支加 break",
|
||
"只能匹配字符串"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>switch 表达式</b>用 <code>=></code> 代替冒号,整体作为值赋给变量,配合模式匹配可以解构、判断类型,比旧式 switch 简洁,也天然避免了漏 break 的坑。"
|
||
},
|
||
{
|
||
"id": 126,
|
||
"lv": 6,
|
||
"q": "想给系统类 <code>String</code> 加一个 <code>isEmail()</code> 方法,但 String 不能改源码,用?",
|
||
"opts": [
|
||
"<b>extension</b> 扩展:<code>extension EmailCheck on String { bool isEmail() {...} }</code>",
|
||
"继承 String",
|
||
"用 mixin 混入",
|
||
"写静态工具类"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>extension</b> 可以不修改原类给已有类型添加方法,之后 <code>\"a@b.com\".isEmail()</code> 直接调用。注意扩展方法是静态分发(编译期决定),不参与运行时多态。"
|
||
},
|
||
{
|
||
"id": 127,
|
||
"lv": 6,
|
||
"q": "泛型容器 <code>class Box<T extends num> {}</code> 中 <code>extends num</code> 的含义是?",
|
||
"opts": [
|
||
"T 只能是 int、double 等数值类型",
|
||
"T 可以是任意类型",
|
||
"T 必须可空",
|
||
"T 必须是集合类型"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>泛型约束</b>限定 T 必须是 num 及其子类(int/double),这样 Box 内部就能安全调用数值运算。不加约束时 T 等价于 <code>Object?</code>。"
|
||
}
|
||
],
|
||
"lvNames": [
|
||
"基础语法",
|
||
"控制流与函数",
|
||
"集合",
|
||
"面向对象",
|
||
"空安全与异步",
|
||
"Dart 高级特性"
|
||
]
|
||
},
|
||
{
|
||
"id": 10,
|
||
"name": "Flutter 开发实战",
|
||
"questions": [
|
||
{
|
||
"id": 128,
|
||
"lv": 1,
|
||
"q": "新建 Flutter 项目后,把应用真正跑起来的入口代码是?",
|
||
"opts": [
|
||
"<code>void main() => runApp(MyApp());</code>",
|
||
"<code>void main() { MyApp(); }</code>",
|
||
"<code>runApp() { MyApp }</code>",
|
||
"<code>main() { MaterialApp(); }</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Dart 程序入口是 <code>main()</code>,Flutter 通过 <code>runApp(Widget)</code> 把根组件挂载到引擎上。注意 <code>MyApp()</code> 是创建 Widget 实例,不能少了括号。"
|
||
},
|
||
{
|
||
"id": 129,
|
||
"lv": 1,
|
||
"q": "实现一个\"3 秒倒计时\"按钮,每秒刷新显示剩余秒数,应使用?",
|
||
"opts": [
|
||
"StatelessWidget",
|
||
"StatefulWidget",
|
||
"Container",
|
||
"Scaffold"
|
||
],
|
||
"ans": 1,
|
||
"exp": "有<b>动态变化的数据</b>(倒计时数字)就需要 StatefulWidget:数据保存在 State 里,变化时调 <code>setState()</code> 刷新。没有可变状态时应优先用 StatelessWidget。"
|
||
},
|
||
{
|
||
"id": 130,
|
||
"lv": 1,
|
||
"q": "界面上要显示一行固定文案\"登录成功\",最合适的组件是?",
|
||
"opts": [
|
||
"Text",
|
||
"Label",
|
||
"Paragraph",
|
||
"RichText"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Text</code> 是最常用的文本组件,可通过 style 配置字号、颜色、字重等。RichText 用于一段文字中混排多种样式的富文本场景。"
|
||
},
|
||
{
|
||
"id": 131,
|
||
"lv": 1,
|
||
"q": "点击\"确认\"按钮后分数要 +1 并立即显示到界面,正确流程是?",
|
||
"opts": [
|
||
"修改 State 字段后调用 <code>setState()</code>",
|
||
"直接修改字段就行",
|
||
"重启整个 App",
|
||
"修改 Widget 的构造参数"
|
||
],
|
||
"ans": 0,
|
||
"exp": "State 里的字段只是普通数据,必须调用 <code>setState()</code> 通知框架\"数据变了\",框架才会重新执行 build 刷新 UI。注意 setState 只重建<b>当前 State 的子树</b>,不是整棵树。"
|
||
},
|
||
{
|
||
"id": 132,
|
||
"lv": 1,
|
||
"q": "要展示 10000 条商品记录,性能最优的做法是?",
|
||
"opts": [
|
||
"<code>ListView.builder</code>(按需构建可见项)",
|
||
"Column 里循环放 10000 个组件",
|
||
"SingleChildScrollView 包 Column",
|
||
"GridView.count 全部构建"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>ListView.builder</code> 只构建<b>当前可见</b>的列表项(懒加载),滚动时回收复用;Column + for 会一次性构建全部组件,万条数据直接卡死。数据量大优先 builder 系列。"
|
||
},
|
||
{
|
||
"id": 133,
|
||
"lv": 2,
|
||
"q": "登录页要垂直排列\"输入框、输入框、登录按钮\",用哪个布局组件?",
|
||
"opts": [
|
||
"Column",
|
||
"Row",
|
||
"Stack",
|
||
"Wrap"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Column</code> 垂直方向排列子组件,<code>Row</code> 水平方向,<code>Stack</code> 叠加并支持定位,<code>Wrap</code> 在空间不足时自动换行。"
|
||
},
|
||
{
|
||
"id": 134,
|
||
"lv": 2,
|
||
"q": "Row 里想让一个按钮<b>占满剩余宽度</b>(剩余空间全给它),按钮外层套?",
|
||
"opts": [
|
||
"<code>Expanded</code>",
|
||
"<code>Padding</code>",
|
||
"<code>Center</code>",
|
||
"<code>Spacer</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Expanded</code> 让子组件占据主轴的剩余空间(Row 的主轴是水平方向)。多个 Expanded 可通过 flex 参数按比例分配空间。"
|
||
},
|
||
{
|
||
"id": 135,
|
||
"lv": 2,
|
||
"q": "Column 里有几个按钮,想让它们在<b>水平方向居中</b>,应该设置?",
|
||
"opts": [
|
||
"<code>crossAxisAlignment: CrossAxisAlignment.center</code>",
|
||
"<code>mainAxisAlignment: MainAxisAlignment.center</code>",
|
||
"<code>alignment: Alignment.center</code>",
|
||
"<code>textAlign: TextAlign.center</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Column 的<b>主轴是垂直</b>方向、<b>交叉轴是水平</b>方向。水平居中=沿交叉轴居中=crossAxisAlignment.center;mainAxisAlignment 控制的是垂直方向。主/交叉轴搞反是新手最常踩的坑。"
|
||
},
|
||
{
|
||
"id": 136,
|
||
"lv": 2,
|
||
"q": "封面图要<b>铺满整个区域且不拉伸变形</b>(多出的部分裁掉),fit 参数应该用?",
|
||
"opts": [
|
||
"<code>BoxFit.cover</code>",
|
||
"<code>BoxFit.fill</code>",
|
||
"<code>BoxFit.none</code>",
|
||
"<code>BoxFit.scaleDown</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>cover</b>:等比缩放填满、超出裁剪,常用于头像、封面;<b>fill</b> 会拉伸变形;<b>contain</b> 完整显示但可能留白;<b>none</b> 不缩放。"
|
||
},
|
||
{
|
||
"id": 137,
|
||
"lv": 2,
|
||
"q": "商品名过长时希望<b>单行显示,超出部分显示省略号</b>,Text 的配置是?",
|
||
"opts": [
|
||
"<code>maxLines: 1, overflow: TextOverflow.ellipsis</code>",
|
||
"只设 overflow: TextOverflow.clip",
|
||
"只设 maxLines: 3",
|
||
"<code>softWrap: false</code> 就够了"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>maxLines</code> 限制最大行数 + <code>overflow: TextOverflow.ellipsis</code> 指定超出的表现(省略号/裁剪/fade),两者配合才能出现\"…\"效果。"
|
||
},
|
||
{
|
||
"id": 138,
|
||
"lv": 3,
|
||
"q": "输入框需要\"预填默认值 + 按钮点击后读取内容 + 随时清空\",推荐用?",
|
||
"opts": [
|
||
"<code>TextEditingController</code>",
|
||
"每次读 onChanged 参数",
|
||
"GlobalKey 临时查找",
|
||
"无法实现"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>TextEditingController</code> 是输入框的\"数据源\":<code>TextEditingController(text: \"预填\")</code> 初始化,<code>controller.text</code> 读取,<code>controller.clear()</code> 清空。用完后在 dispose 里释放。"
|
||
},
|
||
{
|
||
"id": 139,
|
||
"lv": 3,
|
||
"q": "页面销毁时,取消监听、释放 Controller 的代码应该写在哪里?",
|
||
"opts": [
|
||
"<code>dispose()</code>",
|
||
"<code>initState()</code>",
|
||
"<code>build()</code>",
|
||
"<code>didChangeDependencies()</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>dispose()</code> 是 State 被移除时的清理钩子:取消 Timer、Stream 订阅、释放 Controller。不清理会造成内存泄漏和 \"setState called after dispose\" 报错。"
|
||
},
|
||
{
|
||
"id": 140,
|
||
"lv": 3,
|
||
"q": "登录状态、用户信息要被<b>多个页面共享</b>,又不想层层传构造参数,项目常用方案是?",
|
||
"opts": [
|
||
"Provider / InheritedWidget",
|
||
"定义全局变量直接读写",
|
||
"每个页面重新请求接口",
|
||
"把状态塞进路由参数"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Flutter 官方的底层方案是 <b>InheritedWidget</b>,<b>Provider</b> 是对它的优雅封装:共享数据放顶层,子组件 <code>context.watch()</code> 取用并自动刷新。全局变量没有通知机制,页面不会自动更新。"
|
||
},
|
||
{
|
||
"id": 141,
|
||
"lv": 3,
|
||
"q": "商品列表页要支持<b>下拉刷新</b>,应该把 ListView 包进哪个组件?",
|
||
"opts": [
|
||
"<code>RefreshIndicator</code>",
|
||
"<code>GestureDetector</code>",
|
||
"<code>AnimatedList</code>",
|
||
"<code>Scrollbar</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>RefreshIndicator</code> 包住可滚动组件,配合 <code>onRefresh</code> 返回的 Future 实现下拉刷新动画。这是 Material 规范自带的下拉刷新方案。"
|
||
},
|
||
{
|
||
"id": 142,
|
||
"lv": 3,
|
||
"q": "App 切到后台、再回到前台时,需要暂停/恢复计时器,通过什么监听?",
|
||
"opts": [
|
||
"<code>WidgetsBindingObserver</code>",
|
||
"<code>RouteObserver</code>",
|
||
"<code>NavigatorObserver</code>",
|
||
"<code>MediaQuery</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "给 State 混入 <code>with WidgetsBindingObserver</code>,在 <code>didChangeAppLifecycleState()</code> 中监听 AppLifecycleState(paused / resumed / inactive),即可感知前后台切换。"
|
||
},
|
||
{
|
||
"id": 143,
|
||
"lv": 4,
|
||
"q": "列表页点击商品跳转详情页,标准写法是?",
|
||
"opts": [
|
||
"<code>Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage(id: id)))</code>",
|
||
"<code>Navigator.go(DetailPage(id: id))</code>",
|
||
"<code>Route.open(DetailPage())</code>",
|
||
"<code>DetailPage(id: id).show()</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Navigator.push</code> 把新路由压入导航栈,MaterialPageRoute 提供转场动画,返回上一页用 <code>Navigator.pop(context)</code>。"
|
||
},
|
||
{
|
||
"id": 144,
|
||
"lv": 4,
|
||
"q": "详情页编辑完要把结果<b>回传给列表页</b>,详情页返回时应该写?",
|
||
"opts": [
|
||
"<code>Navigator.pop(context, result)</code>",
|
||
"<code>Navigator.back(result)</code>",
|
||
"<code>Navigator.pushResult(result)</code>",
|
||
"<code>context.popAndPush(result)</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>pop(context, 返回值)</code> 的第二个参数就是回传的数据,列表页 <code>await push(...)</code> 的返回值即可收到。注意 pop 的参数是\"返回给上一页的数据\",不是新路由。"
|
||
},
|
||
{
|
||
"id": 145,
|
||
"lv": 4,
|
||
"q": "跳转时需要传递商品 id、名称等参数,团队协作中<b>最推荐</b>的传参方式是?",
|
||
"opts": [
|
||
"<code>DetailPage(id: 1, name: \"商品\")</code> 构造参数传递",
|
||
"全局变量临时存",
|
||
"static 静态字段存",
|
||
"URL 字符串拼接"
|
||
],
|
||
"ans": 0,
|
||
"exp": "构造参数显式、类型安全、可读性好——跳转处一眼看到传了什么。全局变量/静态字段是隐藏依赖,页面多起来后非常难维护。"
|
||
},
|
||
{
|
||
"id": 146,
|
||
"lv": 4,
|
||
"q": "在 App 里注册命名路由(如 <code>/login</code>、<code>/home</code>),配置位置是?",
|
||
"opts": [
|
||
"<code>MaterialApp(routes: {...})</code>",
|
||
"<code>main()</code> 函数里",
|
||
"<code>runApp()</code> 参数",
|
||
"每个页面里各自声明"
|
||
],
|
||
"ans": 0,
|
||
"exp": "命名路由集中配置在 <code>MaterialApp</code> 的 <code>routes</code> 表,之后用 <code>Navigator.pushNamed(context, \"/login\")</code> 跳转;需要传参时配合 <code>arguments</code> 参数。"
|
||
},
|
||
{
|
||
"id": 147,
|
||
"lv": 4,
|
||
"q": "跳到选择页,用户选完返回后要根据<b>选择结果</b>继续处理,列表页怎么写?",
|
||
"opts": [
|
||
"<code>final r = await Navigator.push(...);</code> 拿到返回值再处理",
|
||
"无法等待结果",
|
||
"用 setState 轮询",
|
||
"跳转时把回调函数传过去"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>push</code> 返回 <code>Future<T></code>,await 后拿到 pop 回传的值,再执行后续逻辑(如刷新选中项)。这是\"页面间取结果\"的标准姿势。"
|
||
},
|
||
{
|
||
"id": 148,
|
||
"lv": 5,
|
||
"q": "接口返回 JSON 字符串,执行 <code>var data = jsonDecode(text);</code> 后 data 的类型是?",
|
||
"opts": [
|
||
"<code>dynamic</code>(实际可能是 Map 或 List)",
|
||
"<code>String</code>",
|
||
"<code>Map<String, dynamic></code>",
|
||
"<code>Object</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>jsonDecode</code> 的返回类型是 <code>dynamic</code>:JSON 对象变 Map<String, dynamic>、数组变 List<dynamic>、数字/布尔原样返回。通常需要手动转换:<code>jsonDecode(text) as Map<String, dynamic></code>。"
|
||
},
|
||
{
|
||
"id": 149,
|
||
"lv": 5,
|
||
"q": "要把用户对象 <code>User(id: 1, name: \"张三\")</code> 转成 JSON 字符串发给服务器,正确流程是?",
|
||
"opts": [
|
||
"给 User 写 <code>toJson()</code>,再 <code>jsonEncode(user.toJson())</code>",
|
||
"<code>jsonEncode(user)</code> 直接序列化",
|
||
"<code>user.toString()</code>",
|
||
"<code>JSON.stringify(user)</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Dart 默认不会自动序列化任意对象,惯例是模型类自己实现 <code>Map<String, dynamic> toJson()</code>,再用 <code>jsonEncode</code> 转字符串;反序列化则对应 <code>User.fromJson(Map)</code> 工厂构造。"
|
||
},
|
||
{
|
||
"id": 150,
|
||
"lv": 5,
|
||
"q": "网络请求可能超时/断网,用户点\"登录\"时抛出了异常,正确的处理是?",
|
||
"opts": [
|
||
"用 try-catch 包裹 await 请求,捕获异常并提示用户",
|
||
"不做处理",
|
||
"打印日志后忽略",
|
||
"延迟 3 秒自动重试"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>try { await api.login(...) } catch (e) { 提示\"网络异常,请重试\" }</code> 是最基础的错误处理;生产环境还会区分 SocketException(断网)、TimeoutException(超时)等做分类提示。"
|
||
},
|
||
{
|
||
"id": 151,
|
||
"lv": 5,
|
||
"q": "请求进行中需要展示\"加载中\"状态,最合适用?",
|
||
"opts": [
|
||
"<code>CircularProgressIndicator</code>",
|
||
"Text(\"加载中...\")",
|
||
"Opacity 半透明遮罩",
|
||
"AnimatedContainer 闪烁"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Material 标准的加载指示器是 <code>CircularProgressIndicator</code>(转圈)和 <code>LinearProgressIndicator</code>(条),配合 loading 状态变量做条件渲染。"
|
||
},
|
||
{
|
||
"id": 152,
|
||
"lv": 5,
|
||
"q": "http 请求要设置 10 秒超时,正确写法是?",
|
||
"opts": [
|
||
"<code>await http.get(url).timeout(Duration(seconds: 10))</code>",
|
||
"<code>http.get(url, timeout: 10)</code>",
|
||
"<code>Timer(Duration(seconds: 10), ...)</code>",
|
||
"<code>Future.any</code> 自动超时"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Future 自带 <code>.timeout()</code>:超时抛 TimeoutException,可再接 <code>onTimeout</code> 参数兜底。http 包本身没有 timeout 参数,这是常见的误解。"
|
||
},
|
||
{
|
||
"id": 153,
|
||
"lv": 6,
|
||
"q": "首页同时请求\"轮播图、公告、商品列表\"三个接口,全部完成再渲染页面,用?",
|
||
"opts": [
|
||
"<code>Future.wait([...])</code> 并发等待",
|
||
"三个 await 串行",
|
||
"依次 catch 分别显示",
|
||
"用 <code>Future.any</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "并发场景用 <code>Future.wait</code>,总耗时约等于最慢的请求而不是三个相加。生产上可配合记录单个请求失败做局部降级(某块失败不影响整页)。"
|
||
},
|
||
{
|
||
"id": 154,
|
||
"lv": 6,
|
||
"q": "商品列表滑到底部要自动加载下一页,监听滚动的方案是?",
|
||
"opts": [
|
||
"<code>ScrollController</code> 监听 pixels 接近 maxScrollExtent 时触发",
|
||
"用 Timer 每 3 秒加载",
|
||
"下拉刷新代替",
|
||
"GestureDetector 监听手势"
|
||
],
|
||
"ans": 0,
|
||
"exp": "给 ListView 挂 <code>ScrollController</code>,在 <code>addListener</code> 里判断 <code>position.pixels >= position.maxScrollExtent - 200</code>(预留阈值)就加载下一页,配合 loading 状态防止重复触发。"
|
||
},
|
||
{
|
||
"id": 155,
|
||
"lv": 6,
|
||
"q": "商品标题中要<b>红色高亮\"热卖\"二字</b>、其余部分灰色,用?",
|
||
"opts": [
|
||
"<code>RichText</code> + <code>TextSpan</code> 组合不同样式",
|
||
"拆成多个 Text 排一排",
|
||
"用换行符拼接字符串",
|
||
"TextStyle 做不到,只能整段同色"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>RichText</code> 的 <code>TextSpan</code> 支持树状嵌套:每个 span 可以有自己的 style,是\"一段文字多种样式\"的标准方案。"
|
||
},
|
||
{
|
||
"id": 156,
|
||
"lv": 6,
|
||
"q": "界面出现<b>黄黑条纹</b>溢出警告(RenderFlex overflowed),说明?",
|
||
"opts": [
|
||
"子组件超出了父组件给定的约束空间",
|
||
"主题颜色配置错误",
|
||
"内存泄漏",
|
||
"字体加载失败"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Row/Column 空间不够时子组件溢出,debug 模式显示黄黑条纹并打印 \"RenderFlex overflowed by XX pixels\"。解决思路:Expanded/Flexible 让出空间、SingleChildScrollView 滚动、FittedBox 缩放。"
|
||
},
|
||
{
|
||
"id": 157,
|
||
"lv": 6,
|
||
"q": "瀑布流商品图列表很卡、图片解码占内存高,优先优化手段是?",
|
||
"opts": [
|
||
"<code>cacheWidth</code> / <code>ResizeImage</code> 限制解码尺寸",
|
||
"给图片加圆角",
|
||
"提高图片的清晰度",
|
||
"减少列表项数量"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Image.network 默认按原图尺寸解码,一张 4000px 大图在手机上就是内存杀手。用 <code>cacheWidth: 300</code> 或 <code>ResizeImage</code> 让引擎按需解码成小尺寸位图,内存占用和滚动性能立竿见影。"
|
||
}
|
||
],
|
||
"lvNames": [
|
||
"Widget 基础",
|
||
"布局与组件",
|
||
"状态管理",
|
||
"路由与导航",
|
||
"数据与网络",
|
||
"实战综合"
|
||
]
|
||
},
|
||
{
|
||
"id": 11,
|
||
"name": "Dart 基础巩固",
|
||
"lvNames": [
|
||
"基础语法",
|
||
"控制流与函数",
|
||
"集合",
|
||
"面向对象",
|
||
"空安全与异步",
|
||
"Dart 高级特性"
|
||
],
|
||
"questions": [
|
||
{
|
||
"id": 158,
|
||
"lv": 1,
|
||
"q": "Dart 中布尔类型 <code>bool</code> 的字面量写法是?",
|
||
"opts": [
|
||
"<code>TRUE</code>",
|
||
"<code>true</code>",
|
||
"<code>1</code>",
|
||
"<code>yes</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "bool 只有两个值:<b>true</b> 和 <b>false</b>(全小写)。TRUE、1、yes 都不是 Dart 的合法写法。"
|
||
},
|
||
{
|
||
"id": 159,
|
||
"lv": 1,
|
||
"q": "要存储 3.14 这样的浮点数,应声明为什么类型?",
|
||
"opts": [
|
||
"<code>float</code>",
|
||
"<code>decimal</code>",
|
||
"<code>big</code>",
|
||
"<code>double</code>"
|
||
],
|
||
"ans": 3,
|
||
"exp": "浮点数用 <b>double</b>,整数用 <b>int</b>,两者的共同父类是 <b>num</b>。Dart 没有 float、decimal 类型。"
|
||
},
|
||
{
|
||
"id": 160,
|
||
"lv": 1,
|
||
"q": "定义一个内容跨多行的字符串,应该用哪组引号包裹?",
|
||
"opts": [
|
||
"<code>'''</code> 三个单引号(或 <code>\"\"\"</code>)",
|
||
"<code>\"</code> 单个双引号",
|
||
"<code>'</code> 单个单引号",
|
||
"<code>`</code> 反引号"
|
||
],
|
||
"ans": 0,
|
||
"exp": "三个单引号 <code>'''</code> 或三个双引号 <code>\"\"\"</code> 包裹的字符串<b>可以直接跨行书写</b>,换行会被保留;单引号/双引号字符串内不能直接换行。Dart 没有反引号模板字符串。"
|
||
},
|
||
{
|
||
"id": 161,
|
||
"lv": 1,
|
||
"q": "关于 Dart 字符串的特性,以下说法正确的是?",
|
||
"opts": [
|
||
"字符串创建后可随意修改内部字符",
|
||
"字符串只能用双引号包裹",
|
||
"字符串方法返回新字符串,原字符串不变",
|
||
"字符串本质是数字类型"
|
||
],
|
||
"ans": 2,
|
||
"exp": "Dart 字符串<b>不可变</b>:<code>toUpperCase()</code> 等方法返回<b>新字符串</b>,原值不变。频繁拼接字符串时建议用 <b>StringBuffer</b> 提升性能。"
|
||
},
|
||
{
|
||
"id": 162,
|
||
"lv": 1,
|
||
"q": "<code>String s = \"Hello\";</code> 要得到全大写的 \"HELLO\",应调用?",
|
||
"opts": [
|
||
"<code>s.upper()</code>",
|
||
"<code>s.toUpperCase()</code>",
|
||
"<code>s.up()</code>",
|
||
"<code>String.upper(s)</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "转大写用 <b>s.toUpperCase()</b>,转小写用 <code>s.toLowerCase()</code>。注意返回的是<b>新字符串</b>,原字符串不变。"
|
||
},
|
||
{
|
||
"id": 163,
|
||
"lv": 1,
|
||
"q": "把数字 42 转成字符串 \"42\",正确写法是?",
|
||
"opts": [
|
||
"<code>(String) 42</code>",
|
||
"<code>int.stringify(42)</code>",
|
||
"<code>42.toString()</code>",
|
||
"<code>String.valueOf(42)</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "调用 <b>toString()</b>:<code>42.toString()</code>。注意 double 的 toString 会带小数:<code>3.0.toString()</code> 得到 \"3.0\"。(String) 强转、String.valueOf 是其它语言的习惯,Dart 没有。"
|
||
},
|
||
{
|
||
"id": 164,
|
||
"lv": 2,
|
||
"q": "取 a、b 中较大的值,用一行表达式应写?",
|
||
"opts": [
|
||
"<code>a ?? b</code>",
|
||
"<code>a > b ? a : b</code>",
|
||
"<code>a || b</code>",
|
||
"<code>a && b</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>三元运算符</b> <code>条件 ? 表达式1 : 表达式2</code>:条件成立取第一个,否则取第二个。<code>a ?? b</code> 是空值合并(a 为 null 才取 b),|| 和 && 是逻辑运算,都不能比较大小。"
|
||
},
|
||
{
|
||
"id": 165,
|
||
"lv": 2,
|
||
"q": "<code>bool ok = false; print(ok && check());</code> 中 check() 会被执行吗?",
|
||
"opts": [
|
||
"会执行",
|
||
"报错",
|
||
"不会执行,&& 左侧为 false 时短路",
|
||
"执行两次"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>短路求值</b>:<code>&&</code> 左侧为 false 时结果已确定为 false,<b>右侧不会执行</b>;<code>||</code> 左侧为 true 时同理。常用于安全判断:<code>user != null && user.isVip</code>。"
|
||
},
|
||
{
|
||
"id": 166,
|
||
"lv": 2,
|
||
"q": "循环里要<b>跳过当前这一次迭代</b>、继续下一次,用哪个关键字?",
|
||
"opts": [
|
||
"<code>break</code>",
|
||
"<code>return</code>",
|
||
"<code>continue</code>",
|
||
"<code>skip</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>continue</b> 结束本次迭代、立即进入下一轮;<b>break</b> 直接退出整个循环;return 退出的是整个函数。"
|
||
},
|
||
{
|
||
"id": 167,
|
||
"lv": 2,
|
||
"q": "<code>var f = (x) => x * 2;</code> 这种没有名字的函数写法是?",
|
||
"opts": [
|
||
"命名函数",
|
||
"构造函数",
|
||
"静态函数",
|
||
"匿名函数(Lambda 表达式)"
|
||
],
|
||
"ans": 3,
|
||
"exp": "箭头 <b>=></b> 配合无名参数列表就是<b>匿名函数 / Lambda</b>,常作为回调、比较器传递:<code>list.sort((a, b) => a - b)</code>。"
|
||
},
|
||
{
|
||
"id": 168,
|
||
"lv": 2,
|
||
"q": "<code>void greet(String name, [String word = \"你好\"])</code>,调用 <code>greet(\"小明\")</code> 时 word 的值是?",
|
||
"opts": [
|
||
"<code>null</code>",
|
||
"报错",
|
||
"<code>\"你好\"</code>",
|
||
"空字符串"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>[ ]</b> 包裹的是<b>可选位置参数</b>,不传时使用默认值 <b>\"你好\"</b>。它与命名参数({} 包裹、必须按名传)不同,是按顺序传参的:<code>greet(\"小明\", \"早上好\")</code>。"
|
||
},
|
||
{
|
||
"id": 169,
|
||
"lv": 2,
|
||
"q": "循环里要<b>立即终止整个循环</b>,用哪个关键字?",
|
||
"opts": [
|
||
"<code>break</code>",
|
||
"<code>continue</code>",
|
||
"<code>exit</code>",
|
||
"<code>stop</code>"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<b>break</b> 立即结束当前循环;continue 只跳过本次。break 也常用于 switch 的 case 结尾——Dart 要求非空 case 必须以 break、return、throw 等结束。"
|
||
},
|
||
{
|
||
"id": 170,
|
||
"lv": 3,
|
||
"q": "<code>var data = {'name': '张三', 'age': 18};</code> data 的类型是?",
|
||
"opts": [
|
||
"<code>Set</code>",
|
||
"<code>List</code>",
|
||
"<code>Map</code>",
|
||
"<code>Tuple</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>{}</b> 且含冒号键值对时创建 <b>Map</b>;无冒号的 <code>{1, 2, 3}</code> 是 Set;<code>[]</code> 是 List。Dart 没有 Tuple 类型。"
|
||
},
|
||
{
|
||
"id": 171,
|
||
"lv": 3,
|
||
"q": "<code>var map = {'a': 1}; map['b'] = 2;</code> 之后 map 的内容是?",
|
||
"opts": [
|
||
"<code>{'a': 1}</code>",
|
||
"<code>{'b': 2}</code>",
|
||
"报错",
|
||
"<code>{'a': 1, 'b': 2}</code>"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<code>map[key] = value</code> 写入时,key 已存在则<b>更新值</b>,不存在则<b>新增条目</b>,所以得到 <code>{'a': 1, 'b': 2}</code>。"
|
||
},
|
||
{
|
||
"id": 172,
|
||
"lv": 3,
|
||
"q": "<code>['2024', '2025', '2026'].join('-')</code> 的结果是?",
|
||
"opts": [
|
||
"<code>'2024,2025,2026'</code>",
|
||
"报错",
|
||
"<code>'2024-2025-2026'</code>",
|
||
"<code>'202420252026'</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>join(分隔符)</b> 把列表元素按分隔符拼成一个字符串,得到 <code>2024-2025-2026</code>。注意不传参数时是<b>无分隔符</b>直接拼接。"
|
||
},
|
||
{
|
||
"id": 173,
|
||
"lv": 3,
|
||
"q": "从 <code>[1, 2, 3, 4]</code> 中挑出偶数(2、4),应调用哪个方法?",
|
||
"opts": [
|
||
"<code>map</code>",
|
||
"<code>reduce</code>",
|
||
"<code>addAll</code>",
|
||
"<code>where</code>"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<b>where()</b> 按条件<b>过滤</b>:<code>[1,2,3,4].where((n) => n % 2 == 0)</code> 得到 2、4。map() 是变换每个元素,reduce() 是聚合,addAll() 是批量添加。"
|
||
},
|
||
{
|
||
"id": 174,
|
||
"lv": 3,
|
||
"q": "<code>var nums = [3, 1, 2]; nums.sort();</code> 排序后 nums 是?",
|
||
"opts": [
|
||
"<code>[3, 1, 2]</code>",
|
||
"<code>[1, 2, 3]</code>",
|
||
"报错",
|
||
"<code>[2, 3, 1]</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>sort()</b> 原地<b>升序</b>排序,结果 <b>[1, 2, 3]</b>。注意它直接修改原列表并返回 void;要降序需传比较器:<code>sort((a, b) => b - a)</code>。"
|
||
},
|
||
{
|
||
"id": 175,
|
||
"lv": 3,
|
||
"q": "<code>[1, 2, 3].contains(5)</code> 返回?",
|
||
"opts": [
|
||
"<code>true</code>",
|
||
"<code>5</code>",
|
||
"<code>null</code>",
|
||
"<code>false</code>"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<b>contains()</b> 判断是否包含指定元素,包含返回 true、否则 <b>false</b>。判断 Map 的键用 <code>containsKey</code>,判断值用 <code>containsValue</code>。"
|
||
},
|
||
{
|
||
"id": 176,
|
||
"lv": 4,
|
||
"q": "<code>class Point { Point(this.x, this.y); int x; int y; }</code> 中 this.x 的作用是?",
|
||
"opts": [
|
||
"创建私有变量",
|
||
"引用父类构造函数",
|
||
"把构造参数自动赋给同名成员变量",
|
||
"声明静态字段"
|
||
],
|
||
"ans": 2,
|
||
"exp": "构造参数写成 <b>this.x</b> 时,Dart 会把参数值<b>自动赋给同名成员变量</b>,省去手写 <code>this.x = x;</code> 的样板代码。"
|
||
},
|
||
{
|
||
"id": 177,
|
||
"lv": 4,
|
||
"q": "想给 Point 提供便捷构造 <code>Point.origin()</code>(x、y 初始化为 0),应该用?",
|
||
"opts": [
|
||
"静态方法",
|
||
"构造重载",
|
||
"默认构造函数",
|
||
"命名构造函数"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<b>命名构造函数</b>:<code>Point.origin() : x = 0, y = 0;</code>,调用 <code>Point.origin()</code>。Dart 不支持构造重载,用命名构造区分多种初始化方式。"
|
||
},
|
||
{
|
||
"id": 178,
|
||
"lv": 4,
|
||
"q": "统计 Point 类一共创建了多少个实例,计数变量应声明为?",
|
||
"opts": [
|
||
"<code>final int count</code>",
|
||
"<code>static int count</code>",
|
||
"<code>int count</code>",
|
||
"<code>late int count</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>static</b> 成员属于<b>类本身</b>、所有实例共享一份,在构造函数里 <code>count++</code> 即可统计,读取用 <code>Point.count</code>,不需要实例。"
|
||
},
|
||
{
|
||
"id": 179,
|
||
"lv": 4,
|
||
"q": "私有字段 _name 想对外<b>只读</b>(能读不能写),应该声明?",
|
||
"opts": [
|
||
"<code>void name() {}</code>",
|
||
"<code>static String name;</code>",
|
||
"<code>String get name => _name;</code>",
|
||
"<code>set String name;</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>getter</b> 提供只读访问:<code>String get name => _name;</code>,外部用 <code>p.name</code> 读取;需要可写时再补 <code>set name(String v)</code>。"
|
||
},
|
||
{
|
||
"id": 180,
|
||
"lv": 4,
|
||
"q": "<code>@override</code> 注解放在重写方法上的作用是?",
|
||
"opts": [
|
||
"调用父类方法",
|
||
"声明重写父类方法,签名不符会编译报错",
|
||
"创建新方法",
|
||
"删除父类方法"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>@override</b> 声明\"此方法重写了父类/接口方法\",签名不一致时<b>编译报错</b>,防止手滑写错。调用父类实现用 <code>super.xxx()</code>。"
|
||
},
|
||
{
|
||
"id": 181,
|
||
"lv": 4,
|
||
"q": "子类构造函数里显式调用父类构造函数,用哪个关键字?",
|
||
"opts": [
|
||
"<code>this</code>",
|
||
"<code>base</code>",
|
||
"<code>super</code>",
|
||
"<code>parent</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "用 <b>super</b>:<code>Dog(String n) : super(n);</code>。父类有默认构造时可省略(自动调用);需要传参或指定父类命名构造时才显式写 super。"
|
||
},
|
||
{
|
||
"id": 182,
|
||
"lv": 5,
|
||
"q": "<code>String? s;</code>(可能为 null),直接写 <code>print(s.length)</code> 会?",
|
||
"opts": [
|
||
"输出 0",
|
||
"输出 null",
|
||
"编译报错:可空类型不能直接调用方法",
|
||
"正常运行"
|
||
],
|
||
"ans": 2,
|
||
"exp": "空安全下 <code>String?</code> 可能为 null,<b>不能直接调用方法</b>,编译期直接报错——这是空安全的核心价值:把空指针问题拦在编译期。需先判空,或用 <code>?</code> / <code>!</code>。"
|
||
},
|
||
{
|
||
"id": 183,
|
||
"lv": 5,
|
||
"q": "希望\"s 为 null 时整句返回 null,否则调用 length\",应该用?",
|
||
"opts": [
|
||
"<code>s.length</code>",
|
||
"<code>s?.length</code>",
|
||
"<code>s!.length</code>",
|
||
"<code>s ?? length</code>"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>?.</b> 安全调用:s 为 null 时结果为 null、不执行调用。常与 <code>??</code> 配合兜底:<code>s?.length ?? 0</code>。<code>s!.length</code> 是强制解包,s 为 null 时运行时报错。"
|
||
},
|
||
{
|
||
"id": 184,
|
||
"lv": 5,
|
||
"q": "<code>int? x = null; x ??= 10;</code> 的作用是?",
|
||
"opts": [
|
||
"把 x 强制转为非空",
|
||
"x 为 null 时才赋值为 10,否则保持原值",
|
||
"报错",
|
||
"x 永远等于 10"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>??=</b> 是\"若空则赋值\":x 为 null 时赋 10,若已有值则保持不变。等价于 <code>x = x ?? 10</code>,常用于给可空变量设置默认值。"
|
||
},
|
||
{
|
||
"id": 185,
|
||
"lv": 5,
|
||
"q": "执行 <code>throw FormatException('格式错误');</code> 会?",
|
||
"opts": [
|
||
"打印日志后继续执行",
|
||
"抛出异常,未被捕获则程序中断",
|
||
"返回 null",
|
||
"编译错误"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>throw</b> 主动抛出异常并中断当前执行,外层 <b>try-catch</b> 能接住就继续运行,否则异常冒泡导致程序崩溃。适合校验不通过时立即终止。"
|
||
},
|
||
{
|
||
"id": 186,
|
||
"lv": 5,
|
||
"q": "处理可能出错的代码,正确的结构是?",
|
||
"opts": [
|
||
"<code>catch { } try { }</code>",
|
||
"<code>if (error) { }</code>",
|
||
"<code>try { } catch (e) { } finally { }</code>",
|
||
"<code>except: { }</code>"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>try-catch-finally</b>:try 放可能异常的代码,catch 捕获处理(可写 <code>on FormatException</code> 精确分类),finally 无论成败都会执行,适合释放资源。"
|
||
},
|
||
{
|
||
"id": 187,
|
||
"lv": 6,
|
||
"q": "定义星期、状态码这类<b>固定的一组取值</b>,应使用?",
|
||
"opts": [
|
||
"<code>typedef</code>",
|
||
"<code>enum</code>",
|
||
"<code>const</code> 列表",
|
||
"<code>abstract</code> 类"
|
||
],
|
||
"ans": 1,
|
||
"exp": "<b>enum</b> 枚举:<code>enum Status { pending, done, failed }</code>,用 <code>Status.done</code> 取值,配合 switch 模式匹配处理,比散落的字符串常量更类型安全。"
|
||
},
|
||
{
|
||
"id": 188,
|
||
"lv": 6,
|
||
"q": "写一个\"返回列表首个元素\"的通用函数,任意类型都能用,正确的签名是?",
|
||
"opts": [
|
||
"<code>String first(List<String> list)</code>",
|
||
"<code>void first(List list)</code>",
|
||
"<code><T> T first(List<T> list)</code>",
|
||
"函数不能带类型参数"
|
||
],
|
||
"ans": 2,
|
||
"exp": "<b>泛型函数</b>:<code><T> T first(List<T> list)</code>,T 是类型占位,调用时自动推断:<code>first([1, 2])</code> 返回 int,<code>first(['a'])</code> 返回 String,既通用又保留类型信息。"
|
||
},
|
||
{
|
||
"id": 189,
|
||
"lv": 6,
|
||
"q": "<code>typedef Compare = int Function(int a, int b);</code> 的作用是?",
|
||
"opts": [
|
||
"创建了一个新函数",
|
||
"声明一个类",
|
||
"定义枚举",
|
||
"给函数类型起别名"
|
||
],
|
||
"ans": 3,
|
||
"exp": "<b>typedef</b> 给函数类型起别名:之后 <code>Compare cmp;</code> 或作为参数类型 <code>void sort(Compare cmp)</code>,签名一目了然。Dart 2.13+ 还能给普通类型起别名。"
|
||
},
|
||
{
|
||
"id": 190,
|
||
"lv": 6,
|
||
"q": "<code>var (name, age) = ('张三', 18);</code> 之后 name 和 age 的值是?",
|
||
"opts": [
|
||
"name=18,age='张三'",
|
||
"name='张三',age=18",
|
||
"报错:不能解构",
|
||
"两者都是 null"
|
||
],
|
||
"ans": 1,
|
||
"exp": "这是 Dart 3 的<b>解构</b>:把 Record <code>('张三', 18)</code> 按位置拆开,name='张三'、age=18。函数返回 Record 时配合解构取值非常简洁。"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": 12,
|
||
"name": "Flutter 基础巩固",
|
||
"questions": [
|
||
{
|
||
"id": 191,
|
||
"lv": 1,
|
||
"q": "新建 Flutter 项目后,日常编写页面代码的入口文件是哪一个?",
|
||
"opts": [
|
||
"lib/main.dart",
|
||
"bin/main.dart",
|
||
"web/index.html",
|
||
"pubspec.yaml"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Flutter 约定业务代码都放在 <code>lib/</code> 目录下,入口文件是 <code>lib/main.dart</code>;<code>bin/</code> 是纯 Dart 命令行项目的约定目录,<code>pubspec.yaml</code> 是项目配置而不是代码。"
|
||
},
|
||
{
|
||
"id": 192,
|
||
"lv": 1,
|
||
"q": "要做一个“顶部有标题栏、中间是内容、右下角有悬浮按钮”的标准页面,最合适的骨架组件是?",
|
||
"opts": [
|
||
"Scaffold",
|
||
"Container",
|
||
"Column",
|
||
"MaterialApp"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Scaffold</code> 是 Material 页面骨架,直接提供 <code>appBar</code>、<code>body</code>、<code>floatingActionButton</code>、<code>bottomNavigationBar</code> 等插槽;<code>MaterialApp</code> 是整个 App 的根配置,一个 App 通常只需要一个。"
|
||
},
|
||
{
|
||
"id": 193,
|
||
"lv": 1,
|
||
"q": "<code>MaterialApp(home: LoginPage())</code> 中 <b>home</b> 参数的作用是?",
|
||
"opts": [
|
||
"指定 App 启动后显示的第一个页面",
|
||
"指定 App 在手机桌面上的图标",
|
||
"注册全部命名路由的映射表",
|
||
"设置按下返回键后回到的页面"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>home</code> 就是应用的首页 Widget;注册命名路由用的是 <code>routes</code> 参数;App 图标要在各平台的原生工程里配置。"
|
||
},
|
||
{
|
||
"id": 194,
|
||
"lv": 1,
|
||
"q": "编辑器提示你把 <code>Text('登录')</code> 改写成 <code>const Text('登录')</code>,这样做的主要好处是?",
|
||
"opts": [
|
||
"该 Widget 在父组件重建时可复用同一个实例,减少重建开销",
|
||
"让这段文字变成只读,用户无法选中复制",
|
||
"只是编译更快,运行时没有任何区别",
|
||
"保证这个组件在页面中只会出现一次"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>const</code> Widget 在编译期就确定下来,父组件的 <code>build</code> 重新执行时会直接复用同一个常量实例,不必重新创建,是 Flutter 里最廉价的性能优化,所以 lint 会主动提示你加上。"
|
||
},
|
||
{
|
||
"id": 195,
|
||
"lv": 1,
|
||
"q": "改完页面样式想立刻在模拟器上看到效果,并且保留当前页面已经填好的表单内容,应该用?",
|
||
"opts": [
|
||
"热重载 Hot Reload(按 r)",
|
||
"热重启 Hot Restart(按 R)",
|
||
"停止调试后重新执行 flutter run",
|
||
"先 flutter clean 再重新编译"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Hot Reload 只把改动注入正在运行的应用并重建 Widget 树,<b>会保留 State</b>;Hot Restart 会重新执行 <code>main()</code>,所有状态清零。改 UI 用前者,改 <code>main()</code>、全局变量或依赖时才需要后者。"
|
||
},
|
||
{
|
||
"id": 196,
|
||
"lv": 2,
|
||
"q": "想让一段文字四周都留出 16 像素的<b>内</b>边距,正确写法是?",
|
||
"opts": [
|
||
"Padding(padding: EdgeInsets.all(16), child: Text('内容'))",
|
||
"Container(margin: EdgeInsets.all(16), child: Text('内容'))",
|
||
"SizedBox(width: 16, height: 16, child: Text('内容'))",
|
||
"Text('内容', textAlign: TextAlign.center)"
|
||
],
|
||
"ans": 0,
|
||
"exp": "内边距用 <code>Padding</code> 搭配 <code>EdgeInsets</code>。<code>EdgeInsets.all(16)</code> 四边相同,<code>EdgeInsets.symmetric(horizontal: 16, vertical: 8)</code> 分别设置左右和上下,<code>EdgeInsets.only(left: 16)</code> 只设某一边。"
|
||
},
|
||
{
|
||
"id": 197,
|
||
"lv": 2,
|
||
"q": "<code>Container</code> 的 <b>padding</b> 和 <b>margin</b> 有什么区别?",
|
||
"opts": [
|
||
"padding 是内容到自身边界的内边距,margin 是自身到外部的外边距",
|
||
"两者完全等价,只是写法不同",
|
||
"padding 只对文字生效,margin 只对图片生效",
|
||
"padding 会撑大父组件,margin 会撑大子组件"
|
||
],
|
||
"ans": 0,
|
||
"exp": "记忆方式:<b>padding 撑开里面,margin 推开外面</b>。给卡片内容留白用 padding,让卡片之间拉开距离用 margin。"
|
||
},
|
||
{
|
||
"id": 198,
|
||
"lv": 2,
|
||
"q": "只是想在两个按钮之间加 12 像素的垂直间距,最轻量的写法是?",
|
||
"opts": [
|
||
"SizedBox(height: 12)",
|
||
"Container(height: 12, color: Colors.transparent)",
|
||
"Padding(padding: EdgeInsets.all(12))",
|
||
"Divider(height: 12)"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>SizedBox</code> 只占位、不绘制,是做间距的首选;<code>Container</code> 功能多也更重;<code>Divider</code> 会额外画出一条分割线,语义不同。"
|
||
},
|
||
{
|
||
"id": 199,
|
||
"lv": 2,
|
||
"q": "页面上要展示一排数量不固定的标签,希望一行放不下时自动折到下一行,应该用?",
|
||
"opts": [
|
||
"Wrap",
|
||
"Row",
|
||
"Column",
|
||
"Expanded"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Row</code> 是单行布局,放不下就会溢出报警告;<code>Wrap</code> 在主轴空间不足时自动换行,再配 <code>spacing</code>(同行间距)和 <code>runSpacing</code>(行与行间距)即可。"
|
||
},
|
||
{
|
||
"id": 200,
|
||
"lv": 2,
|
||
"q": "页面顶部内容被状态栏/刘海挡住、底部被手势条挡住,最稳妥的处理是?",
|
||
"opts": [
|
||
"在最外层套一个 SafeArea",
|
||
"给最外层写死 EdgeInsets.only(top: 44)",
|
||
"把页面改成 StatefulWidget",
|
||
"用 Center 把内容整体居中"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>SafeArea</code> 会读取系统的安全区信息,自动补上合适的内边距,各机型自适应;写死 44 像素换一台设备就会错位。"
|
||
},
|
||
{
|
||
"id": 201,
|
||
"lv": 3,
|
||
"q": "页面上放一个“提交”按钮并响应点击,标准写法是?",
|
||
"opts": [
|
||
"ElevatedButton(onPressed: () { ... }, child: Text('提交'))",
|
||
"ElevatedButton(child: Text('提交'), onClick: () { ... })",
|
||
"Button(text: '提交', onTap: () { ... })",
|
||
"ElevatedButton(onPressed: '提交')"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Flutter 按钮的点击回调统一叫 <code>onPressed</code>,接收一个无参函数。常用的三种:<code>ElevatedButton</code>(填充)、<code>TextButton</code>(纯文字)、<code>OutlinedButton</code>(描边)。"
|
||
},
|
||
{
|
||
"id": 202,
|
||
"lv": 3,
|
||
"q": "表单没填完时,希望“提交”按钮自动变灰且点不动,最简单的做法是?",
|
||
"opts": [
|
||
"把 onPressed 设为 null",
|
||
"把 child 设为 null",
|
||
"用 Visibility 把按钮整个隐藏",
|
||
"外面包一层 IgnorePointer 并手动改颜色"
|
||
],
|
||
"ans": 0,
|
||
"exp": "Flutter 按钮的约定是:<code>onPressed == null</code> 即为禁用状态,会自动切换成灰色的 disabled 样式。所以常写成 <code>onPressed: canSubmit ? _submit : null</code>。"
|
||
},
|
||
{
|
||
"id": 203,
|
||
"lv": 3,
|
||
"q": "要做“左边头像、中间标题加副标题、右边一个箭头”的列表行,最省事的组件是?",
|
||
"opts": [
|
||
"ListTile",
|
||
"Row",
|
||
"Card",
|
||
"Column"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>ListTile</code> 直接提供 <code>leading</code>、<code>title</code>、<code>subtitle</code>、<code>trailing</code> 四个插槽,还内置了 <code>onTap</code> 点击和标准的高度与间距,省去手写 Row/Column 对齐。"
|
||
},
|
||
{
|
||
"id": 204,
|
||
"lv": 3,
|
||
"q": "想在搜索框前面放一个放大镜图标,写法是?",
|
||
"opts": [
|
||
"Icon(Icons.search)",
|
||
"Image(Icons.search)",
|
||
"Text(Icons.search)",
|
||
"IconData('search')"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Icon</code> 负责渲染,<code>Icons</code> 是 Material 内置的图标常量集合(<code>Icons.search</code>、<code>Icons.add</code>…)。如果图标本身要能点击,用 <code>IconButton</code>。"
|
||
},
|
||
{
|
||
"id": 205,
|
||
"lv": 3,
|
||
"q": "商品封面图要显示成圆角矩形,标准做法是?",
|
||
"opts": [
|
||
"用 ClipRRect(borderRadius: BorderRadius.circular(8)) 把图片包起来",
|
||
"给 Image 设置 borderRadius 参数",
|
||
"把 Image 的 fit 设为 BoxFit.cover",
|
||
"用 Opacity 把四个角变透明"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Image</code> 本身没有圆角参数,需要靠外层裁剪:圆角矩形用 <code>ClipRRect</code>,正圆用 <code>ClipOval</code> 或 <code>CircleAvatar</code>;也可以用 <code>Container</code> 的 <code>decoration: BoxDecoration(borderRadius: ...)</code> 实现。"
|
||
},
|
||
{
|
||
"id": 206,
|
||
"lv": 4,
|
||
"q": "把设计给的 logo.png 放进项目,想用 <code>Image.asset('assets/logo.png')</code> 显示,还必须做的一步是?",
|
||
"opts": [
|
||
"在 pubspec.yaml 的 flutter -> assets 下声明这个路径",
|
||
"在 main.dart 里 import 这张图片",
|
||
"把图片改名成 logo.dart",
|
||
"在 AndroidManifest.xml 里注册这张图片"
|
||
],
|
||
"ans": 0,
|
||
"exp": "本地资源必须先在 <code>pubspec.yaml</code> 里声明才会被打包,在 <code>flutter:</code> 节点下写 <code>assets:</code> 再列出 <code>- assets/logo.png</code>;只写目录 <code>- assets/</code> 可以一次声明整个文件夹。注意改完 pubspec 要重新运行,Hot Reload 不会重新打包资源。"
|
||
},
|
||
{
|
||
"id": 207,
|
||
"lv": 4,
|
||
"q": "<code>Image.asset</code> 和 <code>Image.network</code> 的区别是?",
|
||
"opts": [
|
||
"asset 加载随 App 打包的本地资源,network 通过 URL 加载网络图片",
|
||
"asset 加载网络图片,network 加载本地图片",
|
||
"两者完全一样,只是命名习惯不同",
|
||
"asset 只能加载 png,network 只能加载 jpg"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Image.asset('assets/logo.png')</code> 读取打包进 App 的本地资源;<code>Image.network('https://...')</code> 运行时才下载,因此通常还要配 <code>loadingBuilder</code> 做占位、<code>errorBuilder</code> 做兜底。"
|
||
},
|
||
{
|
||
"id": 208,
|
||
"lv": 4,
|
||
"q": "要把一段文字设置成 18 号、加粗、灰色,正确写法是?",
|
||
"opts": [
|
||
"Text('标题', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey))",
|
||
"Text('标题', fontSize: 18, bold: true, color: Colors.grey)",
|
||
"Text('标题', css: 'font-size:18px;font-weight:bold')",
|
||
"TextStyle('标题', size: 18, bold: true)"
|
||
],
|
||
"ans": 0,
|
||
"exp": "文字样式统一放在 <code>Text</code> 的 <code>style</code> 参数里,类型是 <code>TextStyle</code>。想在主题字号的基础上微调,用 <code>Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.grey)</code>。"
|
||
},
|
||
{
|
||
"id": 209,
|
||
"lv": 4,
|
||
"q": "项目里多个页面都要用同一个主色调,最推荐的取色方式是?",
|
||
"opts": [
|
||
"Theme.of(context).colorScheme.primary",
|
||
"每个页面各自写死 Color(0xFF3D6B5E)",
|
||
"定义一个全局可变变量,谁都能改",
|
||
"每次从设计稿图片里取色"
|
||
],
|
||
"ans": 0,
|
||
"exp": "颜色、字体等统一配置在 <code>MaterialApp(theme: ThemeData(...))</code>,页面里通过 <code>Theme.of(context)</code> 取用。这样换肤、适配深色模式只需要改一处。顺带一提,Flutter 的颜色是 8 位 ARGB,<code>0xFF</code> 开头才是不透明。"
|
||
},
|
||
{
|
||
"id": 210,
|
||
"lv": 4,
|
||
"q": "要让卡片宽度占屏幕宽度的 80%,获取屏幕宽度的写法是?",
|
||
"opts": [
|
||
"MediaQuery.of(context).size.width",
|
||
"context.width",
|
||
"Scaffold.of(context).width",
|
||
"Window.screenWidth"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>MediaQuery</code> 提供屏幕尺寸、状态栏高度、字体缩放比等信息,依赖 <code>context</code>,只能在 <code>build</code> 里或拿到 context 之后使用。如果想按<b>父容器</b>而不是屏幕的尺寸来布局,用 <code>LayoutBuilder</code>。"
|
||
},
|
||
{
|
||
"id": 211,
|
||
"lv": 5,
|
||
"q": "想让整张商品卡片都能点击,并且点下去有 Material 水波纹反馈,应该用?",
|
||
"opts": [
|
||
"InkWell",
|
||
"GestureDetector",
|
||
"Container",
|
||
"AbsorbPointer"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>GestureDetector</code> 也能响应点击,但没有任何视觉反馈;<code>InkWell</code> 会在点击时绘制水波纹,更符合 Material 交互习惯。注意水波纹画在 <code>Material</code> 上,所以 InkWell 上层要有 Material 背景才看得见。"
|
||
},
|
||
{
|
||
"id": 212,
|
||
"lv": 5,
|
||
"q": "保存成功后,想在页面底部弹出一条“保存成功”的短提示,写法是?",
|
||
"opts": [
|
||
"ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存成功')))",
|
||
"showDialog(context: context, builder: ...)",
|
||
"print('保存成功')",
|
||
"Navigator.push 跳到一个提示页"
|
||
],
|
||
"ans": 0,
|
||
"exp": "底部短提示用 <code>SnackBar</code>,通过 <code>ScaffoldMessenger.of(context)</code> 弹出(旧写法 <code>Scaffold.of(context).showSnackBar</code> 已废弃)。它会自动消失,不打断用户当前操作。"
|
||
},
|
||
{
|
||
"id": 213,
|
||
"lv": 5,
|
||
"q": "点“删除”后要弹确认框,并根据用户点的是“确定”还是“取消”决定是否真的删除,正确写法是?",
|
||
"opts": [
|
||
"final ok = await showDialog<bool>(...); if (ok == true) { 执行删除 }",
|
||
"showDialog(...); 紧接着直接执行删除",
|
||
"用 SnackBar 代替确认框",
|
||
"在 build 方法里判断用户点了什么"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>showDialog</code> 返回一个 <code>Future</code>,对话框里用 <code>Navigator.pop(context, true)</code> 把结果带回来,外面 <code>await</code> 拿到后再决定后续动作。注意用户点遮罩关闭时结果是 <code>null</code>,所以要判断 <code>== true</code> 而不是直接当布尔用。"
|
||
},
|
||
{
|
||
"id": 214,
|
||
"lv": 5,
|
||
"q": "点“选择城市”后,希望从屏幕底部滑出一个选项面板,应该用?",
|
||
"opts": [
|
||
"showModalBottomSheet",
|
||
"showDialog",
|
||
"Navigator.pop",
|
||
"BottomNavigationBar"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>showModalBottomSheet</code> 从底部滑出模态面板,同样返回 <code>Future</code>,面板里用 <code>Navigator.pop(context, value)</code> 回传用户的选择;<code>BottomNavigationBar</code> 是常驻的底部标签栏,用途完全不同。"
|
||
},
|
||
{
|
||
"id": 215,
|
||
"lv": 5,
|
||
"q": "App 底部要有“首页 / 分类 / 我的”三个标签来回切换,正确做法是?",
|
||
"opts": [
|
||
"Scaffold 的 bottomNavigationBar 放 BottomNavigationBar,用 currentIndex 记录选中项,onTap 里 setState",
|
||
"每点一个标签就 Navigator.push 一个新页面",
|
||
"用三个 Column 上下排列,点谁显示谁",
|
||
"在 MaterialApp 里配置三个 home"
|
||
],
|
||
"ans": 0,
|
||
"exp": "标签栏切换的是同一个页面里的内容,用 <code>currentIndex</code> + <code>onTap</code> 改状态即可;如果每次都 <code>push</code> 新页面,返回栈会越堆越深。希望切回来时保留各标签的滚动位置,可以配合 <code>IndexedStack</code>。"
|
||
},
|
||
{
|
||
"id": 216,
|
||
"lv": 6,
|
||
"q": "登录表单要一次性校验“手机号”和“密码”两个输入框,官方推荐做法是?",
|
||
"opts": [
|
||
"用 Form 包住输入框,配一个 GlobalKey<FormState>,提交时调用 _formKey.currentState!.validate()",
|
||
"在每个 TextField 的 onChanged 里手写 if 判断",
|
||
"提交时挨个读 controller.text 用 if 判断",
|
||
"前端不校验,全部交给服务端返回错误"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>Form</code> + <code>GlobalKey<FormState></code> 是官方表单方案:<code>validate()</code> 会依次触发每个 <code>TextFormField</code> 的 <code>validator</code>,<b>全部通过才返回 true</b>,并自动把错误文案显示在对应输入框下方。"
|
||
},
|
||
{
|
||
"id": 217,
|
||
"lv": 6,
|
||
"q": "<code>TextFormField</code> 的 <code>validator</code> 回调中,返回 <code>null</code> 表示什么?",
|
||
"opts": [
|
||
"校验通过,不显示任何错误提示",
|
||
"校验失败,但不提示用户",
|
||
"清空输入框里的内容",
|
||
"该字段不参与校验"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>validator</code> 的约定是:返回 <code>null</code> 代表通过;返回一个字符串代表失败,且这个字符串就是显示在输入框下方的错误文案。例如 <code>validator: (v) => (v == null || v.isEmpty) ? '请输入手机号' : null</code>。"
|
||
},
|
||
{
|
||
"id": 218,
|
||
"lv": 6,
|
||
"q": "进入页面时要立刻发一次网络请求拿数据,这段代码应该写在 State 的哪个方法里?",
|
||
"opts": [
|
||
"initState()",
|
||
"build()",
|
||
"dispose()",
|
||
"setState()"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>initState()</code> 在 State 创建后只执行一次,适合做初始化:发首次请求、创建 Controller、注册监听。<code>build()</code> 可能被调用很多次,把请求写在里面会反复触发;<code>dispose()</code> 是销毁时做清理的地方。"
|
||
},
|
||
{
|
||
"id": 219,
|
||
"lv": 6,
|
||
"q": "页面数据来自一次异步请求,要根据“加载中 / 成功 / 失败”分别渲染不同界面,最省事的组件是?",
|
||
"opts": [
|
||
"FutureBuilder",
|
||
"ListView.builder",
|
||
"StatelessWidget",
|
||
"AnimatedBuilder"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>FutureBuilder</code> 接收一个 <code>Future</code>,在 <code>builder</code> 里根据 <code>snapshot.connectionState</code> 和 <code>snapshot.hasError</code> 分支渲染,不用自己维护一堆 isLoading 字段。<b>坑</b>:不要把 <code>future: fetchData()</code> 直接写在 build 里,那样每次重建都会重新请求,应在 <code>initState</code> 里先创建好再传进去。持续变化的数据流则用 <code>StreamBuilder</code>。"
|
||
},
|
||
{
|
||
"id": 220,
|
||
"lv": 6,
|
||
"q": "异步请求 <code>await</code> 回来后要调用 <code>setState</code>,为什么通常先判断一下 <code>mounted</code>?",
|
||
"opts": [
|
||
"请求返回时页面可能已被关闭,对已销毁的 State 调用 setState 会报错",
|
||
"mounted 是用来判断网络是否连通的",
|
||
"不判断的话请求会被系统取消",
|
||
"判断 mounted 能让请求返回得更快"
|
||
],
|
||
"ans": 0,
|
||
"exp": "<code>await</code> 期间用户可能已经返回上一页,State 被销毁。此时再 <code>setState</code> 会抛出 setState() called after dispose()。标准写法是拿到结果后先 <code>if (!mounted) return;</code>,再更新状态。"
|
||
}
|
||
],
|
||
"lvNames": [
|
||
"项目与骨架",
|
||
"布局与间距",
|
||
"常用组件",
|
||
"资源与样式",
|
||
"交互与反馈",
|
||
"表单与异步"
|
||
]
|
||
}
|
||
],
|
||
"nextSetId": 13,
|
||
"nextQId": 221
|
||
} |