JavaScript学习笔记整理法:从课程到排错手册
这套课程我前后刷了三遍。第一遍开着倍速当背景音第二遍老老实实跟着敲代码第三遍是准备面试前拿出来重新过了一遍。最后真正让我把 JavaScript 学会的不是视频里那些听得我连连点头的知识点而是两样东西持续地写小例子以及一本自己一个字一个字攒出来的 JS 笔记。这里说的课程是那位讲课时总喜欢把幻灯片做成粉色调、声音很有辨识度的前端 UP 主的 JavaScript 基础教程。不管你是跟着谁的课学今天的重点都不是课程本身而是我如何把一套几十集的 JavaScript 教程沉淀成了一本能随时翻、能排错、能在面试前突击的笔记。这份笔记不只在电脑里躺着它后来真的帮我在实际项目里少踩了好几个坑。1. 这套JavaScript教程我刷了三遍最后留下了什么1.1 从“听懂了”到“会写了”中间隔着一条沟刚开始学 JS 的时候我的状态应该和大多数人一样老师敲一行我跟着敲一行页面上的效果出来了心里很开心觉得自己已经会了。直到某天我想关掉视频自己独立写一个“点击按钮切换图片轮播”的小 Demo才发现我连最基本的取元素、绑定事件、切换 class 这段逻辑都要想很久写出来还有一堆报错。那一刻我才意识到跟着视频写代码时真正的逻辑判断都是老师做的我只是在当打字员。眼睛和手都参与了大脑却没有。后来我了解到这叫“流畅度错觉”当你看着别人一步步解决问题时你会误以为那种顺畅感是自己的能力。破解方法只有一个——脱离视频自己纯手写一遍。为了逼自己完成这个过程我决定做一本 JS 笔记规则很简单不看视频、不看文档用自己的话把每个知识点写下来写不出来就说明没真正理解。这个过程非常痛苦但效果立竿见影。第一遍写笔记时很多知识点我只能写出个大概比如“闭包就是函数套函数”但完全说不清它到底有什么用。这些缺口就是需要重看教程的地方。1.2 笔记不是抄目录而是写给三个月后的自己一开始我犯过一个错把笔记做成了课程目录的摘抄也就是把视频里的小标题抄下来再配上几句老师说的话。这种笔记除了让我看起来很努力之外没有任何用处。因为三个月后我翻回去看根本不知道那些标题底下到底想表达什么。后来我换了一种思路——把笔记当作“写给三个月后的自己看的信”。我不是在记录老师讲了什么而是在记录“我下次遇到这个问题时应该怎么办”。具体来说我的每一个知识点都包含三块内容结论、原因、踩过的坑。举一个例子“ 和 的区别”这个知识点如果只是抄定义那没有任何意义。我的笔记里写的是日常开发一律用 因为 会做隐式类型转换容易造成“看起来相等”的 bug什么时候会用到 几乎没有除非你在做框架底层的兼容判断。笔记里还补了一个我亲身踩过的坑用 if (a null) 判断 a 是否为空这里 a 能同时匹配 null 和 undefined很多人不清楚这一点容易误判。这种“结论 原因 坑”的结构让复习效率高了很多。1.3 一本能打的知识笔记要有自己的目录结构经过反复调整我的 JS 笔记最终分成了六个部分分别是JavaScript 地基数据类型、作用域、闭包、函数与面向对象、DOM 与 BOM 操作、事件机制、ES6 与异步编程、排错手册。前五部分和大多数教程的章节顺序差不多但第六部分“排错手册”是我自己加上的也是后来价值最高的部分。每当我踩到一个新的报错坑就会在手册里记一页报错信息是什么、出错的代码什么样、我当时的猜测是什么、最终怎么解决的。这份排错手册后来成了我面试前最常翻的东西因为大多数面试题问的其实就是你在实操中踩过的坑。2. 整理笔记时JavaScript地基部分我翻了又翻2.1 数据类型判断typeof 和 instanceof 各自管哪一段数据类型这块教程里讲得很快但真正写代码时坑特别多。我笔记里专门用一张表整理了三种判断方式的适用范围这张表后来面试时也被我用上了。判断方式适用情况典型返回值注意点typeof基本类型string、number、boolean、undefined、symbol、bigintstring、number、boolean、undefined、object、functiontypeof null 返回 object这是历史遗留 buginstanceof引用类型对象、数组、函数等true / false判断的是原型链上存不存在某个构造函数跨 iframe 场景会失效Object.prototype.toString.call()万物皆可判断[object Array]、[object Object]、[object Date]最稳定的“终极大招”日常写工具函数时最推荐这里有一个特别容易混淆的点typeof function 返回的是 function但这并不代表 function 是一种独立的数据类型。函数本质上是对象的一种特殊形态在 typeof 看来它有自己的标记而已。所以面试里如果有人问你“JS 有哪些数据类型”你的回答里不能把“function”算作数据类型。另外还有一个高频坑是 NaNtypeof NaN 返回 number但 NaN 并不等于自身。判断 NaN 时isNaN() 会把参数先转成数字再判断比如 isNaN(abc) 返回 true而 Number.isNaN() 不会做转换只判断传入值本身是不是 NaN所以更可靠。这些细节我全部记在了笔记的地基部分因为它们是后边所有代码判断的前提。2.2 作用域链与闭包先画图再做题作用域和闭包是 JS 笔记里最绕的一页我最初以为背下定义就行后来发现不行。闭包的标准定义是“函数嵌套函数内层函数被返回到外部使用时外层函数的作用域不释放”这个定义每个字我都认识但完全不知道它想表达什么。直到我自己画了一次作用域链图才算真正想明白。我用一个生活化的类比帮助自己理解外层函数好比一个自习室里面放着一些书变量内层函数就是坐在自习室里的学生。正常情况下自习室关门后学生就必须离开书也带不走。但闭包做的事情是学生离开时偷偷带走了自习室的钥匙之后任何时候他都可以通过这把钥匙回来拿书。在代码里这把“钥匙”就是被返回到外部的内层函数那些“书”就是外层函数的局部变量。因为内层函数在外面还能被调用所以 JS 引擎就不会回收外层函数的作用域这就是闭包的本质。闭包的实际用途很广防抖和节流函数、模块化开发里的私有变量、循环里保存每一次的状态全都依赖闭包。笔记里我会要求自己每次写闭包代码时先画一张作用域链的图画出全局作用域、外层函数作用域、内层函数作用域之间的嵌套关系标出变量查找的路径。画图的过程帮我建立了空间感背概念时的一片模糊在画图之后清晰了很多。2.3 变量提升与暂时性死区控制台为什么“报错”又“不报错”这块地基内容之所以让我反复翻是因为它在控制台里的表现非常反直觉。比如下面这段代码console.log(a) // undefined var a 10不报错却打印出 undefined。原因是 var 声明的变量会被“提升”到作用域顶部但赋值操作还留在原地。所以第一行执行时 a 已经存在但还没有赋值默认就是 undefined。但如果把 var 换成 letconsole.log(a) // ReferenceError: Cannot access a before initialization let a 10这次直接报错了。原因是 let 声明的变量也存在“提升”但它被放进了“暂时性死区”从作用域顶部到声明语句执行之前的这段区域任何对变量的访问都会报错。为什么不干脆不提升呢这是我笔记里写给自己的问题。答案是var 的“宽松提升”带来了太多隐蔽 bug比如忘记初始化变量时undefined 和真正的值错误混在一起很难排查。let 和 const 通过暂时性死区强制代码“先声明后使用”把错误暴露在更早的阶段宁可报错也不让你带着错误跑。笔记里还记了一个面试高频题for 循环里的 var 和 let 有什么区别。原因就在这里——var 是函数作用域循环结束之后 i 还是那个 i所有异步回调共享同一个变量let 是块级作用域每轮循环都创建了独立的变量绑定。理解了变量提升和暂时性死区这道题就不需要背答案了。3. 从“看得懂”到“写得出”函数和面向对象是分水岭3.1 函数声明与函数表达式两个写法差在哪教程视频中经常出现两种函数写法我学的时候完全没注意它们有什么区别直到某次我在函数声明之前就调用了一个函数表达式浏览器直接报“xxx is not a function”才意识到问题比我想象中严重。函数声明和函数表达式的核心区别在于提升机制// 函数声明整体提升可以在声明位置之前调用 sayHi() function sayHi() { console.log(hello) } // 函数表达式只有变量提升声明位置之前调用会报错 sayBye() // TypeError: sayBye is not a function const sayBye function () { console.log(bye) }原因很简单var或 const / let声明的变量会被提升但右侧的匿名函数不会被提前创建所以在赋值完成之前sayBye 这个变量还是 undefinedconst 则是处于暂时性死区。这段笔记我补充了一条实用经验项目里函数声明式写起来更直观适合工具函数函数表达式更适合做回调、事件处理函数因为它可以随时赋给变量还能传参控制。如果涉及递归或需要解绑事件就必须用函数表达式并给函数命名而不是纯匿名函数。箭头函数也是常客。它没有自己的 this也不能用作构造函数不能使用 arguments。也就是说箭头函数身上发生的“函数行为”比普通函数少很多它是纯表达式逻辑适合做小型回调。我在笔记里的结论是不是所有地方都适合用箭头函数尤其对象的方法里用了箭头函数this 就不再指向这个对象这个后面排错章节还会提到。3.2 this 的指向不在定义处而在调用处this 是 JavaScript 新手阶段最容易懵的概念之一因为它和大多数语言里“this 指向当前对象”的直觉不一样。JS 里 this 的值取决于函数被调用的那一刻而不是函数被定义的地方。我笔记里整理了四条最常见的调用场景调用场景this 指向典型例子普通函数调用window严格模式下是 undefined直接调用一个函数对象方法调用调用方法的那个对象obj.showName()构造函数调用通过 new 创建的实例对象new Person()箭头函数外层作用域的 this自己没有 thissetTimeout 里的回调函函数记忆方法很简单别管函数定义在哪个对象里只看“调用时点号前边是谁”。obj.showName() 的 this 是 objarr.push() 的 this 是 arr。一旦脱离了对象去调用比如 const fn obj.showName; fn()this 就丢了变成了普通函数调用。当初我在笔记里给自己反复强调这一点后来真的帮我解决了排错手册里的一类高频 bug。在实际项目中想要固定 this可以用箭头函数利用外层 this或者 Function.prototype.bind 来硬绑定这一点也是闭包和 this 结合最紧密的地方。3.3 原型链与 class面向对象笔记里最绕的一页面向对象这一章我花的时间最多。因为 ES6 的 class 看起来很简单但背后的原型链机制如果没搞懂遇到复杂继承还是会翻车。new 一个构造函数时JS 引擎做了四件事创建一个新对象把新对象的原型指向构造函数的 prototype 属性把构造函数内部的 this 指向新对象并执行构造函数体如果构造函数显式返回了一个对象则返回那个对象否则返回新创建的对象。原型链的查找规则是“顺着原型链向上找”。对象访问属性时先看自己有没有没有就往构造函数的 prototype 上去找还没有就继续往 prototype 的原型上找直到 Object.prototype。这套机制解释了为什么所有对象都能调用 toString 和 hasOwnProperty——它们定义在 Object.prototype 上。class 是 ES6 提供的语法糖它的本质仍然是构造函数加原型。用一句话说class 是给原型机制穿上的一件衣服穿起来好看但脱了衣服里子还是原型。笔记里我对比了 ES5 和 ES6 的继承写法发现 ES6 的 extends 加 super 其实是在帮我们处理“把父类构造逻辑先跑一遍再把子类构造逻辑接上”这件事。我自己动手手写了一遍继承从借用构造函数继承到原型链继承再到组合继承把每一步为什么会有问题整理清楚之后再去理解 class就变得顺理成章。这部分内容虽然没有直接体现在日常业务里但 Debug 的时候经常用到比如看到某个方法来自原型链我就知道 bug 大概率出在 prototype 被覆盖而不是方法本身写错了。4. DOM、BOM 与事件笔记记满了“我以为”和“实际上”4.1 取元素、改内容从“能用就行”到“知道为什么”DOM 操作是让页面“动起来”的关键但刚学的时候我的代码全是“能跑就行”选择器换来换去完全不清楚各自差异。后来我把常见的选择器和操作方式整理成了一张对照表这张表成了我写交互代码时的速查手册选择器方式返回结果性能适用场景getElementById单个元素最快取特定 id 元素getElementsByClassNameHTMLCollection动态集合较快取同一 class 的元素组getElementsByTagNameHTMLCollection动态集合较快取同一标签的元素组querySelector / querySelectorAll单个元素 / NodeList静态快照较慢但最灵活需要复杂 CSS 选择器时我特别想强调的是 querySelectorAll 返回的 NodeList 是一个伪数组不能直接调用数组方法。很多人写代码时想对它做 forEach发现报错就是因为没有先转成真实数组。我的习惯是 Array.from(document.querySelectorAll(.item))转完之后 map、filter 随便用。另外innerHTML 和 textContent 的区别也要注意innerHTML 会把字符串里的 HTML 标签解析成真实节点有注入风险textContent 只改文本安全得多。如果只是改文字就用 textContent如果确实要插入一段包含标签的结构也必须确保内容是可信的不要直接把用户输入塞进 innerHTML。4.2 事件冒泡与事件委托一个表格讲清楚事件机制这章我从“知道有冒泡”到“能主动用事件委托”经历了一个不小的跨越。事件在浏览器里会经过三个阶段捕获阶段、目标阶段、冒泡阶段。默认情况下addEventListener 的第三个参数为 false意思是只在冒泡阶段处理事件。冒泡意味着点击一个子元素事件会一路向上传给父元素、祖父元素直到 document。事件委托就是利用冒泡机制把子元素的事件监听器注册到父元素上然后通过判断实际触发目标 e.target 来处理。比如一个动态列表const ul document.querySelector(#list) ul.addEventListener(click, (e) { if (e.target.tagName LI) { console.log(e.target.textContent) } })这样的好处有两个一是减少了事件监听器的数量特别是在长列表里性能优势明显二是新增的子元素不需要重新绑定事件天然就能被父级监听到。这里要分清楚 e.target 和 e.currentTargete.target 是实际触发事件的元素e.currentTarget 是当前正在执行事件监听器的元素也就是 ul。在事件委托里这两个指向不同弄混了代码就会出现莫名其妙的 bug。我的笔记里专门为这一对概念画了对比表每次写委托事件之前都会扫一眼。4.3 防抖与节流笔记里除了代码还有两张“时间轴图”防抖和节流是我在实际项目里用到最多的两个工具函数它们都是为了限制函数被频繁触发。课程里讲得比较快但面试和工作中特别常见。我用通俗的话做了区分防抖是“你疯狂触发我等你消停了才执行”好比电梯关门后如果又有人来按门又重新打开只有在一段安静时间内没人按电梯才真正关门走人节流是“每隔固定时间我就执行一次不管你触发多少次”好比景点售票窗口每 10 分钟放一批人进去游客再怎么挤窗口的开闸频率是固定的。// 防抖最后一次生效 function debounce(fn, delay 300) { let timer return function (...args) { clearTimeout(timer) timer setTimeout(() fn.apply(this, args), delay) } } // 节流固定间隔生效 function throttle(fn, interval 300) { let lastTime 0 return function (...args) { const now Date.now() if (now - lastTime interval) { lastTime now fn.apply(this, args) } } }笔记里我不仅贴了这段代码还在旁边画了时间轴图横轴是时间竖条代表用户触发动作阴影段代表实际执行函数的时间。画完图之后我就记住了搜索输入框的联想建议用防抖因为用户敲字过程中我们不想每次按键都发请求页面滚动监听、窗口大小变化这种持续触发的事件用节流保证固定频率的执行。防抖和节流内部都用到了闭包正好把前面学的闭包知识串起来了。4.4 事件绑定的三种方式以及“怎么绑就怎么解”事件绑定有几种方式写法不同行为也不同。直接在 HTML 上写 onclick 属性是最原始的方式混在结构里不利于维护我基本不用。用 element.onclick fn 这种方式DOM 元素上的 onclick 属性只能挂一个函数后挂的会覆盖前面的。而 addEventListener 可以给同一元素、同一事件挂多个监听函数互不覆盖这是它的优势也是陷阱所在——如果不小心重复挂载同一个函数点击一次会执行多次。解绑时要特别注意removeEventListener 必须传入和添加时完全相同的函数引用。如果你添加时写的是匿名函数哪怕长得一模一样在内存里也是两个不同的函数removeEventListener 根本找不到要删的目标事件依然存在。这是排错手册里一个非常经典的问题后面我会专门讲一次完整的排查过程。笔记里我的建议是凡是需要解绑的事件从一开始就不要用匿名函数直接命名一个具名函数再传进去。5. ES6 与异步编程笔记里最难啃的几页5.1 解构赋值、模板字符串、展开运算符写起来爽坑也不少ES6 的内容让写代码的体验好了很多尤其是解构赋值、模板字符串和展开运算符。以前拼接字符串用加号一次写一长串还容易漏符号模板字符串 ${} 直接内嵌变量一目了然。解构赋值最常见的用法是从对象或数组里快速取出需要的字段const user { name: 张三, age: 20 } const { name, age } user const arr [1, 2, 3] const [first, second] arr展开运算符 ... 则常用于复制数组、合并对象。但我笔记里反复标注过... 复制对象时是浅拷贝只会复制一层。如果对象属性里嵌套着另一个对象展开之后里层对象依然是同一个引用。这一点直接导致后面的深浅拷贝内容也是面试时的一个高频扩展问题。解构赋值还有一个隐藏的坑就是会把 this 一起带丢比如从对象里解构出一个方法再单独调用this 已经不再指向原对象。这个坑我排错时遇到了记到了排错手册里后面会展开说。5.2 Promise从回调地狱到链式调用代码的可读性转折点在没有 Promise 之前多层异步操作要靠一层层回调嵌套来实现代码越写越往右缩进俗称“回调地狱”。它不只是难看更重要的是错误处理很难做内层抛出的异常很容易被吞掉。Promise 解决的问题就是让异步流程变得扁平化、可读、可统一处理错误。Promise 有三种状态pending进行中、fulfilled已成功、rejected已失败。状态一旦从 pending 变为 fulfilled 或 rejected就不会再变。then 方法用于处理成功后拿到值catch 处理失败信息。链式调用时then 里面返回的值会被包成一个新的 Promise所以可以继续往下 .then形成一条链。面试里经常问 Promise.all 和 Promise.race 的区别all 等待所有 Promise 都成功返回一个数组race 返回第一个 settled 的 Promise 结果不管成功还是失败。我笔记里给 Promise 部分做了不少标注。比如 .then 里面如果抛出了异常这个异常会传递到后面的 .catch 被捕获。再比如 Promise.all 里如果有一个 Promise reject 了整个 Promise.all 都会 reject并且不会等其他的 Promise 完成。这些行为看起来简单但写并发请求、做并行数据加载时特别容易踩坑。5.3 async/await 配 try...catch异步代码的“同步写法”async/await 是 Promise 的语法糖让异步代码以几乎同步的顺序呈现阅读体验好了很多。但我在刚开始学的时候误以为它是“阻断型”的把异步变成了同步。实际上async 函数执行时遇到 await 会暂停向下执行让出控制权给事件循环等 Promise 结果回来以后再继续向下执行。await 下面的代码本质上相当于被放进了 .then 里区别只是写法更接近同步代码。async function loadData() { try { const user await getUser() const posts await getPosts(user.id) console.log(posts) } catch (error) { console.error(error) } }这段代码最大的好处是错误处理变成了熟悉的 try...catch不用一路 .catch。笔记里我给自己写了一个提醒await 只能用在 async 函数里普通函数里写 await 直接报语法错误如果要在 for 循环里连续 await 多个任务默认是串行执行的速度慢时要考虑用 Promise.all 来并行。还有一个坑是 async 函数只要不写 await它也会立刻返回一个 Promise调用处如果不做处理返回值拿不到真实数据。这些细节在实际开发中很容易绊倒人。5.4 深拷贝与浅拷贝面试题里的“送分题”也是隐藏得最深的坑拷贝这个话题课程里只是简单提了一下但面试和实际项目里出现频率极高。浅拷贝只复制一层深拷贝则要把嵌套的所有层级全部复制出来生成一份独立的数据。展开运算符和 Object.assign 都属于浅拷贝。假设对象里有一个嵌套对象浅拷贝后新对象和旧对象的嵌套子对象仍然指向同一份内存修改其中一边另一边也会跟着变。JSON.parse(JSON.stringify(obj)) 是最常见的“伪深拷贝”但它的坑很多函数、undefined、Symbol 作为属性值时会直接被丢弃Date 会被转成字符串对象的循环引用会直接抛错。我笔记里写了一个手写深拷贝的递归版本核心要点是先判断是不是数组再区分是不是普通对象递归处理每一个属性值同时用 WeakMap 记录已经拷过的对象解决循环引用问题。手写这个函数的过程帮我理顺了“数据类型判断”“递归”“引用类型”几个重要概念这也是我在笔记里把它归到“综合实战”部分的原因。6. 把笔记改造成“排错手册”之后复习效率确实高了很多6.1 解构赋值把 this 带丢了方法一调就报错这个 bug 是我实际开发里遇到的第一个“看起来很怪”的报错。当时我从一个对象里解构出某个方法然后想直接调用const user { name: 阿强, showName() { console.log(this.name) } } const { showName } user showName() // TypeError: Cannot read properties of undefined (reading name)报错的根本原因是我在 3.2 里反复强调的那条规则 this 的值由调用方式决定。showName 被解构出来后是作为一个普通函数被调用的前面没有任何对象this 就不再指向 user所以 this.name 自然就 undefined。排查过程其实不复杂先在报错行打印 this确认它不是 user反推调用方式发现解构赋值改变了 this 的指向。解决方式有三种不要解构用 user.showName() 调用用箭头函数定义方法但它会失去动态 this 的能力或者调用时用 bind 硬绑定 this。这个案例记进排错手册后我对 this 规则的理解就再也没忘过。6.2 闭包加循环变量var 留下的经典问题上一个坑解决后不久我又遇到了闭包加 var 的经典问题也是教程里反复讲、但写代码时还是会犯的错。需求很简单给三个按钮分别绑定点击事件点击第几个按钮就提示“第几个”。for (var i 0; i 3; i) { btn[i].onclick function () { console.log(i) } }结果无论如何点击打印出来的都是 3。原因是 var 声明的 i 属于函数作用域变量 i 在整个循环里只有一个循环结束后它的值已经变成 3所有按钮的 click 回调共享的是同一个 i。等到点击事件被触发时循环早就跑完了i 自然就是 3。解决方式是使用 let每一轮循环都会创建独立的变量绑定闭包访问到的就是当轮循环的 i而不是共享的同一个变量。如果非要使用 var就得通过立即执行函数加参数传递来制造闭包副本。排错顺序是先打印 i 确认值再打印闭包捕获的环境最后定位到循环变量的绑定问题上。这个案例让我真正理解了块级作用域和闭包变量的关系。6.3 事件解绑失败removeEventListener 为什么删不掉有一个功能需求是点击按钮后给另一个元素注册一个 mousemove 监听再次点击后解除监听。我写的代码大概是这样的btn.addEventListener(click, function () { if (isListening) { box.removeEventListener(mousemove, function (e) { console.log(e.offsetX) }) isListening false } else { box.addEventListener(mousemove, function (e) { console.log(e.offsetX) }) isListening true } })问题很典型监听永远解不掉而且每次点击后 mousemove 会同时打印多行。排查时我先在 removeEventListener 前后打印了 box 的事件监听器数量发现注册之后数量一直增加。再仔细看代码原因很直接removeEventListener 里面传的匿名函数和 addEventListener 里的匿名函数不是同一个引用浏览器根本找不到要移除的目标。解决方法是把处理函数提取出来命名function onMouseMove(e) { console.log(e.offsetX) } btn.addEventListener(click, function () { if (isListening) { box.removeEventListener(mousemove, onMouseMove) isListening false } else { box.addEventListener(mousemove, onMouseMove) isListening true } })这个坑让我养成了“事件绑定函数必须具名”的习惯也让我对 JS 函数引用的概念有了更具体的感知。排错手册里我还特意补了一句话匿名函数在 addEventListener 里看着很美但需要解绑时就是给自己埋雷。6.4 异步请求的竞态样式和数据的错乱最后一个排错案例来自异步请求的时序问题。场景很简单搜索框里输入关键词每输入一次就发一次请求结果却是先输入的内容被后返回的旧数据覆盖了。最常见的表现是快速输入“前端”后页面先显示了为“前”字请求到的结果然后又跳回了旧结果。本质上这不是服务端的问题而是客户端没有处理“请求的先后返回顺序不确定”这个事实。排查的完整链路是我先在请求回调里打印了发送时间和接收时间发现一个请求明明先发出去却比后发的请求晚返回页面被旧数据刷新了。解决办法就是加入一个请求序号每次请求发出前让序号加一只有最新序号的响应才允许更新页面或者用 AbortController 取消掉之前未完成的请求。我最后选择了 AbortController因为浏览器本身支持取消请求不光是逻辑上忽略旧响应还能节省网络资源。这个案例的根因已经不在“语法”层面而是对“异步时序”的理解所以也被我记在了排错手册的最后一页。写到这儿这本 JS 笔记已经从我最初以为的“课程目录摘抄”变成了我自己的排错手册和面试弹药库。如果让我给正在学 JavaScript 的朋友一句建议那就是别急着收集别人的笔记一定要写一份自己的哪怕开头是流水账、字很丑、逻辑也乱。因为整理笔记的过程本身就是你和自己的理解互相较劲的过程别人替代不了。最后说一个我自己坚持了很久的小习惯每解决一个报错我会先在笔记里新建一页把报错信息、出错的代码片段和当时的思考过程原样贴上去再写下结论。这样三个月后再看到时我不仅能想起来“怎么改”还能想起来“当时为什么卡住”。这份 JS 笔记后续我还会往里补充网络请求、性能优化和相关设计模式的内容它对我来说不只是一份资料更像是学前端这条路上自己写下的答卷。

