☰
黑马程序员JavaScript前端开发课后习题:从语法到DOM实战精讲
2026/9/29 7:53:45 网站建设 项目流程

1. 为什么这本教材的课后习题值得逐题啃下来

《JavaScript前端开发案例教程》这本书,黑马程序员的读者群体里几乎人手一本。我在带人和自己复盘的时候发现一个很奇怪的现象:书里的正文部分大家读得挺认真,一到课后习题就集体“跳过”,然后跑去网上搜一份答案文档,扫两眼觉得“哦,原来是这么写”,就翻篇了。结果是章节测验能过,一上手写真实页面就卡死,连一个简单的选项卡切换都要翻半天资料。

问题不在习题本身,而在于大部分人把习题当成了“对答案”,而不是“练手筋”。课后习题的设计逻辑,是把正文里散落的知识点重新打散、重新组合,逼你在没有现成模板的情况下自己搭一遍。这个过程才是真正把知识变成手感的地方。所以这篇文章我不准备给你一份干巴巴的答案清单,那份东西网上到处都是,抄完就忘。我更想做的是把每一类习题背后的考点拆开,讲清楚“为什么这么写”“不这么写会出什么事”“换一种写法行不行”,再附上典型题的完整思路和代码。JavaScript、前端开发、黑马程序员这几个词在你搜索框里出现的时候,你八成已经在学或者准备学这本书了,那这篇文章就是给你省时间的。

内容适合三类人:完全零基础、正在跟着书一章一章往前推的;学过一遍语法但动手能力差、想补基本功的;以及准备面试、需要用基础题把知识网络重新串一遍的。前两类人建议按章节顺序读,边读边在编辑器里敲;第三类人可以直接跳到函数、对象、DOM 和排查技巧那几节,那里面的东西面试里高频得很。

先说说这本教材习题的整体结构。它的题目大概分成四层:第一层是概念辨析,比如选择题、判断题,考的是你对定义和行为的记忆;第二层是语法应用,给你一个小需求,让你用刚学的方法写出来;第三层是逻辑推理,典型的就是循环嵌套打印图形、数组去重、查找最大最小值这类,考的是你会不会把需求翻译成代码;第四层是综合案例,把 DOM、事件、数组、字符串混在一起,做一个能点、能动、能看的小东西。很多人卡在第三层和第四层,其实不是语法不会,而是缺少一套“把中文需求拆成步骤”的方法。

1.1 课后习题到底在考什么:从语法记忆到工程思维

我拿书里非常典型的一道题举例:用循环打印一个九九乘法表。看起来简单,但它同时考了四件事——外层循环控制行数、内层循环控制每行的列数、内层循环的终止条件与行号的关系、以及把数字和字符串拼成一行输出的方式。很多人第一次写会写成for (let j = 1; j <= 9; j++),结果打印出一个九九八十一项的矩形三角块,而不是下三角的乘法表。这就是没理解“内层循环的边界是动态的”这个点。

再往后,同一个知识点会被反复变着花样考。今天让你打印下三角,明天让你打印菱形,后天让你打印倒金字塔。如果你每次都是抄一份答案,那你永远在背题型;如果你理解了“每一行的星号数量 = 与行号相关的函数”,那所有图形题都是同一道题换参数。这就是我常说的:习题的价值不在答案本身,而在于它逼你抽象出规律。这个能力,等你以后写列表渲染、表格分页、日历组件的时候,会直接派上用场。

1.2 三类学习者对应三种刷题策略

零基础的同学,我的建议是“先抄后改”。无所谓,先把书上给的思路或者典型答案原封不动敲一遍,保证能跑通,然后做三件事:改参数、改条件、改输出。比如把九九乘法表改成金字塔,把数组遍历改成倒序遍历,把按钮点击改成鼠标移入。这种小改动花不了几分钟,但它能让你迅速建立“哪一行代码控制哪个效果”的直觉。

有一定基础的同学,直接上“一题多解”。同一个数组去重,你能不能写出循环嵌套版、indexOf 版、Set 版三种?同一个元素获取,getElementById、querySelector、querySelectorAll 的返回值和适用场景各是什么?同一个求和,for 循环、forEach、reduce 三种写法的可读性和性能差异在哪?能写出三种解法的人,面试时基本不会被基础题问住。

