开篇寄语在上一篇文章中我们从理论层面了解了 JavaScript 是什么、它能做什么、以及它如何与 HTML 和 CSS 协同工作。我们学习了向页面添加 JavaScript 的几种方式也了解了脚本加载策略和注释的用法。今天我们将彻底告别纯理论的讲解真正动手编写一个完整的 JavaScript 小程序。通过构建一个经典的“猜数字”游戏你将亲身体验变量、函数、条件语句、事件、循环和对象这些核心概念是如何在实际项目中协同运作的。这篇文章的学习目标非常明确获得编写 JavaScript 的初步体验大致了解编写一个完整程序会涉及哪些环节和思维方式。我们并不要求你读完本文就能立刻完全理解所有代码的细节也不期望你借此就精通 JavaScript。当前的目的是让你在一个真实的项目场景中概括性地接触一些抽象的概念感受一下 JavaScript 的各种特性是如何组合在一起完成一个具体任务的。所有涉及的具体语法和特性都会在后续的文章中逐一深入讲解所以如果你暂时没有全部理解完全不必担心保持耐心跟着节奏走就好。像程序员一样思考学习编程的过程中语法本身往往并不是最困难的部分。真正具有挑战性的是如何运用这些语法去解决现实世界中的实际问题。换句话说你需要开始像程序员一样思考。这种思维方式大致包含几个层面首先你要清楚地理解你的程序要达成的目标是什么其次你要判断为了实现这个目标应该选用哪些类型的代码结构最后你还需要规划这些代码结构之间如何协同运行形成一个有机的整体。要培养这种思维能力没有捷径可走。你需要持续不断地编写代码在实践中积累对语法和逻辑的感知同时还需要注入一点创造力几项要素缺一不可。你写的代码越多这种思维就越自然解决问题也就越得心应手。虽然我们无法保证你在五分钟内就拥有所谓的“程序员大脑”但在整个课程的学习过程中你将获得大量的机会来反复训练这种思维方式。请你带着这个意识来阅读本文的示例认真体会如何将一个看似复杂的需求逐步分解为一个个可操作的具体任务这是从新手走向熟练的关键一步。示例猜数字游戏的需求分析本文将带你一步步构建一个猜数字的小游戏。在开始写代码之前请你先设想一下这样的场景你的老板或者客户给你布置了以下游戏设计的需求要求你按照这个描述去实现完整的功能。需求是这样的开发一个猜数字游戏。游戏应该随机选择一个 1 到 100 之间的自然数然后邀请玩家在 10 轮以内猜出这个数字。每一轮猜测之后游戏都应该告知玩家答案是否正确如果猜错了则需要告诉玩家他猜的数字是低了还是高了。同时页面上应该显示出玩家之前所有轮次所猜的数字。一旦玩家猜对了或者十次机会全部用完游戏就宣告结束。游戏结束后页面上需要提供一个选项让玩家可以选择重新开始游戏。面对这样一段用自然语言描述的需求一个程序员首先要做的不是立刻打开编辑器写代码而是把这个整体需求拆解成一个个简单的、可操作的具体步骤。这个过程就是所谓的“分解问题”。让我们试着从程序员的思维方式出发把上面的需求转化为一系列清晰的逻辑步骤第一步随机生成一个 1 到 100 之间的自然数作为本轮游戏的正确答案。第二步记录玩家当前的猜测轮数从第 1 轮开始计数。第三步为玩家提供一种能够输入并提交猜测数字的方法。第四步每当玩家提交一个猜测结果先将这个数字记录下来以便玩家可以看到他们之前都猜过哪些数字。第五步检查玩家当前提交的数字是否等于正确答案。如果猜对了则显示祝贺消息同时阻止玩家继续猜测以免造成逻辑混乱并且显示一个控件让玩家可以重新开始游戏。第六步如果猜错了但玩家还有剩余的猜测次数则告诉玩家猜错了并提示他猜的数字是偏低了还是偏高了。然后允许玩家输入下一个猜测数字同时将猜测轮数加一。第七步如果猜错了而且玩家已经没有剩余的猜测次数则告诉玩家游戏结束同样阻止玩家继续猜测并显示重新开始游戏的控件。第八步一旦玩家选择重新开始游戏确保游戏的逻辑和界面全部重置到初始状态然后回到第一步生成新的随机数并开始新一轮游戏。当你把这个步骤清单列出来之后你会发现原本看起来有点复杂的游戏需求现在已经变成了一连串清晰的、可以逐个击破的小任务。接下来我们要做的就是看看如何将这些自然语言的步骤翻译成 JavaScript 代码。初始设置与变量声明在开始编写 JavaScript 代码之前你需要先准备好游戏的 HTML 页面结构。请在你的电脑上创建一个新的 HTML 文件设置好基本的页面结构包括一个标题、一段游戏说明文字以及一个用于输入猜测数字的表单。表单中应该包含一个数字输入框和一个提交按钮。此外页面上还需要预留几个区域分别用于显示玩家之前的猜测记录、上一轮猜测结果的反馈以及本次猜测是偏高还是偏低的提示信息。这些元素在 HTML 中准备好之后我们就可以在页面底部的 script 标签中开始编写 JavaScript 代码了。首先我们需要声明一些变量和常量来存储程序运行所需要的数据。在 script 标签中添加以下代码letrandomNumberMath.floor(Math.random()*100)1;constguessesdocument.querySelector(.guesses);constlastResultdocument.querySelector(.lastResult);constlowOrHidocument.querySelector(.lowOrHi);constguessSubmitdocument.querySelector(.guessSubmit);constguessFielddocument.querySelector(.guessField);letguessCount1;letresetButton;这段代码的核心知识点在于理解变量和常量的区别以及它们的声明方式。在 JavaScript 中变量本质上就是给某个值起的一个名字你可以使用 let 关键字来创建一个变量并且这个变量的值在后续的代码中是可以被修改的。而常量同样也是给值命名但使用 const 关键字声明的常量在创建之后就不能再被重新赋值。在我们的代码中randomNumber 和 guessCount 使用了 let 声明因为它们的值在游戏过程中会被修改。而那些使用 const 声明的常量比如 guesses、lastResult、guessSubmit 等它们存储的是对页面上某个 HTML 元素的引用。虽然这些 HTML 元素内部的文字内容可能会发生变化但元素本身的引用是不变的所以我们用 const 来声明是最合适的。具体来看这行代码let randomNumber Math.floor(Math.random() * 100) 1; 这里运用了 Math 对象提供的两个方法。Math.random() 会生成一个 0 到 1 之间的随机小数包含 0 但不包含 1。把这个小数乘以 100就得到了 0 到 100 之间的一个随机小数。Math.floor() 方法则会将这个小数向下取整得到一个 0 到 99 之间的整数。最后再加 1就得到了我们需要的 1 到 100 之间的随机整数。接下来的几个常量使用 document.querySelector() 方法获取页面上的元素引用。这个方法接受一个 CSS 选择器作为参数比如 “.guesses” 表示选择 class 属性为 guesses 的元素。通过这种方式我们可以把 HTML 元素和 JavaScript 代码关联起来后续就可以在代码中操作这些元素的内容和样式了。最后声明的 guessCount 变量初始化为 1用来跟踪玩家当前的猜测次数。resetButton 变量则预留了一个位置用来存储稍后动态创建的重置按钮的引用它目前还没有被赋值但稍后会派上用场。函数的定义与调用在设置好变量之后我们需要定义一个函数来执行猜测数字的核心逻辑。在之前的代码下面继续添加functioncheckGuess(){alert(I am a placeholder);}函数是编程中最重要的概念之一它是可复用的代码块你只需要编写一次就可以在需要的时候反复调用它运行这避免了大量重复代码的编写。定义函数的方法有多种现阶段我们先使用最基本的方式使用 function 关键字后面跟着函数的名字然后是一对小括号最后是一对花括号。花括号内部就是函数体里面写着调用这个函数时要执行的所有代码。要运行一个函数中的代码你只需要写出函数的名字后面加上一对小括号就可以了这被称为函数调用。让我们来实际体验一下。保存你的代码刷新浏览器页面然后打开浏览器的开发者工具切换到 JavaScript 控制台标签页。在控制台中输入以下内容并按下回车键checkGuess();执行之后你会看到一个弹出警告窗口上面显示着“I am a placeholder”。这就说明你成功定义并调用了一个函数。虽然这个函数目前只是弹出一个占位符消息还没有实现任何实际的游戏逻辑但它已经展示了函数的基本结构定义一次随时调用。函数在程序设计中扮演着至关重要的角色。它们不仅能够消除重复代码更重要的是能够将复杂的程序逻辑拆分成多个小的、职责单一的模块。当你把一个大问题分解成多个小函数时每个函数只负责完成一件具体的事情整个程序的结构就会变得清晰易懂维护和修改也会更加方便。在我们的猜数字游戏中checkGuess 函数将负责检查玩家的猜测是否正确并给出相应的反馈setGameOver 函数负责处理游戏结束时的界面更新resetGame 函数负责将一切重置以便开始新游戏。通过这样的职责划分原本复杂的游戏逻辑就被清晰地组织起来了。运算符的运用在继续丰富 checkGuess 函数之前我们需要了解 JavaScript 中运算符的概念。运算符允许我们执行各种操作包括数学计算、比较大小、连接字符串等等。这些运算符将会频繁出现在我们后续的代码逻辑中因此有必要先对它们建立一个基本的认识。首先来看算术运算符。最基本的加减乘除分别用加号、减号、星号和斜杠来表示。你可以在浏览器的 JavaScript 控制台中尝试输入以下代码来观察运算结果6920-153*710/5除了这些基本的算术运算之外加号运算符还有一个特殊的功能就是可以把文本字符串拼接在一起这个操作在专业术语中被称为“串联”。尝试在控制台中依次输入以下代码constnameBingo;name;consthello says hello!;hello;constgreetingnamehello;greeting;你会发现最终 greeting 变量的值变成了“Bingo says hello!”这就是字符串串联的效果。JavaScript 还提供了一些快捷的复合赋值运算符可以让代码更加简洁。比如如果你想把一个字符串追加到某个已有变量的末尾可以这样写letname1Bingo;name1 says hello!;这里的 运算符等价于 name1 name1 says hello!但写法更加精炼。这种复合赋值运算符在处理字符串拼接和数值累加时都非常实用。另一类非常重要的运算符是比较运算符它们用于判断两个值之间的关系返回的结果是一个布尔值即 true 或 false。最常用的比较运算符包括全等运算符三个等号用来判断两个值是否完全相等包括值和类型都要一致不相等运算符感叹号加两个等号用来判断两个值是否不相等小于运算符和大于运算符用来比较数值的大小关系。这些比较运算符是构成条件语句的基础我们在下一节中就会大量使用它们。你可以在控制台中试着输入一些比较表达式来观察它们的返回结果这对于理解后续的条件逻辑非常有帮助。条件语句的实现现在让我们回到 checkGuess 函数把它从一个简单的占位符替换为完整的游戏逻辑。将原来的 checkGuess 函数替换为以下版本functioncheckGuess(){constuserGuessNumber(guessField.value);if(guessCount1){guesses.textContentPrevious guesses: ;}guesses.textContent${userGuess};if(userGuessrandomNumber){lastResult.textContentCongratulations! You got it right!;lastResult.style.backgroundColorgreen;lowOrHi.textContent;setGameOver();}elseif(guessCount10){lastResult.textContent!!!GAME OVER!!!;lowOrHi.textContent;setGameOver();}else{lastResult.textContentWrong!;lastResult.style.backgroundColorred;if(userGuessrandomNumber){lowOrHi.textContentLast guess was too low!;}elseif(userGuessrandomNumber){lowOrHi.textContentLast guess was too high!;}}guessCount;guessField.value;guessField.focus();}这段代码虽然看起来比较长但只要逐段拆解它的逻辑其实非常清晰。第一行代码使用 const userGuess Number(guessField.value); 声明了一个变量用来存储玩家在输入框中填入的数字。这里有一个重要的细节guessField.value 获取到的是输入框中的文本内容它是一个字符串类型。但我们后续需要把它和随机数进行数值比较所以必须用 Number() 方法将这个字符串转换为真正的数字类型。如果省略这一步后续的全等比较可能会因为类型不一致而得到错误的结果。接下来我们遇到了本节的核心知识点条件语句。条件代码块允许你根据某个条件的真假来决定执行哪一段代码。最基本的条件语句以 if 关键字开头后面跟一对括号括号里面放一个比较表达式再后面跟一对花括号花括号里面是需要条件为真时执行的代码。如果括号里的比较结果是 true则执行花括号内的代码。如果比较结果是 false则跳过花括号内的代码继续向下执行。在这段代码中我们首先检查 guessCount 是否等于 1也就是判断这是不是玩家的第一次猜测。如果是第一次我们就把 guesses 显示区域的文本内容设置为“Previous guesses:”作为猜测记录的标题。如果不是第一次则跳过这个设置直接进入下一步。然后我们将玩家当前猜测的数字追加到 guesses 区域的文本末尾并在后面加一个空格这样多次猜测的数字之间就会有清晰的分隔。这里使用了模板字符串的语法即用反引号包裹字符串并用 ${} 在字符串中嵌入变量的值。接着是一个多层嵌套的条件判断结构它包含了 if、else if 和 else 三个分支。第一个 if 分支检查 userGuess 是否全等于 randomNumber也就是判断玩家是否猜对了。如果猜对了就更新 lastResult 区域的文字为祝贺消息并将它的背景颜色设置为绿色同时清空提示高低的信息区域最后调用 setGameOver 函数结束游戏。第二个分支使用 else if 来检查 guessCount 是否等于 10也就是判断玩家是否已经用完了所有十次机会。如果十次机会用完且还没有猜对就显示游戏结束的消息同样清空提示区域并调用 setGameOver 函数。第三个分支是 else它在前两个条件都不满足时执行也就是玩家猜错了但还有剩余机会的情况。这时我们告诉玩家猜错了将结果区域的背景设为红色。然后在这个 else 分支内部又嵌套了另一层 if 和 else if 判断用来比较玩家猜测的数字与正确答案的大小关系从而给出“猜低了”或“猜高了”的提示。函数的最后三行负责为下一轮猜测做准备。guessCount 将猜测次数加一guessField.value “” 清空输入框guessField.focus() 则将光标重新聚焦到输入框中方便玩家直接输入下一个数字。这种对用户体验细节的关注是写出高质量程序的重要习惯。事件监听与交互现在 checkGuess 函数的逻辑已经完整了但它目前还不会自动执行。我们需要让它在玩家点击提交按钮的时候被调用。这就需要用到事件的概念。事件是浏览器中发生的各种事情比如点击按钮、加载页面、按下键盘、播放视频等等。我们可以编写代码来监听这些事件并在事件发生时执行特定的响应代码。监听事件的结构叫做事件监听器而响应事件触发所执行的代码块叫做事件处理器。在 checkGuess 函数定义之后添加下面这行代码guessSubmit.addEventListener(click,checkGuess);这行代码为 guessSubmit 按钮添加了一个事件监听器。addEventListener 方法接收两个参数。第一个参数是一个字符串表示要监听的事件类型这里我们传入 “click”表示监听鼠标点击事件。第二个参数是当事件发生时要执行的函数这里我们传入 checkGuess。特别需要注意的是作为参数传递的函数名后面千万不要加括号。如果写成了 checkGuess()那么函数会在 addEventListener 这一行代码执行时就被立即调用而不是等到按钮被点击时才调用这显然不是我们想要的行为。保存代码并刷新页面此时你的游戏应该已经能够运行起来了。输入一个数字点击提交按钮你会发现页面会根据你的猜测给出相应的反馈。但如果玩家猜对了或者用完了所有次数游戏就会出错因为我们在 checkGuess 函数中调用了 setGameOver 函数但这个函数目前还没有被定义。接下来就让我们来补全这个缺失的部分。补全游戏结束功能在代码的末尾添加 setGameOver 函数的定义functionsetGameOver(){guessField.disabledtrue;guessSubmit.disabledtrue;resetButtondocument.createElement(button);resetButton.textContentStart new game;document.body.append(resetButton);resetButton.addEventListener(click,resetGame);}这个函数负责处理游戏结束时的界面更新。前两行代码将输入框和提交按钮的 disabled 属性设置为 true也就是禁用这两个表单元素。这是必须的因为如果不禁用的话玩家在游戏已经结束之后仍然可以继续提交猜测这会破坏游戏规则并导致逻辑混乱。接下来的三行代码动态创建了一个全新的按钮元素。document.createElement(“button”) 方法创建了一个 button 元素节点然后将它的文本内容设置为“Start new game”。接着使用 document.body.append 方法把这个新创建的按钮添加到页面的 body 底部。最后一行在这个新按钮上绑定了点击事件监听器当玩家点击这个按钮时会调用 resetGame 函数来重新开始游戏。现在继续添加 resetGame 函数的定义functionresetGame(){guessCount1;constresetParasdocument.querySelectorAll(.resultParas p);for(constresetParaofresetParas){resetPara.textContent;}resetButton.parentNode.removeChild(resetButton);guessField.disabledfalse;guessSubmit.disabledfalse;guessField.value;guessField.focus();lastResult.style.backgroundColorwhite;randomNumberMath.floor(Math.random()*100)1;}这个函数的职责是将游戏中的一切完全重置到初始状态让玩家可以开始全新的一轮游戏。它首先将 guessCount 重置为 1。然后选中结果展示区域内的所有段落元素通过循环将它们的文本内容逐一清空为空字符串。接着使用 parentNode.removeChild 方法把之前动态添加的重置按钮从页面中移除。之后重新启用输入框和提交按钮清空输入框的值并将光标聚焦进去。同时也清除 lastResult 区域的背景颜色将其恢复为白色。最后最关键的一步是重新生成一个 1 到 100 之间的随机数这样玩家猜测的目标就变成了一个全新的数字。至此一个功能完整的猜数字游戏就构建完成了。保存所有代码刷新页面尽情地玩上几盘检验一下各个功能是否都按预期运行。如果出现了问题请仔细对照文中的代码检查是否有遗漏或拼写错误。循环的妙用在 resetGame 函数中我们使用了一个循环结构来清空所有信息段落的内容。循环是编程中一个至关重要的概念它让你能够重复运行一段代码直到满足某个条件为止。JavaScript 提供了多种循环方式这里我们使用的是 for…of 循环它是遍历数组元素的一种简洁而优雅的方式。让我们在浏览器控制台中做一个简单的实验来理解循环的工作原理constfruits[apples,bananas,cherries];for(constfruitoffruits){console.log(fruit);}当你执行这段代码后控制台会依次打印出 apples、bananas、cherries 这三个字符串。第一行代码创建了一个数组数组就是一组按顺序排列的值的集合在 JavaScript 中用方括号来表示里面的每个元素之间用逗号分隔。这个 fruits 数组包含了三个字符串元素。for…of 循环的语法是这样的for 关键字后面跟一对括号括号中先用 const 或 let 声明一个临时变量然后用 of 关键字连接你要遍历的数组。循环执行时它会依次取出数组中的每一个元素赋值给你声明的那个临时变量然后执行一次花括号中的代码。当数组中的所有元素都被遍历过一遍之后循环自动结束。回到我们的游戏中resetGame 函数中的循环使用了相同的原理constresetParasdocument.querySelectorAll(.resultParas p);for(constresetParaofresetParas){resetPara.textContent;}第一行使用 document.querySelectorAll 方法选中了 class 为 resultParas 的容器内所有的 p 元素。与 querySelector 返回单个元素不同querySelectorAll 返回的是一个包含所有匹配元素的集合这个集合的行为类似于数组。然后 for…of 循环遍历这个集合中的每一个段落元素将它的 textContent 属性设置为空字符串从而清除掉页面上的所有提示信息。这里有一个值得注意的细节虽然 resetParas 是用 const 声明的常量但这并不妨碍我们修改它所包含的元素的内部属性比如 textContent。const 只保证变量本身的引用不会改变也就是说你不能让 resetParas 指向另外一个完全不同的东西但指向的对象内部的属性是可以自由修改的。对象的基本概念在结束本文之前让我们再接触一个重要的概念对象。JavaScript 中的一切几乎都是对象对象是存储在单个分组中的相关功能的集合。你可以把对象理解为一个容器里面装着各种相关的属性数据和方法可以执行的函数。你可以创建自己的对象但这属于较高阶的知识我们暂时不展开。目前我们只需要了解浏览器内置的对象它们已经提供了大量开箱即用的强大功能。在我们的猜数字游戏中其实一直在使用各种对象只是你可能没有意识到而已。比如在代码顶部声明这个常量时constguessFielddocument.querySelector(.guessField);这里的 document 就是一个内置对象它代表当前浏览器中加载的整个 HTML 文档。querySelector 是 document 对象提供的一个方法你可以调用它来查找页面上的元素。我们传入 CSS 选择器 “.guessField” 作为参数这个方法就会返回对页面上 class 为 guessField 的那个 input 元素的引用。现在 guessField 这个常量持有的是一个指向 input 元素的引用而这个 input 元素本身也是一个对象。既然是对象它就拥有自己的属性和方法。属性是存储在对象内部的变量其中存储着关于这个对象的各种信息。方法则是存储在对象内部的函数调用它们可以让对象执行特定的操作。我们可以在浏览器控制台中直观地探索这些概念。打开你的游戏页面然后打开开发者工具的 JavaScript 控制台。输入 guessField 并回车控制台会显示这个变量指向的 input 元素对象。接着输入以下代码guessField.value2;你会发现输入框中的数字立刻变成了 2。这就是在访问和修改对象的属性。value 是 input 元素对象的一个属性它存储着当前输入框中的文本内容。再试试输入 guesses 并回车控制台会显示一个指向 p 元素的引用。然后输入guesses.value;浏览器会返回 undefined因为段落元素并没有 value 这个属性。value 属性是表单元素特有的。要修改段落中的文本内容你需要使用 textContent 属性guesses.textContentWhere is my paragraph?;更有趣的是页面上的每个元素都有一个 style 属性这个属性本身又是一个包含各种 CSS 样式的对象。你可以通过它来动态修改元素的样式。在控制台中依次尝试输入以下代码guesses.style.backgroundColoryellow;guesses.style.fontSize200%;guesses.style.padding10px;guesses.style.boxShadow3px 3px 6px black;你会发现页面上的段落元素实时地改变了外观。这就是 JavaScript 操作 CSS 的强大之处通过修改元素的 style 对象中的属性你可以动态地对页面样式进行精细的控制。对象的概念是 JavaScript 中最核心的知识之一。当你逐渐习惯用“一切都是对象对象有属性和方法”这样的思维去看待代码时很多原本模糊的概念就会变得清晰起来。总结恭喜你完成了 JavaScript 学习的第二天。今天我们从一个实际的游戏项目出发完整经历了从需求分析、任务拆解到代码实现的全部流程。我们亲自动手构建了一个功能完备的猜数字游戏在这个过程中你接触到了变量和常量的声明与赋值学习了函数的定义与调用理解了运算符的用法掌握了条件语句的编写了解了事件监听和响应的机制还体验了循环的强大以及对象的基本概念。这些知识单独拿出来看似乎都不难理解但真正考验能力的是如何将它们有机地组合在一起解决一个实际的问题。这也是本文最希望传达给你的核心理念编程不仅仅是记住语法更重要的是学会拆解问题、组织逻辑、并用代码将想法变为现实。如果你在阅读本文的过程中觉得有些概念还比较模糊或者某些代码的写法还不太理解这完全正常。本文的目的是让你获得一个整体的感性认识而不是要求你立刻掌握每一个细节。在接下来的文章中我们将对每一个知识点进行更加深入和系统的讲解帮助你逐步建立起扎实的 JavaScript 基础。在下一次学习之前建议你把这个猜数字游戏的代码从头到尾自己敲一遍甚至尝试修改一些参数比如把猜测次数改成 15 次或者把随机数的范围改成 1 到 50看看游戏的行为会如何变化。动手实践永远是最好的学习方式我们下一篇文章再见。还在为 JavaScript 代码写得像“意大利面条”、逻辑混乱难以维护而头秃收藏本文持续跟进后续将系统分享 JS 高效语法糖、浏览器兼容与 Polyfill 实战、手写核心源码解析、常见坑点避雷指南从基础语法到进阶逻辑一站式打通助你快速提升前端开发硬实力