相关新闻

变压器DGA油色谱数据建模避坑指南:从数据体检到随机森林基线

变压器DGA油色谱数据建模避坑指南:从数据体检到随机森林基线

简介:这份变压器溶解气体数据.rar面向电力设备运维、故障诊断研究与机器学习建模人员,聚焦变压器油中溶解气体分析这一典型场景,帮助解决故障类型识别与状态评估问题。数据覆盖中低温过热、高温过热、低能放电、高能放电、局部放电及正常状态…

2026/10/13 17:33:45 阅读更多 →
fiji-win64 安装、宏脚本与插件管理:ImageJ 生物图像分析实战指南

fiji-win64 安装、宏脚本与插件管理:ImageJ 生物图像分析实战指南

简介:Fiji(ImageJ)是一个面向 Windows 六十四位系统的免安装图像处理软件,专门服务于科研和医学图像分析用户。解压后不需要安装即可运行,能够直接读取 Raw 格式原始数据,对八位以上的高动态范围图像支持良…

2026/10/13 17:32:45 阅读更多 →
DeepSeek V4.1 Flash:低延迟大模型推理引擎实战解析

DeepSeek V4.1 Flash:低延迟大模型推理引擎实战解析

1. 项目概述:这不是一次常规升级,而是一次“响应速度革命”最近在多个技术社区和开发者群聊里,几乎每天都能刷到类似这样的消息:“DeepSeek V4.1 Flash 内测开了”“Flash 版推理延迟压到 87ms”“实测比上一代 V4 Pro 在长文本场…

