跟着 MDN 学 JavaScript Day 30:JSON 技能实战——解析猫舍数据并动态展示

发布时间:2026/6/14 18:28:08

跟着 MDN 学 JavaScript Day 30:JSON 技能实战——解析猫舍数据并动态展示 引言从理论到实践的跨越在上一篇文章中我们系统学习了 JSON 的基础知识包括它的语法结构、数据访问方式以及序列化与反序列化的核心方法。理论知识固然重要但真正的掌握来自于动手实践。MDN 为我们准备了一道经典的 JSON 技能测试题要求我们解析一组关于母猫及其小猫的 JSON 数据并将统计结果动态展示在网页上。这道题目看似简单却巧妙地串联起了 JSON 解析、循环嵌套、字符串拼接、异步编程等多个关键知识点是检验 JSON 理解程度的绝佳试金石。本文将详细拆解这道技能测试题的解题思路与实现过程。我们将从分析 JSON 数据结构入手逐步讲解如何解析文本为对象、如何设计循环逻辑遍历嵌套数组、如何优雅地处理字符串拼接的边界情况以及为何 DOM 更新操作必须放在异步回调函数内部。通过完整的代码实现和深入的原理解析你将获得处理真实 JSON 数据的扎实能力。题目解析任务目标与数据结构题目为我们提供了一组描述母猫及其小猫的 JSON 数据存储在一个名为sample.json的文件中。数据通过 Fetch API 以文本形式加载到页面并作为catString参数传递给displayCatInfo函数。我们需要在该函数内部完成两项统计任务第一项是将所有母猫的名字用逗号分隔并拼接成一个完整的句子存储在motherInfo变量中第二项是统计所有小猫的总数以及雄性和雌性的数量同样拼接成句子存储在kittenInfo变量中。两个变量的值最终会通过para1和para2这两个段落元素展示在页面上。理解 JSON 数据的结构是编写正确代码的前提。sample.json的内容是一个数组其中每个元素都是一个代表母猫的对象。每个母猫对象包含四个属性name表示名字breed表示品种color表示毛色而kittens则是一个数组包含该母猫的所有小猫信息。每只小猫又是一个对象拥有name和gender两个属性gender的值为m表示雄性f表示雌性。[ { name : Lindy, breed : Cymric, color : white, kittens : [ { name : Percy, gender : m }, { name : Thea, gender : f }, { name : Annis, gender : f } ] }, { name : Mina, breed : Aphrodite Giant, color : ginger, kittens : [ { name : Doris, gender : f }, { name : Pickle, gender : f }, { name : Max, gender : m } ] }, { name : Antonia, breed : Ocicat, color : leopard spotted, kittens : [ { name : Bridget, gender : f }, { name : Randolph, gender : m } ] } ]观察数据结构可以发现这是一个典型的两层嵌套关系外层数组遍历母猫内层数组遍历每只母猫的小猫。这种结构自然地引导我们采用外部循环加内部循环的双层遍历策略。外部循环负责收集母猫的名字并拼接到motherInfo字符串中内部循环则遍历当前母猫的kittens数组累加小猫总数并根据gender值分别统计雄性和雌性的数量。第一步将 JSON 文本解析为 JavaScript 对象题目明确指出JSON 数据在displayCatInfo函数内以文本形式提供这意味着catString参数是一个原始的 JSON 字符串不能直接使用点表示法或括号表示法来访问其中的属性。在操作数据之前必须先将这个字符串解析为 JavaScript 对象。这正是JSON.parse方法的用武之地。在displayCatInfo函数内部第一行代码应该调用JSON.parse并传入catString将返回值赋给一个变量比如catData。这一步骤至关重要因为只有经过解析JSON 才能从无结构的字符串转变为可按索引和属性名访问的对象数组。如果忘记调用JSON.parse而直接对catString进行遍历操作代码将无法正常工作因为字符串不具备数组和对象的访问特性。解析操作的核心代码非常简洁constcatDataJSON.parse(catString);这行代码执行后catData就成为了与原始 JSON 结构完全对应的 JavaScript 数组。此时我们可以安全地使用catData.length获取母猫的数量使用catData[0].name获取第一只母猫的名字使用catData[1].kittens[2].gender获取第二只母猫第三只小猫的性别。所有在上一篇文章中学到的链式访问技巧在这里都能派上用场。JSON.parse是反序列化的标准方法它与JSON.stringify互为逆操作两者共同构成了 JavaScript 中数据序列化与反序列化的完整工具链。第二步外部循环遍历母猫并拼接名字数据解析完成后首要任务是构建motherInfo字符串。题目已经为motherInfo变量提供了初始值The mother cats are called 我们只需在此基础上追加所有母猫的名字并在最后一只猫的名字前添加and字样在句末添加句号。处理这个问题有多种策略。一种直观的思路是先用循环将所有母猫名字收集到一个数组中然后使用数组的join方法以逗号加空格作为分隔符进行拼接最后手动处理最后一个逗号和and的替换逻辑。另一种更灵活的方式是在循环中直接判断当前索引位置对最后一只猫进行特殊处理。考虑到题目要求无论 JSON 中有多少只猫都能正常工作我们应当避免硬编码任何具体的名字或数量而是依赖数组的长度和索引动态判断。采用循环内判断索引的方式核心逻辑可以这样实现for(leti0;icatData.length;i){if(icatData.length-1){motherInfoand${catData[i].name}.;}else{motherInfo${catData[i].name},;}}在这段代码中循环变量i从0递增到数组长度减一。对于每一次迭代我们检查当前索引是否等于catData.length - 1即是否到达了最后一只母猫。如果条件成立说明这是最后一只在名字前拼接and 名字后拼接句号。如果不成立则在名字后拼接逗号和空格作为分隔符。这种基于索引的条件判断保证了代码对任意数量母猫的适用性。无论将来 JSON 数据中添加或删除母猫代码都能自动调整输出格式。最终motherInfo的值将类似于The mother cats are called Lindy, Mina and Antonia.。第三步内部循环统计小猫数量与性别在外部循环处理每只母猫的同时我们需要开启第二个循环来遍历当前母猫的kittens数组完成小猫的统计工作。题目要求统计三个数值所有小猫的总数、雄性小猫的数量以及雌性小猫的数量。total变量用于累加总数每次内部循环迭代就自增一。male变量用于累计雄性数量需要判断每只小猫的gender属性是否等于字符串m。雌性数量则可以通过total减去male间接得出无需单独维护计数器。内部循环的实现代码如下for(letj0;jcatData[i].kittens.length;j){total;if(catData[i].kittens[j].genderm){male;}}内部循环直接嵌套在外部循环之中这意味着对于每一只母猫都会完整遍历一次它的小猫列表。循环中首先无条件执行total自增确保每只小猫都被计入总数。然后检查当前小猫的gender属性只有当它严格等于字符串m时才让male变量自增。这种分别处理的方式让统计逻辑清晰明了total追踪的是小猫总量male追踪的是其中的雄性个体而雌性数量则由两者之差自动体现。完成所有循环后我们需要使用这些统计数据构建kittenInfo字符串。由于题目没有为kittenInfo提供初始值需要从零开始构造完整句子kittenInfoThere are${total}kittens,${male}are male and${total-male}are female.;这行代码使用了模板字面量语法将total、male以及total - male的计算结果直接嵌入到句子结构中。模板字面量使用反引号包围以${}包裹表达式相比传统的字符串拼接方式更加直观易读。最终kittenInfo的值将类似于There are 8 kittens, 3 are male and 5 are female.。使用total - male来计算雌性数量避免了维护第三个计数器的需要保持了代码的简洁性。第四步理解异步代码中的 DOM 更新时机题目提供的 HTML 骨架中JavaScript 代码的组织方式蕴含了一个关于异步编程的重要知识点。完整的脚本结构如下constsectiondocument.querySelector(section);letpara1document.createElement(p);letpara2document.createElement(p);letmotherInfoThe mother cats are called ;letkittenInfo;constrequestURLhttps://mdn.github.io/learning-area/javascript/oojs/tasks/json/sample.json;fetch(requestURL).then(responseresponse.text()).then(textdisplayCatInfo(text))functiondisplayCatInfo(catString){lettotal0;letmale0;// 解析和统计代码在此处para1.textContentmotherInfo;para2.textContentkittenInfo;}section.appendChild(para1);section.appendChild(para2);观察这段代码的执行顺序可以清楚地看到异步编程的影响。脚本首先执行同步代码获取section元素、创建两个段落元素、初始化motherInfo、定义requestURL。然后调用fetch函数发起网络请求。由于fetch是异步的JavaScript 引擎不会等待请求完成而是继续执行后面的同步代码也就是将para1和para2追加到section中。至此页面上已经有了两个段落元素但它们的textContent尚未被设置为最终的值。当网络请求完成后then回调链中的函数依次执行response.text将响应体提取为文本然后displayCatInfo被调用。在displayCatInfo内部JSON 文本被解析双层循环完成统计motherInfo被拼接完整kittenInfo被构造完成最后两行赋值语句将这两个字符串分别设置给para1.textContent和para2.textContent。此时页面上的两个段落元素才真正显示出有意义的内容。题目特意提出了一个引导性思考为什么para1.textContent和para2.textContent的赋值语句放在displayCatInfo函数内部而不是放在脚本末尾如果将这些赋值语句移到脚本末尾它们会在fetch请求尚未完成、displayCatInfo尚未执行时就被调用。此时motherInfo还只是The mother cats are called 这个初始前缀kittenInfo甚至是undefined页面上显示的将是残缺的内容。将赋值语句放在displayCatInfo函数内部确保了这些操作只有在 JSON 数据成功获取并解析之后才会执行从而保证页面展示的是完整正确的结果。完整代码实现与逻辑串联将以上所有步骤整合起来displayCatInfo函数的完整实现应当包含解析、循环、拼接三个核心部分。整合后的完整代码如下functiondisplayCatInfo(catString){lettotal0;letmale0;constcatDataJSON.parse(catString);for(leti0;icatData.length;i){if(icatData.length-1){motherInfoand${catData[i].name}.;}else{motherInfo${catData[i].name},;}for(letj0;jcatData[i].kittens.length;j){total;if(catData[i].kittens[j].genderm){male;}}}kittenInfoThere are${total}kittens,${male}are male and${total-male}are female.;para1.textContentmotherInfo;para2.textContentkittenInfo;}这段代码的执行流程清晰而连贯。首先声明total和male计数器并初始化为零。然后调用JSON.parse将catString解析为catData数组。接着进入外部for循环遍历每一只母猫。在外部循环体内先通过if条件判断当前母猫是否为最后一只按规则将名字追加到motherInfo字符串中。然后立即进入内部for循环遍历当前母猫的kittens数组每次迭代total自增一遇到gender为m的小猫则male自增一。所有循环结束后使用模板字面量构造kittenInfo字符串。最后将两个字符串分别赋值给两个段落元素的textContent属性。整个过程一气呵成每一行代码都有明确的职责构成了一个完整的数据处理管道。边界情况与扩展思考这道题目虽然以三只母猫的固定数据为背景但代码的设计应当具备对动态数据的适应能力。题目提示特别强调如何确保无论 JSON 中有多少只猫都能正常工作这正是考察开发者编写健壮代码的意识。我们的实现中使用了数组长度判断、索引比较等通用方法而非硬编码特定名字或数量因此能够天然适应数据规模的变化。如果将来 JSON 中只有一只母猫循环只执行一次。此时i等于0同时catData.length - 1也等于0if条件成立代码执行motherInfo and catData[0].name .这一分支。最终输出的字符串将是The mother cats are called and Lindy.。虽然语法上没有错误但从语义上看只有一只猫时使用and显得有些奇怪。在真正的生产环境中可以对此进行优化例如当数组长度为一时完全不使用逗号或and直接将名字拼接上去。这提醒我们通用逻辑虽然覆盖了主要场景但边界情况往往需要额外考量。另一个值得思考的点是代码中计数器变量的声明位置。total和male被声明在displayCatInfo函数的作用域内而非全局作用域。这是一个好的实践因为它限定了变量的生命期和可见范围避免了全局命名空间的污染。如果将来这个函数被多次调用每次调用都会创建全新的total和male变量不会相互干扰。这种基于函数作用域的封装思想是编写可维护 JavaScript 代码的基础习惯。异步处理模式的选择同样值得讨论。题目中使用了传统的then回调链来处理fetch的结果这是 Promise 的经典用法。如今async和await语法提供了更接近同步代码的书写体验。如果使用async函数重写可以将整个fetch逻辑包裹在一个async函数中使用await等待请求结果使得代码流程更加直观。不过理解then链的运作机制依然是掌握异步编程的必经之路因为它是 Promise 对象最基础的使用方式也是许多老旧代码库中仍在广泛使用的模式。总结JSON 技能的实战验收本文围绕 MDN 的 JSON 技能测试题完整呈现了从数据解析到页面渲染的全过程。我们首先分析了sample.json的数组嵌套结构明确了外部循环处理母猫、内部循环统计小猫的策略。接着详细讲解了JSON.parse将文本转换为可操作对象的关键作用。然后分别处理了motherInfo字符串的动态拼接逻辑以及kittenInfo字符串中总数与性别统计的累加方法。最后深入探讨了异步代码中 DOM 更新时机的重要性解释了为何textContent赋值必须放置在回调函数内部。这道题目虽然篇幅不大但浓缩了 JSON 操作的核心技能解析、遍历、访问嵌套属性、统计计算以及结合 DOM 进行结果展示。掌握这些技能意味着你具备了处理 Web 应用中绝大多数 JSON 数据场景的能力。无论是解析服务器返回的列表数据、处理复杂嵌套的配置信息还是构造用于提交到后端的 JSON 请求体其底层逻辑都与本文所演示的技术一脉相承。JSON 的学习至此告一段落。在掌握了数据结构传输的基础之后下一阶段我们将转向 JavaScript 中的面向对象编程探索如何更好地组织和抽象代码逻辑构建更加模块化和可维护的应用程序。扎实的 JSON 基础将为你理解和操作对象数据结构提供有力的支撑。

相关新闻