准备面试的同学,反过来刷。先看面试题库,把高频考点列出来,然后回到书的目录里找对应的章节习题。比如“var、let、const 的区别”对应变量声明那一章,“this 指向”对应函数那一章,“闭包”虽然书里讲得浅,但可以用作用域链那部分的习题来铺垫。这种带着问题回去刷题的方式,效率比顺着目录刷高得多。

1.3 环境准备:VS Code 加浏览器控制台的最小可用配置

动手之前先把工具链理顺,不然后面一半的精力都耗在“为什么我的页面没反应”上。VS Code 装完之后,我建议至少配三个插件:Live Server 用来起本地预览,代码保存自动刷新,省得你手动 F5;Prettier 用来统一格式,避免因为括号缩进问题怀疑人生;Code Runner 用来快速跑纯逻辑片段,比如你只想验证一下typeof null到底是什么,不用专门建 HTML 文件。

HTML 文件里引入脚本的时候,<script>标签放在</body>前面还是<head>里加defer,这两种写法差别很大。放前面意味着 DOM 已经渲染完了,你直接document.querySelector就能拿到元素;放 head 里没加 defer,脚本执行时元素还不存在,你就会看到那句经典的Cannot read properties of null。这是新手第一大坑,先记住结论:能放到 body 末尾就放到末尾。

浏览器端必须熟练的是 F12 打开开发者工具,重点三个面板:Console 看报错和打印,Elements 看 DOM 结构和实时改样式,Sources 打断点单步调试。我可以负责任地说,把 Sources 面板的断点用熟,你的调试效率至少翻三倍,比在代码里到处console.log高到不知道哪里去。

2. 基础语法章节习题的核心解法

这一部分的题量最大,也最容易被轻视。数据类型的题看着像送分,实际上每年都能筛掉一批人。我把这几章拆成四块来讲:类型判断、运算符与隐式转换、条件与循环、字符串与数组方法。

2.1 数据类型与类型转换题的通用套路

这类题的套路其实特别固定:给你一个值,让你说 typeof 结果;或者给你一段隐式转换,让你说输出。要拿满分,你只需要把下面这张表刻进脑子里。

表达式结果说明
typeof 'abc''string'基本类型
typeof 123'number'注意 NaN 也是 number
typeof true'boolean'
typeof undefined'undefined'声明未赋值
typeof null'object'历史遗留问题,高频考点
typeof Symbol()'symbol'
typeof 123n'bigint'
typeof {}'object'
typeof []'object'无法区分数组
typeof function(){}'function'唯一能直接区分的引用类型

typeof null返回'object'这个问题,书里通常会加一条注,但很多人记不住原因。简单说,早期 JavaScript 用低位标记来区分类型,对象的标记位是 000,而 null 在底层被表示成全零指针,于是就被误判成对象了。这个错误改不了,因为改了会影响大量老代码,所以只能一直留着。要判断 null,用value === null;要判断数组,用Array.isArray(value);要精确判断对象类型,用Object.prototype.toString.call(value),返回[object Array]、[object Null]这种格式。

隐式转换的题,记住三条主线就不会乱:转字符串用String()或加号拼接,转数字用Number()或一元加号,转布尔用Boolean()。具体哪些值转成 false,其实只有六个:false、0、-0、0n、''、null、undefined、NaN。严格来说这算七个,但常考的就这么几个,其余全部为 true,包括空数组[]和空对象{}。

2.2 运算符与隐式转换:最容易翻车的判断题

我见过太多人在这一类题上栽跟头。'5' + 3是字符串拼接得到'53','5' - 3是数字运算得到2,原因在于加号有两种语义(拼接和相加),优先走拼接,而减号乘号除号只有数值语义,所以会先做类型转换。自制一道口诀:加减乘除,加号偏心。