2026/10/13 17:32:45 阅读更多 →

最新新闻

MuAsk:离线桌面级Text2SQL工具实战指南

MuAsk:离线桌面级Text2SQL工具实战指南

1. 项目概述:为什么一个桌面级 Text2SQL 工具值得你花十分钟装上试试?“沐问 MuAsk”这四个字刚出现在我 GitHub Star 列表里时,我第一反应是——又一个披着中文外衣的 LLM 前端?点开仓库主页,看到PySide6SQLite本地模…

2026/10/13 18:15:04 阅读更多 →
Cesium地形高度获取实战:从屏幕拾取到服务端批量算高全方案解析

Cesium地形高度获取实战:从屏幕拾取到服务端批量算高全方案解析

做三维 GIS 的朋友应该都有过这种经历:地图上有一个点,或者一串经纬度坐标,领导扔过来一句“把这片区域每个格子的地形高度都给我算出来”,或者产品经理说“鼠标挪过去就显示当前位置的海拔”。你以为在 Cesium 里调一个 API 就完…

2026/10/13 18:15:04 阅读更多 →
基于Xilinx 7系列的pcie以太网卡demo

基于Xilinx 7系列的pcie以太网卡demo

demo综述 1、本demo基于xc7a35tfgg484芯片,pcie部分使用axi memory mapped to pci express,外挂vivado自带的axi dma,网卡mac模块移植开源的eth,实现一个千兆带宽的pcie网卡。 2、工程结构如下3、demo整体框图 ┌────────…

