资讯动态

JavaScript双重循环实战:从九九乘法表到函数封装,彻底搞懂for循环嵌套

发布时间:2026/9/16 11:46:25 来源:尧图企业网站定制
1. 为什么九九乘法表是JavaScript入门的“试金石”很多人学JavaScript学到循环这一章都会碰到这道题用双重循环打印九九乘法表。说实话你要是去翻各种教程、题库、面试题这题出现的频率高得离谱。它看起来简单但背后考的其实不是你会不会背口诀而是你有没有真正理解“循环嵌套”这个思维模型。我见过不少初学者能看懂单层for循环一到双重循环就懵了只会照着抄换个题目就不知道从哪下手。九九乘法表之所以被反复拿来当练习题是因为它把三个核心知识点焊在了一起循环的边界条件、内外层循环的执行顺序、字符串拼接的格式控制。这三件事搞透了后面写冒泡排序、遍历二维数组、渲染表格这类常见需求都会顺畅很多。这篇文章我会把这题从最基础的Console版本讲到“可上桌”的网页版本顺带拆一拆常见的报错和坑。不管你是刚学到for循环的纯新手还是想找人帮你理清“为什么这么写”的老读者这篇都值得花几分钟看完。2. 双重循环的核心思路从数学规律到程序逻辑2.1 先把九九乘法表的“形状”搞清楚先别急着写代码先看我们手上有什么原料。九九乘法表长这样1x11 1x22 2x24 1x33 2x36 3x39 ...横向看每一行的乘号左边是从1递增到当前行号纵向看行号从1到9。也就是说任意一个格子都可以用“行号 i”和“列号 j”来描述。程序里我们不叫行号列号叫外层循环变量和内层循环变量。外层管“第几行”内层管“这一行里第几个算式”。两个数字相乘结果就是j * i。关键在于内层循环的结束条件不是固定的9而是当前的 i。比如第5行内层只打印1到5对应的乘法式到了6就停。这个“小循环跟着大循环变”的设计就是双重循环区别于两个独立for循环最核心的地方。独立for循环各跑各的嵌套循环内层每轮都按外层的当前值重新计算范围所以能打印出三角形这种“不规则形状”。2.2 用什么工具实现先解决运行环境文章开始之前先说环境。JavaScript是浏览器的母语所以你不需要装任何IDE打开电脑上的浏览器按F12Mac上是CommandOptionI打开开发者工具切到“Console”控制台标签页就可以直接写代码跑。这是最快验证逻辑的方式。另外如果你在用HBuilder或者其他编辑器做前端开发也可以新建一个HTML文件把JavaScript写在script标签里用浏览器打开HTML文件就能看到结果。HBuilder本身对创建HTML项目、自动补全标签这块体验很不错适合新手练手。我下面的示例主要围绕“控制台运行”和“浏览器页面显示”两种场景来写两种环境都能跑通。3. 第一版实现用for循环和console.log把结果打出来3.1 最朴素的写法for (var i 1; i 9; i) { var row ; // 先准备一个空字符串用来拼接这一行的所有算式 for (var j 1; j i; j) { row j x i (j * i) ; } console.log(row); }运行之后Console面板里会出来这样一串结果1x11 1x22 2x24 1x33 2x36 3x39 ...第一版代码已经能干活了但它值得解读的点其实很多。外层i从1跑到9控制一共有9行。内层j从1跑到i控制每一行输出的算式数量。注意看row这个变量它是内层循环外面的“收纳箱”。为什么必须在每一行开始之前重置一次如果把它放到外层循环外面那第一行的内容会被带到第二行第二行又追加到第三行整个表格就乱套了。这个问题我在带新人时几乎必讲它是理解变量作用域的最直观案例。字符串拼接这里j x i (j * i)括号别漏。j * i如果不用括号包起来在字符串拼接的场景里会被当成字符串的一部分先参与拼接得到的结果可能出乎意料。这个坑值得记一下。3.2 为什么必须是“j i”而不是“j 9”这是新手最容易卡住的地方。想明白这个问题双重循环就通了一半。我们想象一个“坐标网格”i代表行j代表列。如果内层j 9那么每一行都会输出9个算式结果会变成一个“矩形”不是三角形。但九九乘法表的形状是左下角有内容、右上角为空的下三角形所以内层循环必须在j超过i的那一刻停止。为了更形象一点你可以把内层循环理解成“任务列队”外层每跑一轮就下达一个指令“本轮只处理前 i 个任务”。第1轮只做第1个任务第5轮做前5个任务这样叠加起来形状自然就是三角形。这种“外层控制整体规模、内层跟随外层动态伸缩”的设计逻辑在后端渲染行数不固定的表格、打印树形结构、甚至处理某些图像像素时都会反复出现。把这道题吃透等于给这种思维模式打了一次底。3.3 用while实现的双重循环逻辑等不等价很多教程会顺手提一句“for能干的while也能干”但很少给例子。我在这里补一个while版本逻辑和上面的for完全等价var i 1; while (i 9) { var row ; var j 1; while (j i) { row j x i (j * i) ; j; } console.log(row); i; }从可读性角度for循环把“初始化、条件判断、步长更新”都集中在了一行结构更紧凑大部分情况下推荐优先用for。但while版本能帮你看见一件事内层循环的计数器j必须在外层循环体内重新初始化为1。如果在进入内层循环时不对j重新赋值那j会沿用上一轮的终值内层循环直接不执行整张表就废了。这个认知很重要我在调试别人代码时遇到过好几次这个问题。报错不会直接告诉你“j忘了重置”而是给你一个歪七扭八的输出新手看到歪掉的表格根本想不到是初始化的问题。4. 第二版升级把表格“画”到网页上4.1 从控制台走向页面用document.write生成表格控制台输出适合验证逻辑但要展示给别人看还是得做成网页。最直接的办法是改用document.write或者在HTML里做个容器用DOM操作把内容塞进去。用document.write的写法如下document.write(table border1); for (var i 1; i 9; i) { document.write(tr); for (var j 1; j i; j) { document.write(td j x i (j * i) /td); } document.write(/tr); } document.write(/table);这段代码跑起来浏览器里就会出现一个规整的、带边框的表格。注意里层的td是“列”每循环一次输出一个单元格tr是“行”外层每跑一次开一个新行。用document.write有一个众所周知的坑如果页面已经加载完再调用它会清空整个文档重新写。所以这段代码要么放在script标签里跟着页面一起加载执行要么你就别用document.write改用下面这种更可控的DOM操作写法var container document.getElementById(table-container); var table document.createElement(table); table.border 1; for (var i 1; i 9; i) { var tr document.createElement(tr); for (var j 1; j i; j) { var td document.createElement(td); td.textContent j x i (j * i); tr.appendChild(td); } table.appendChild(tr); } container.appendChild(table);这个版本不依赖字符串拼接HTML标签而是通过createElement和appendChild一点点把节点“搭”出来。这样做的好处是你不需要担心特殊字符转义也完全避开了document.write的“加载后清空”问题是实际项目里更常用的方式。4.2 用字符串模板模板字符串优化拼接如果觉得j x i (j * i)写起来太啰嗦、眼睛容易看花可以用ES6的模板字符串反引号包裹${}插入变量来优化for (var i 1; i 9; i) { var row ; for (var j 1; j i; j) { row ${j}x${i}${j * i} ; } console.log(row); }这段代码里${j * i}因为模板字符串的机制会先算乘法再拼字符串不需要手动加括号。同为ES6特性的let也顺手提一下用let声明i、j比var更符合现代规范能避免一些作用域上的隐性坑。日常写代码我建议直接用let。4.3 函数封装把“打印乘法表”变成一个可复用的方法题目本身只是要求打印但在真实项目里重复代码永远不该裸奔。把功能封装成函数是一种本能。function printMultiplicationTable(rows) { for (var i 1; i rows; i) { var row ; for (var j 1; j i; j) { row ${j}x${i}${j * i} ; } console.log(row); } } printMultiplicationTable(9); // 默认打9行 printMultiplicationTable(5); // 想打几行就打几行给函数加一个rows参数之后同一套逻辑既能打9行也能打5行、打12行通用性立刻上来了。这就是函数封装的意义把“做什么”和“怎么做”分开调用方只关心传一个数字进去底层具体怎么循环、怎么拼接不用管。如果你已经学到了数组方法还可以用Array.from配合forEach换一种风格实现function printMultiplicationTable(rows) { Array.from({ length: rows }, (_, i) i 1).forEach(i { const row Array.from({ length: i }, (_, j) j 1) .map(j ${j}x${i}${j * i}) .join( ); console.log(row); }); }这个版本是“声明式”的先构造一个[1, 2, 3, ..., rows]的数组外层遍历它对每个 i再构造一个[1, 2, ..., i]的数组内层把每一项映射成字符串然后用join拼接。效果完全一样但风格更接近现代JavaScript的写法。你要是能在面试里写出这种实现说明你对数组方法相当熟。5. 我踩过的坑常见报错与排查技巧5.1 没有输出先分清“语法错误”和“逻辑错误”“执行没反应”分两种情况一种是代码本身报错了比如少写了一个括号、少写了一个分号控制台会直接抛红字这种叫语法错误另一种是代码能跑但输出不对或者压根没进循环这种叫逻辑错误。排查逻辑错误时我习惯在关键位置加console.log打印变量当前值把程序的“心跳”暴露出来。比如for (var i 1; i 9; i) { console.log(外层当前行, i); for (var j 1; j i; j) { console.log(内层列, j); } }看到打印出来的 i、j 值你就能判断循环有没有进、范围对不对。如果是排错之后记得把这些调试代码删掉不然最后打印的结果会混进一堆乱七八糟的调试信息。5.2 输出无限刷屏页面卡死怎么办内层循环的步长忘记写j或者条件写反会导致死循环。浏览器被卡得动不了十有八九是这个问题。处理方法很简单不要慌直接关掉那个标签页。别指望浏览器能自己缓过来关标签页、重新打开然后回头检查循环条件。我见过的最离谱版本是for (var j 1; j i; j--)j 越减越小永远到不了结束条件整个页面直接崩溃。所以当你写循环的时候心里默念三遍计数器要更新、计数器要更新、计数器要更新。5.3 第一行没对齐、第二行格式全乱如果你看到输出长这样1x11 1x22 1x33 1x44 ...也就是每一行输出的内容一模一样这通常说明内层循环没有用到外层变量 i比如把内层条件写成了j 9或者算结果时写成了j * 9。记住内层循环的终止条件依赖 i这样才能形成三角形内层算式的第二个乘数也必须是 i不是9也不是 j。5.4 报错“x is not defined”如果你用let声明变量但在外部访问了循环变量会得到i is not defined的报错这是作用域引起的。let声明的变量只在当前块级作用域内生效出了for循环的{}就访问不到了。这也是为什么我建议用let的原因之一它能帮你尽早暴露作用域错误而不是像var那样悄悄漏出作用域给后面的代码留下隐患。5.5 问题速查表现象可能原因解决方案页面无输出语法错误、script标签没闭合打开控制台看报错信息输出成矩形不是三角形内层条件写成j 9改成j i所有行内容一样内层算式没用外层变量 i检查j * i中的 i 是否存在输出一遍遍重复停不下来内层/外层缺少j或i检查循环步长页面加载后内容被清空在document.write前页面已加载完成改用DOM操作方式填充内容表格每列挤在一起不好看没加分隔符或CSS样式在页面版给td加padding在控制台版加空格6. 九九乘法表之外的延伸双重循环还能干这些事6.1 打印各种星号图案从“会写”到“会变”学完九九乘法表很多人会顺手练一下“打印星号三角形”“打印倒三角”“打印菱形”。这些题本质上就是九九乘法表的变体换掉内层算式的计算过程换成*再改一改循环方向而已。举个例子打印一个倒三角for (var i 9; i 1; i--) { var row ; for (var j 1; j i; j) { row * ; } console.log(row); }看懂这段之后再对比九九乘法表你会发现核心没变外层决定行数内层跟随外层决定本行元素数量只是外层从9往下走。掌握了这个套路所谓“千变万化”的打印题其实都是一个模型。6.2 冒泡排序双重循环在算法里的常见亮相如果你往后学一点算法一定会碰到的冒泡排序内层和也是双重循环的经典用法。不展开讲算法细节只点一下思想外层每跑一轮就“确定一个数的位置”内层负责相邻元素两两比较交换。外层跑了9次、内层比了9-i次这和九九乘法表“外层定行、内层随行数变化”的结构如出一辙。理解了双重循环的执行顺序读冒泡排序的代码会轻松很多。反过来如果你先学会了冒泡排序回头看九九乘法表也会觉得那时困扰你的东西再简单不过。6.3 遍历二维数组前端表格渲染的前置技能前端经常要处理二维数据比如“学生成绩表”就是一个二维数组arr[行][列]这种取数方式想遍历几乎都是双重循环的活儿var scores [ [98, 87, 92], [76, 88, 95], [90, 91, 93] ]; for (var i 0; i scores.length; i) { var row ; for (var j 0; j scores[i].length; j) { row scores[i][j] ; } console.log(row); }这段代码不用背重点是感受scores[i].length这种“内层长度随外层变化”的模式。理解了它将来处理表格数据、日历组件、网格布局时你会感谢现在认真学双重循环的自己。6.4 从这道题到工程实践函数封装和代码组织很多人学完基础语法不知道“函数”到底有什么用总觉得写成一坨也能跑。九九乘法表这道题其实是极好的函数练习素材。把打印逻辑封装成printMultiplicationTable(rows)之后你可以像搭积木一样继续扩展function printMultiplicationTable(rows) { for (var i 1; i rows; i) { console.log(buildRow(i)); } } function buildRow(rowNumber) { var columns []; for (var j 1; j rowNumber; j) { columns.push(${j}x${rowNumber}${j * rowNumber}); } return columns.join( ); }buildRow负责生成一行printMultiplicationTable负责控制行数职责分离之后代码可读性会好很多调试时定位更快。这不是小题大做而是把“会写循环”升级为“会组织代码”的必经之路。7. 写在最后一个小小的心得我给新人讲这道题时最后总会多说一句编程里所谓“懂”分为两种一种是“看懂了别人的代码”一种是“自己能从零写出来”。九九乘法表这道题只看不写永远不知道自己会卡在哪一步。它逼着你把数学规律翻译成循环结构、把循环结果转成字符串、把字符串输出正确格式每一步都在打磨最基本的代码手感。当初我学这道题也犯过把j重置在外层循环外的错误看着控制台里歪歪扭扭的输出愣了半天。踩过坑之后对循环变量的作用域有了肌肉记忆般的理解。所以如果你现在正卡在这个题目上别灰心多打几遍试着换着方式实现比如改成while、改成模板字符串、改成数组方法每一遍都有每一遍的收获。到最后你会发现九九乘法表本身并不值钱值钱的是那个从“懵”到“通”的过程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价