==和===的区别,书里必考。我的建议是:除了if (x == null)这种同时判 null 和 undefined 的写法之外,其余一律用===。你要知道==到底会怎么转:两者类型相同时直接比较;null 与 undefined 互相等于;数字与字符串比较时字符串转数字;布尔值先转数字再比较;对象与原始值比较时对象走valueOf或toString。曾经有一道题是[] == false,答案是 true,因为两边都转成了 0。这种题考试能做对就行,真实项目里千万别写。

浮点数精度那道题也是老面孔:0.1 + 0.2 !== 0.3。原因是二进制没法精确表示这些小数,解决方式是先转成整数运算再除以倍数,或者用toFixed做展示层的四舍五入。要提醒一句,toFixed返回的是字符串,别拿它直接参与后续计算。

短路运算符有两个实用场景,选择题里也常出现。逻辑或||可以用来给参数兜底,比如function fn(name) { name = name || '默认名'; };逻辑与&&可以用在事件绑定前的安全检查,比如el && el.addEventListener(...)。这两个写法在后面的 DOM 章节里会反复出现,早点熟悉没坏处。

2.3 条件语句与循环语句:从“能跑”到“写得好”

条件语句的题,出题点集中在两个地方:一是if与else if的匹配顺序,二是switch的穿透。switch里忘记写break会导致后面的 case 一起执行,这是很多人做判断题时的失分点。有意思的是,穿透并不总是错的,有时候故意不写 break 来做多个条件的合并处理,比如多个分数段落到同一个等级,这种写法在一些老项目里还挺常见。

循环语句的题就多了。for、while、do...while、for...in、for...of各自适用什么场景,是必考内容。for...in遍历的是键名,用来遍历数组会拿到字符串下标,而且还会把原型上可枚举的属性一起带上,所以数组不要用它。for...of遍历的是值,支持数组、字符串、Set、Map,但它不能直接遍历普通对象。

图形打印题我单独说一下。以九九乘法表为例,正确写法是这样:

for (let i = 1; i <= 9; i++) { let row = ''; for (let j = 1; j <= i; j++) { row += j + '×' + i + '=' + j * i + '\t'; } console.log(row); }

内层循环的上限写成i而不是 9,这是三角形状的关键。如果你要打印倒三角,把内层改成j <= 10 - i就行。菱形题则要拆成上下两部分,上半部分 n 行递增,下半部分星号逐个减少。这类题的通用方法只有一句:先找行号和每行元素数量的关系式,再把它写成循环边界。九九乘法表、杨辉三角、空心正方形、菱形,全是这个套路。

关于break和continue,做一个精确的区分:break是跳出当前整个循环,continue是跳过本次迭代进入下一次。嵌套循环里它们只影响最内层,要跳出多层得用标签语句,不过真实项目里几乎不用标签,一般是抽成函数然后return。

2.4 字符串与数组的常用方法清单

字符串方法的题目一般是“给定一串文本,做统计或截取”。高频的有:charAt取指定位置的字符、indexOf找子串位置、slice和substring截取、split分割成数组、trim去首尾空格、replace替换、toUpperCase转大写、includes判断包含。这里有个小坑:substr因为历史原因已经被标记为不建议使用,第二个参数是长度而不是结束位置,容易和slice搞混,答题时优先用slice。

数组方法就更多了,分类记比较清楚。改变原数组的有push、pop、unshift、shift、splice、sort、reverse;不改变原数组的有slice、concat、join、indexOf、includes、map、filter、reduce、every、some、find。判断题最喜欢考“下面哪个方法不会改变原数组”,把这两组分开背就行。

数组合并那道题有好几种写法,我给一个从易到难的顺序:arr1.concat(arr2)最直观;[...arr1, ...arr2]是 ES6 展开运算符,写法现代;如果要在中间插入并且改动原数组,就用arr1.splice(index, 0, ...arr2)。字符串合并同理,加号拼接、模板字符串、join三种方式各有场景,模板字符串在多变量拼接时可读性最好。

排序的坑必须单独拎出来说。[10, 9, 100].sort()默认按字符串比较,结果是[10, 100, 9],数字排序必须传比较函数(a, b) => a - b。降序就是(a, b) => b - a。这道题在书里出现过,在实际项目里也天天出现,写表格排序的时候忘了传比较函数,排名就会莫名其妙。