2026/10/13 18:15:04 阅读更多 →
漏洞挖掘----基础配置Ai工具<二>

漏洞挖掘----基础配置Ai工具<二>

< 一 >Ai/mcp思维:1. Ai 是大脑&#xff1a;负责目标&#xff0c;拆解任务&#xff0c;生成测试思路2. 工具&#xff1a;浏览器&#xff0c;BP&#xff0c;插件&#xff0c;脚本&#xff0c;文件&#xff0c;&#xff0c;&#xff0c;负责真实操作&#xff0c;返回证据…

2026/10/13 18:15:04 阅读更多 →
R语言信贷风控建模:Prosper贷款数据的特征工程与可解释性实践

R语言信贷风控建模:Prosper贷款数据的特征工程与可解释性实践

1. 项目概述&#xff1a;这不是一次普通的R语言练习&#xff0c;而是一场对真实信贷行为逻辑的深度解剖Prosper平台是美国较早一批面向个人投资者与借款人的P2P借贷市场&#xff0c;其公开数据集&#xff08;Prosper Loan Data&#xff09;长期被全球高校金融课程、数据科学竞赛…

2026/10/13 18:15:04 阅读更多 →
MySQL 8.0 管理员实战:从安装到调优的完整指南

MySQL 8.0 管理员实战:从安装到调优的完整指南

简介&#xff1a;这份《MySQL 8.0 for Database Administrators Student Guide》是 Oracle University 官方推出的数据库管理员培训教材&#xff0c;面向正在学习或备考 MySQL 认证的 DBA、运维工程师及数据库方向学生&#xff0c;帮助系统掌握 MySQL 8.0 的安装配置、表设计、…

2026/10/13 18:13:58 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目&#xff0c;我最大的感受是&#xff0c;大部分坑不是出在技术前沿&#xff0c;而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节&#xff0c;最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了&#xff0c;很难不碰到舵机控制这道坎。做智能小车要转方向&#xff0c;做机械臂要抬关节&#xff0c;做云台要偏航俯仰&#xff0c;背后都是同一个东西在干活&#xff1a;舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”&#xff1f;——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台&#xff1f;或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜&#xff1f;又或者&#xff0c;在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →