Skip to content

第一章练习答案:从结果回到规则 ​

答案按文章顺序排列。先预测输出,再运行代码;如果预测与运行不同,定位到具体转换或执行步骤,而不是只记住最后一个值。

运行与语法 ​

第一题,每页行数不变,页数会重新赋值:

js
const rowsPerPage = 12;
let pages = 3;
console.log(rowsPerPage * pages); // 36
pages = 4;
console.log(rowsPerPage * pages); // 48

第二题原程序输出 3,因为 n === 4 只产生布尔值,并未赋值;写 n = 4 后输出 4。比较表达式的结果如果没有被使用,就只是被计算后丢弃。

第三题:已经保存的subtotal仍为36,因为初始化只计算一次;重新求 price * quantity 才得到72。整文件存在语法错误时,不能完成解析,前面的顶层日志也不会开始执行;运行期错误则可能在此前语句已经执行之后出现。

返回文章

值与身份 ​

第一题 b = a 让两者关联同一对象,修改后 a.n 为 2。若 b = { n: a.n },就新建对象并复制数值 1,修改 b 不影响 a。

第二题 0 为假、'0' 为真、null 为假、空对象为真。允许零的数量字段以 quantity ?? defaultValue 处理缺失,它只替换两种空值。是否允许空字符串仍需另做校验。

第三题:重新给a赋对象后,b.n仍为1。a的新值是另一个对象,而b保留旧对象身份;若只写a.n=2,两边仍指向同一个对象,所以b.n也为2。检查赋值左侧是变量还是对象属性,即可区分两条路径。

第四题:

js
const draft = { title: '初稿', pages: 0 };
const published = { title: '发布稿', pages: draft.pages };
published.pages = 1;
console.log(draft.title, draft.pages); // 初稿 0
console.log(published.title, published.pages); // 发布稿 1
console.log(published === draft); // false

新字面量创建另一对象,pages读取的是原数值0,所以重新赋published.pages不影响draft。若直接赋draft,只会多一个访问同一对象的名字。本题两个字段都是原始值;如果以后增加嵌套对象,还需继续检查里面是否共享,不能把这段写法叫通用深复制。

返回文章

条件与循环 ​

第一题:

js
let total = 0;
for (let n = 1; n <= 20; n += 1) {
  if (n % 3 === 0) total += n;
}
console.log(total); // 63:3 + 6 + 9 + 12 + 15 + 18

起点 1 包含最小候选值,<= 20 包含上边界。也可从 3 开始每次加 3,但应能解释它与条件筛选为什么等价。

第二题依题目约定,先只处理有限 Number 的范围分类:

js
const score = 96;
let level;
if (score < 0 || score > 100) {
  level = '无效';
} else if (score >= 90) {
  level = '优秀';
} else if (score >= 60) {
  level = '通过';
} else {
  level = '未通过';
}
console.log(level); // 优秀

先拒绝无效输入,后面每个分支才可假定分数属于有效区间;否则 150 分也会被判为优秀。这个版本依赖输入契约;遇到外部文本或NaN,要在学完本章数值校验后先补输入检查。

第三题:两次独立if都会执行判断,96既满足90线也满足60线,第二次赋值覆盖第一次;互斥等级使用else-if。while的continue回到条件检查,不自动执行放在循环体末尾的推进,所以应保证推进在所有继续循环的路径上执行。

返回文章

运算与转换 ​

第一题 '' == 0 将空字符串转为 0,结果 true;'' === 0 类型不同,结果 false。'5' + 1 走拼接得到 '51';'5' - 1 走数值运算得到 4。

第二题:

js
console.log(0 || 1, 0 ?? 1); // 1 0
console.log(undefined || 1, undefined ?? 1); // 1 1

数量为 0 代表一个有效值,因此选择 ??。它并未校验负数、空字符串或错误类型,只负责默认值。

第三题:parseInt先把输入转成1e+21文本,按十进制读到e停止,因此得到1。关系比较对null走数值转换,而宽松相等的空值分支不把null与0判等,两者算法不同。短路表达式只有左侧不足以决定结果时才求右侧,所以副作用可能不发生。

第四题先把移动后的数学位置约束到0~4:

js
const size = 5;
const current = 0;
let step = -1;
console.log(((current + step) % size + size) % size); // 4
step = -7;
console.log(((current + step) % size + size) % size); // 3
step = 0;
console.log(((current + step) % size + size) % size); // 0
step = -5;
console.log(((current + step) % size + size) % size); // 0

向前翻回先前页面用负移动量;直接 -1 % 5 得到-1,并不会自动回到4。第一次取余保留可能的负结果,加size把它移到非负区域,第二次取余又把整圈结果归到0。本题要求size为正整数且运算在可靠精度范围内,不能把size为0的NaN结果当作合法页码。

返回文章

数值边界 ​

第一题超出安全范围的 Number 可能仍满足 Number.isInteger,但相邻整数已经无法区分。数据库 ID 从接口开始就保留十进制字符串,确有整数运算需求时转换成 BigInt。已经失真的 Number 再转字符串或 BigInt 无法补救。

第二题输出依次为 -3、-2、2n。BigInt(2.8) 抛 RangeError,因为它要求整值 Number;若业务真的允许截断,应先明确写 Math.trunc 并验证范围,而不是假设转换自动舍入。

第三题:0.5和0.25都能以有限二进制分数表示;0.1和0.2需要舍入。toFixed产生字符串,再用加号可能拼接;它也不能恢复已经丢失的精度。EPSILON描述1附近的间隔,不包含数据规模与业务测量误差,阈值必须另行设计。

第四题:

js
const input = '0'; // 逐次替换题目给出的输入后运行
const normalized = input.trim();
const quantity = Number(normalized);
const valid = normalized !== ''
  && Number.isFinite(quantity)
  && Number.isInteger(quantity)
  && quantity >= 0 && quantity <= 100;
console.log(valid, quantity); // true 0

题目六个输入的valid依次为true、true、false、false、false、false。空白需在转换前留下判断,否则空字符串会转换成0;完整文本转换使12本成为NaN;整数与区间判断分别拒绝2.5和101。'1e2' 转换为100,按本题“数值为整数”的契约通过。若产品只允许十进制数字字符,应再加文本格式规则,第三章正则可表达;不能悄悄把两种契约混成一种。

返回文章

用心记录每一步成长