3. 函数与对象章节的答题技巧

从这一章开始,习题的难度会明显上一个台阶。前面是“知道就能答”,这里是“理解了才写得对”。函数部分考的是作用域和调用方式,对象部分考的是引用类型和 this 指向。

3.1 函数定义、参数与返回值的常见考法

JavaScript 里定义一个函数有好几种写法:函数声明function fn(){}、函数表达式const fn = function(){}、箭头函数const fn = () => {},还有new Function这种基本不用的方式。它们之间最关键的区别是函数声明会提升,函数表达式不会。也就是说,函数声明的调用写在定义之前也能跑,函数表达式写在定义之前就会报fn is not a function。

参数部分有一道经典的判断题:形参和实参的个数不一致会怎样?答案是形参多了对应位置就是 undefined,实参多了就用arguments或者剩余参数接。arguments是一个类数组对象,有 length 和下标,但没有数组的方法,得用Array.from或者[...arguments]转一下。这里顺便提醒,箭头函数内部没有自己的arguments,也没有自己的this,用的时候要留意。

返回值也是易错点。函数没有显式 return 就返回 undefined;return后面千万不要换行,因为自动分号插入机制会把return和表达式拆开,直接返回 undefined。这个坑我实际排查过一次,页面数据死活出不来,最后发现就是 return 后面手贱敲了个回车。

3.2 剩余参数与 arguments 的取舍

剩余参数的语法是function fn(first, ...rest) {},rest是一个真正的数组。它必须放在参数列表最后,而且一个函数只能有一个。相比arguments,它有三个优势:是真正的数组可以直接用数组方法;只包含没有对应形参的那些实参,语义更清晰;箭头函数里也能用。

典型习题是“求任意个数的和”。arguments版本要写遍历加类型转换,剩余参数版本可以直接return rest.reduce((a, b) => a + b, 0)。如果是用箭头函数写,就只能用剩余参数,因为箭头函数里访问不到arguments,它只会沿着作用域链往外找,找到外层普通函数的 arguments,这往往不是你想要的结果。

拓展运算符和剩余参数长得一样,都是三个点,但用法正好相反。剩余参数是在函数定义时收集,拓展运算符是在调用或构造数组时展开,比如Math.max(...arr)就是把数组展开成参数列表。这道题经常以“下面哪个是剩余参数哪个是拓展运算符”的形式出现,抓住“定义时收集、调用时展开”这个点就不会错。

3.3 对象、JSON 与遍历:this 指向是分水岭

对象的题,第一层考创建方式和属性访问,点语法和方括号语法的区别,方括号里可以放变量或者字符串;第二层考遍历,for...in、Object.keys、Object.values、Object.entries的用法;第三层就是 this 指向和深浅拷贝了。

this 指向规则我总结成一个四步判断法:第一步看是不是箭头函数,是的话直接沿作用域往外找,不参与后续判断;第二步看调用方式,obj.fn()这种点调用,this 就是点前面的对象;第三步看是不是用new调用,是的话 this 是新创建的实例;第四步以上都不是,非严格模式下是全局对象,严格模式下是 undefined。用call、apply、bind可以手动改,但bind返回的是新函数不会立即执行。

深浅拷贝的题几乎必考。浅拷贝的方式有Object.assign({}, obj)、展开运算符{...obj};深拷贝的老办法是JSON.parse(JSON.stringify(obj)),但它有明显缺陷:会丢掉值为 undefined 和函数的属性,会把 Date 变成字符串,会丢失原型链,还处理不了循环引用。所以做习题时答这个可以,但要额外写一句它的局限,这一句就是加分项。真要在项目里深拷贝,用递归加 WeakMap 处理循环引用,或者直接上structuredClone。

3.4 通过字符串调用函数这类“骚操作”的正确姿势

有一类题会问:能不能把函数名当成字符串,然后动态调用它?答案是可以,但要看你怎么做。最简单的是window['fnName'](),前提是这个函数定义在全局作用域下。如果函数是对象的方法,就是obj['methodName']()。还有一种是用eval('fnName()'),能跑,但强烈不建议,因为它会把字符串当代码执行,有安全和性能双重问题。

更稳的做法是用映射表,把函数名和函数体的对应关系放在一个对象里:

const actions = { play() { console.log('播放'); }, pause() { console.log('暂停'); }, stop() { console.log('停止'); } }; function dispatch(name) { const fn = actions[name]; if (typeof fn === 'function') { return fn(); } console.warn('未知动作:', name); }

这种写法在事件委托、快捷键绑定、权限按钮分发这类场景里非常好用。习题里的标准答案可能只给window[name],但你把映射表的思路写上去,理解深度立刻就不一样了。

4. DOM 与 BOM 实操题的完整落地流程

DOM 是这本书从“语法练习”跨到“真正做页面”的分水岭。前面所有章节的知识,在这一章都要用上:数组遍历生成列表、字符串拼装 HTML、对象存数据、事件处理函数响应交互。这一章的习题也最有意思,做出来是真的能看能点。

4.1 元素获取与选择器家族的正确用法

获取元素的方法有好几种,返回值差别很大,这是必考点。getElementById返回单个元素;getElementsByClassName和getElementsByTagName返回的是 HTMLCollection,是动态的,DOM 一变它就跟着变,而且没有forEach;querySelector返回匹配的第一个元素,找不到返回 null;querySelectorAll返回 NodeList,是静态的,有forEach但没有map、filter。

给元素改样式有三种方式,优先级从低到高是:改className整体换类名、改classList增删单个类、直接改style行内样式。习题里常见的是“点击按钮切换元素样式”,标准做法是预先在 CSS 里写好两个类,JS 里用classList.toggle('active')切换。这种写法比直接堆style.xxx干净得多,也更好维护。

顺手纠正一个网上流传很广的写法。有些帖子里会出现这样的代码:

const v = document.querySelector('video'); v.style.rotate = '-90deg';

这段代码在现代浏览器里能跑,因为 CSS 有个独立的rotate属性,style.rotate对应它。但如果你要兼容稍老的浏览器,更稳的写法是v.style.transform = 'rotate(-90deg)'。做习题的时候看清楚题目要求,两种情况都能答,但要知道区别在哪:transform是变形函数,可以和 translate、scale 一起组合书写,而rotate是独立属性,写起来更直观但兼容面稍窄。

4.2 事件绑定、事件对象与表单提交的差异

事件这一章的考点密度很高。先说绑定方式,onclick = fn和addEventListener('click', fn)最大的区别是后者可以给同一个元素绑多个同名事件,前者会覆盖。真实的列表渲染里,用事件委托配合addEventListener是标准做法:把事件绑在父容器上,然后通过event.target判断点到的是哪个子元素。这样新增的元素自动就有事件,不用一个个绑。

事件对象里最常用的三个属性是target(实际触发的元素)、currentTarget(绑定事件的元素)、type(事件类型)。this和currentTarget在普通函数里通常指向同一个东西,但换成箭头函数,this就丢了。这是新手写事件处理函数时最容易犯的错。

表单提交和 H5 表单校验的区别,这道题问的人特别多。form元素的默认提交行为会导致页面刷新,所以在做单页交互时要用event.preventDefault()阻止。H5 新增的那些校验属性,比如required、type="email"、pattern、minlength、maxlength,是浏览器原生的校验能力,提交时会自动触发,不需要写 JS。区别就在这:原生的负责基础格式校验,JS 负责业务逻辑校验和自定义提示。两者配合使用的典型场景是,先用原生属性挡住明显不合法的输入,再用 JS 校验两次密码是否一致、用户名是否已被占用这类需要请求接口的规则。

4.3 交互案例:从习题到能演示的小作品

我挑两个书里最有代表性的案例说,都是能直接改成作品集的。

第一个是选项卡切换,考的是排他思想。核心逻辑就一句话:先把所有元素的状态清掉,再给当前点击的元素加上状态。

const tabs = document.querySelectorAll('.tab'); const panels = document.querySelectorAll('.panel'); tabs.forEach((tab, index) => { tab.addEventListener('click', () => { // 先清除全部 tabs.forEach(t => t.classList.remove('active')); panels.forEach(p => p.classList.remove('show')); // 再激活当前 tab.classList.add('active'); panels[index].classList.add('show'); }); });

这段代码的价值在于,你把它稍微改一下,就是轮播图的小圆点、导航高亮、多级菜单,逻辑完全一样。理解了排他思想,这一类题全部通关。

第二个是图片切换或轮播。这里有个细节很多人做错:索引越界。点到最后一张再点下一个,如果不做处理,index就变成了数组长度,访问到 undefined,控制台就会报错。标准写法有两种,一种是取余index = (index + 1) % list.length,一种是用条件判断重置。用取余更简洁,一行搞定,而且反过来还能实现向左循环。

4.4 资源加载检测与页面初始化时序问题

有一类题专门考“什么时候执行代码”。window.onload会等页面所有资源(图片、样式、脚本)都加载完才触发;DOMContentLoaded只等 DOM 结构解析完就触发,不等图片。所以如果你只是要获取元素并绑事件,用DOMContentLoaded更快,用户体验更好;如果你要读取图片的真实尺寸,那就必须用window.onload,否则img.width拿到的是 0。

要检测单张图片是否加载完成,可以这样写:

function checkImage(imgEl) { return new Promise((resolve, reject) => { if (imgEl.complete && imgEl.naturalWidth > 0) { resolve(imgEl); return; } imgEl.addEventListener('load', () => resolve(imgEl), { once: true }); imgEl.addEventListener('error', () => reject(new Error('图片加载失败')), { once: true }); }); }

这里的complete属性很关键,如果图片在绑事件之前就已经加载好了,load 事件是不会再触发的,不加这个判断就会永远卡住。这个坑我在做一个图片墙功能时踩过,本地缓存快的时候百分百复现,部署到线上反而正常,排查了半天。

脚本执行时机还有一个常见写法是给 script 标签加defer或async。defer是延迟到 DOM 解析完再按顺序执行,async是下载完立即执行、不保证顺序。做页面初始化逻辑用defer最省事,不用再把脚本挪到 body 末尾。

5. 常见报错与排查技巧实录

这一节的内容在任何教材里都写得比较散,但对新手来说价值极高。我把实际带人过程中出现频率最高的报错和排查方法整理出来,你可以直接当成速查手册用。

5.1 控制台报错速查表

报错信息根本原因排查方法
Cannot read properties of null (reading 'xxx')选择器没拿到元素检查类名拼写、DOM 是否已渲染、脚本位置
xxx is not defined变量未声明或作用域外访问检查拼写、var/let 声明位置、是否跨作用域
xxx is not a function值不是函数,或方法名写错console.log(typeof xxx)确认类型
Uncaught SyntaxError: Unexpected token语法错误,括号引号不配对看报错行号和上一行,优先查括号
Maximum call stack size exceeded递归没有终止条件检查递归的边界判断
Assignment to constant variable给 const 变量重新赋值改成 let,或者换一种不重新赋值的写法
Unexpected end of input括号或花括号少了一个用编辑器的括号高亮配对功能查

排查的第一原则是看行号,看第一个报错,不要看后面那一长串。往往后面二十条报错都是因为第一条引发了连锁反应,改掉第一条,剩下的自动就消失了。第二原则是相信控制台而不是自己的记忆,你觉得这个变量的值是 5,那就打印出来看看,很多时候你会发现它是字符串'5'。

5.2 逻辑错误:断点调试与 console 的正确用法

语法错误控制台会告诉你,逻辑错误不会。代码能跑,但结果不对,这时候就要靠断点。在 Sources 面板找到你的脚本文件,在可疑的那一行点一下设置断点,然后刷新页面。代码执行到这一行会暂停,你可以把鼠标悬停在任意变量上看它的当前值,也可以用右侧的 Watch 面板添加表达式持续观察。

单步执行的四个按钮要分清:继续执行、单步跳过(不进入函数)、单步进入(进入函数内部)、单步跳出(从当前函数返回)。查一个函数内部的逻辑就用单步进入,只看整体流程就用单步跳过。我个人的习惯是在关键分支上打条件断点,比如只在循环到第 5 次时暂停,这样不会把时间浪费在无关的迭代上。

console家族里除了log,还有几个很实用的成员。console.table(arr)把数组或对象以表格形式打印,看结构化的数据特别清楚;console.dir(el)打印 DOM 元素的对象结构而不是 HTML 字符串;console.time和console.timeEnd配对使用统计耗时;console.trace()打印调用栈,定位“这个方法到底是被谁调用的”非常好用。

5.3 新手最容易踩的一批坑

第一个坑是循环里用var。for (var i = 0; i < 3; i++)配合里面的定时器或者事件回调,你会看到所有回调打印出来的都是最终的 3。原因是 var 没有块级作用域,所有回调共享同一个 i。换成let就好了,因为每次迭代会创建一个新的绑定。

第二个坑是getElementsByClassName返回的是动态集合。你一边遍历它一边往页面里加同类名的元素,循环就永远不会结束,浏览器直接卡死。遇到这种情况,用querySelectorAll或者先把集合转成数组再操作。

第三个坑是事件的this。在addEventListener的回调里,如果是普通函数,this指向绑定事件的元素;换成箭头函数,this就指向外层作用域了。写的时候要么统一用普通函数,要么就在回调外面先const self = this存起来,或者干脆用event.currentTarget,这个属性不受箭头函数影响。

第四个坑是数字字符串相加。从输入框拿到的value永远是字符串,price1 + price2得到的是拼接结果而不是数学和。要么用Number()转换,要么用parseFloat(),要保留整数可以用parseInt(str, 10),注意第二个参数要显式写上 10。

第五个坑是sort不传比较函数,前面已经说过,这里再强调一次,因为真的非常高频。

第六个坑是对象引用。const a = {x: 1}; const b = a; b.x = 2;这时a.x也变成 2 了。函数传参传对象也一样,改的是同一个引用。要避免就在传之前先拷贝一份,浅拷贝用展开运算符,嵌套层数多就用深拷贝。

6. 从课后习题走向真实项目

习题做完了,接下来这一步很多人不会走:怎么把它变成自己的东西。我给你三个方向。

6.1 把习题改造成能放进作品集的小项目

书里的综合案例通常是一个独立功能的演示,比如表格的增删改查、图片轮播、购物车计算。你可以把它们串起来做成一个完整的单页应用:一个待办清单,包含新增、删除、勾选完成、按状态筛选、数据存 localStorage 这几个功能,基本上把数组方法、事件、DOM 操作全用上了。再复杂一点可以做一个设备管理表格,支持关键词搜索、分页、排序、批量删除,这套东西在企业后台里天天出现。

改造的时候有个原则:先让它跑通,再让它好看,最后让它好维护。很多人一上来就想用框架,结果基础 API 都没写熟。用原生 JS 把项目写完一遍,你会对事件流、DOM 重绘、数据驱动的概念有非常直观的认识,之后再学任何框架都会快得多。

6.2 面试题与课后习题的映射关系

面试里的基础题,八成以上能在课后习题里找到原型,只是问法变了。习题问“用循环求 1 到 100 的和”,面试问“手写一个 reduce”;习题问“数组去重”,面试问“不用 Set 怎么去重”;习题问“点击按钮改颜色”,面试问“讲讲事件委托和它的优势”。把习题的解法整理成自己的话术,就是一份现成的面试笔记。

高频的几个方向我列一下:变量声明与作用域、this 指向、闭包、原型与原型链、事件循环与宏微任务、数组常用方法的手写实现、防抖节流、深浅拷贝。这些内容书里可能讲得不够深,但你可以把它们当成习题的延伸,在掌握基础语法之后逐个补。

6.3 借助 AI 工具加速,但别让它替你想

现在用 AI 辅助学 JavaScript 确实效率高,关键是用对方式。我自己的用法是三种:报错看不懂时,把完整报错和上下文代码贴过去,让它解释原因,然后自己去验证;同一个题目让它给两到三种解法,对比它们的可读性和性能差异;写完代码后让它帮忙找边界情况,比如输入为空、数组只有一个元素、索引越界会发生什么。

但有一条底线不能破:**AI 给的代码必须自己跑一遍,跑通了还要能逐行讲清

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询