尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

JavaScript核心基础与常见坑:从数据类型到闭包一次讲透

发布时间:2026/9/29 17:31:13

资讯中心
01
ARTICLE

JavaScript核心基础与常见坑:从数据类型到闭包一次讲透

JavaScript核心基础与常见坑:从数据类型到闭包一次讲透
1. JavaScript的人设为什么它总被嫌弃却到处都在1.1 十天写出来的语言凭什么活到今天1995年网景公司的工程师Brendan Eich只用了大约十天就设计出了JavaScript的第一个版本。当时它叫Mocha后来改叫LiveScript最后为了市场宣传改名JavaScript。注意这个改名纯粹是蹭热度——JavaScript和Java在语法、设计上没有血缘关系Java是编译型、强类型、基于类继承JavaScript是解释型、动态类型、基于原型继承。网上那句JavaScript和Java的关系就像老婆和老婆饼虽然有点夸张但真说出了实情。一个十天仓促诞生的语言自然会留下各种历史包袱typeof null返回object这种荒唐结果、和 难舍难分的暧昧关系、变量提升带来的迷惑行为这些坑直到今天还在坑新人。但它偏偏赶上了浏览器普及的大潮随着网景浏览器和后来的IE、Chrome一路坐稳了浏览器唯一脚本语言的位置。2009年Node.js出现以后它又跑到了服务器端。现在我身边的项目前端是JavaScript后端是JavaScript桌面工具也是JavaScript一次学习到处运行这句话反而在它身上先成了现实。1.2 解释型、动态类型、单线程三个关键词决定一切认识JavaScript不需要先把规范读完只要先记住它的三个底层属性。第一解释型语言。多数场景下JS不需要像C那样先编译成可执行文件而是由引擎逐行解释执行。V8引擎后来引入JIT即时编译来提升性能但对普通开发者来说大致可以理解为边翻译边运行。第二动态类型。变量的类型在运行时才确定同一个变量今天存数字、明天存字符串完全合法。写起来确实方便但也埋了不少雷——一个函数里传入的是字符串还是数组行为可能完全不同调用方一不留神就会中招。第三单线程加事件循环。浏览器里只有一个主线程但通过事件循环机制配合定时器、网络请求、用户操作这些异步任务实现了不卡顿的非阻塞体验。正因为单线程回调、Promise、async/await这些概念在JavaScript里才显得如此重要。这三点加在一起决定了JavaScript这门语言看似随性内核却有一套严密的规则。很多新人喊JS太乱其实多半是还没理解这套规则。1.3 ECMAScript、DOM、BOM一个名字背后的三层东西新手经常搞不清JavaScript到底等于ECMAScript吗严格来说ECMAScript只是这门语言核心的规范编号决定的是语法和基础API。而你在浏览器里真正用到的JavaScript其实包含三层ECMAScript负责怎么说人话也就是变量、函数、流程控制这些基础语法DOM负责怎么操作页面比如document.getElementById、appendChild这类操作BOM负责怎么跟浏览器对话比如window.location、setTimeout、navigator这些对象。到了Node.js环境没有DOM和BOM取而代之的是文件系统、网络等模块。这也是为什么有时一段代码在浏览器里跑得好好的换个环境就报错——大概率是用到了某个环境特有的API。理解了这层划分以后再看到JavaScript运行时报错时第一反应就应该是先分辨是语法问题、运行逻辑问题还是环境API不存在的问题。2. 语法地基变量、数据类型与运算符里那些反直觉的坑2.1 var、let、const到底怎么选很多教程会告诉你var是老古董以后统统用let和const。这话大方向没错但它没解释为什么。var真正的麻烦是两点函数作用域和变量提升。函数作用域意味着在for循环里用var声明的变量循环结束后依然活着甚至能污染外层变量提升则允许你在声明之前使用变量结果拿到一个undefined。这两点凑一起让程序的行为变得相当灵异。举个例子for (var i 0; i 3; i) { // 循环体 } console.log(i); // 输出3var把i泄漏到了循环外面let和const是ES6引入的块级作用域变量只在所在的{}块内有效而且存在暂时性死区——声明之前访问它直接报错。这反而让代码行为更可控。我日常写代码的习惯是默认用const真的需要重新赋值才用letvar只有在维护老代码时才会碰到。2.2 数据类型与typeof的坑王typeof nullJavaScript的数据类型分两类基本类型和引用类型。基本类型包括string、number、boolean、null、undefined、symbol、bigint引用类型主要是object数组和函数其实也是object的变体。初学者最常踩的坑就是typeof null的结果是object。这其实是第一版JavaScript的设计失误后来为了兼容海量旧代码一直没改。另一个同样常见的坑是typeof数组返回的也是object所以判断一个值是不是数组不能用typeof必须用Array.isArray()。再比如typeof NaN返回number——虽然NaN全称是Not a Number但它确实被当成数字类型对待判断NaN的可靠方式是Number.isNaN()。console.log(typeof null); // object console.log(typeof [1, 2]); // object console.log(Array.isArray([])); // true类型这一关不要靠背靠试。打开浏览器控制台挨个敲一遍记忆会深很多。2.3 与为什么我劝你永远用和到底什么区别这是被问最多的问题之一。简单说比较值时做隐式类型转换比较值和类型不转换。听起来更智能但这种智能经常帮倒忙。给你看几个能让新手崩溃的结果0 0 结果为 true1 true 结果为 truenull undefined 结果为 true[] false 结果为 true但 [0] true 结果为 false看到没这玩意儿一旦智能起来完全没法凭直觉判断。我的建议简单粗暴一律使用只有在你明确想忽略类型时再考虑而且最好加一行注释说明意图。还有一个类似的坑是算术运算里的隐式转换5 3结果是53因为号一旦碰到字符串就变成了拼接操作而5 - 3结果是2因为减号只做数值运算。记住一句话加号是双面间谍减号是专一数值党。2.4 运算符别让优先级坑了你运算符本身不难算术、比较、逻辑、位运算、赋值。真正值得留心的是逻辑运算里的短路求值。很多老代码习惯用a || b做默认值用a b做条件执行这在JavaScript里完全合法因为逻辑运算符返回的不是布尔值而是决定结果的那个操作数const name input || 默认名; // input为假值时取默认名 const isAdmin user user.admin; // user存在时才读取admin这种写法简洁但对新手来说可读性一般我建议只在确定自己明白假值概念后再用。至于运算符优先级我的经验是不要背优先级表遇到复杂表达式就加括号。代码是写给人读的加括号不是怂是体面。3. 函数与作用域学JavaScript最大的分水岭在这里3.1 函数是一等公民意味着什么在JavaScript里函数可以被赋值给变量、作为参数传给别的函数、还能作为返回值返回。这个设计把函数提到了一等公民的位置直接催生了回调函数和高阶函数这套玩法。你现在学的Vue、React里所有的钩子监听器底层全是函数在传递。三种常见的定义方式// 函数声明 function add(a, b) { return a b; } // 函数表达式 const add function(a, b) { return a b; }; // 箭头函数 const add (a, b) a b;看起来差别不大但有个关键点函数声明有提升可以先调用后定义函数表达式没有。另外箭头函数没有自己的this没有arguments不适合用new调用。所以在很多现代框架的回调里大家都用箭头函数来锁住外层this。3.2 作用域链像一层层套着的房间作用域就是变量可以被访问的范围。JavaScript在查找变量时会沿着当前作用域 - 外层 - 再外层一路向上找直到全局作用域。这条查找路径叫作用域链。内层作用域能读外层变量外层读不到内层变量这个方向不能反过来。这里有个非常经典的坑如果不小心在函数里给一个不存在的变量直接赋值比如没写let就写count 1这个变量会意外变成全局变量。这在团队项目里特别危险可能莫名其妙覆盖掉别人的同名变量。解决办法就一条任何变量先声明再使用let或const写在前面。3.3 闭包把背包背在身上闭包是我认为JavaScript里最值得反复琢磨的概念。简单来说当一个函数引用了它所在作用域的变量并且这个函数跑到外部去执行时它依然能记住那些变量这就形成了闭包。可以把它想象成出门时背上的包外层函数是家局部变量是家里的东西返回的内部函数是要出门的人他背了个背包把要用到的东西装进包里一起带走了。看这个最经典的计数器function createCounter() { let count 0; return function() { count; return count; }; } const counter createCounter(); console.log(counter()); // 1 console.log(counter()); // 2count没有被销毁而是被背包带走了。防抖、节流、柯里化、私有变量底层全是闭包这套逻辑。面试常问的闭包会不会内存泄漏本质就是在问这个背包是不是永远不放下如果闭包引用了一个很大的对象而且这个函数又常驻内存那确实可能造成额外的内存占用。3.4 thisJavaScript里最玄幻的关键字this在JavaScript里不是简单理解为这个对象就够的。它的指向取决于函数怎么被调用而不是在哪里定义。最常见的四种情况调用方式this指向普通函数调用非严格模式下指向window/global对象对象方法调用指向该方法所属对象call/apply/bind调用指向手动指定的对象new调用指向新创建的对象箭头函数是个例外它没有自己的this会沿作用域链向上找最近的非箭头函数的this。新手阶段不需要把四种情况完全背熟但至少要会判断这里的this到底是谁。判断标准不是代码写在哪而是函数是怎么被调用的这个思维转变很关键。4. 条件与循环把程序思维落实成看得见的代码4.1 真值与假值JavaScript的真假跟直觉不一样if语句并不要求条件一定是布尔值任何值都能参与真假判断。false、0、-0、0n、、null、undefined、NaN 这八个值会被判定为假其余全是真。规则本身不复杂但有些结果特别反直觉if () {} // 不执行因为是假值 if ( ) {} // 执行因为带空格的字符串非空 if ([]) {} // 执行空数组是真值 if ({}) {} // 执行空对象是真值所以判断一个数组是不是为空正确写法是arr.length 0而不是if ([] true)这种后者铁定翻车。真值假值这套规则几乎天天用到接口返回一个空字符串、undefined、0能不能继续往下走都是靠它来判断。4.2 if、三元表达式和switch选哪种更合适if/else是绝对主力没什么好讨论的。三元表达式适合二选一且表达式简单的场景比如let tip isVip ? 尊贵用户 : 普通用户;。但如果嵌套到两层以上三元表达式可读性会断崖式下跌建议立刻改成if。switch适合对同一个变量的多种取值做分支比如根据星期几显示不同文案。注意switch用的是严格相等比较而且每个case通常要写break否则会穿透继续执行下一个case这种bug非常隐蔽很多人踩过。我自己的习惯是写case时顺手把break补上哪怕一开始觉得这个case不需要break也先写上等逻辑跑通了再删不迟。4.3 for循环的三兄弟别再用错in和of经典for循环for (let i 0; i arr.length; i)能用但写起来啰嗦。ES6给了两个更友好的循环for...in和for...of。很多人把这两个搞混其实区分很简单for...in遍历的是对象的键名遍历数组时拿到的是索引而且类型是字符串for...of遍历的是可迭代对象的值适合数组、字符串、Set、Map。所以遍历数组用for...of比for...in更符合直觉。比如const fruits [苹果, 香蕉, 橙子]; for (const fruit of fruits) { console.log(fruit); // 依次输出 苹果、香蕉、橙子 }如果真的需要同时拿到索引我一般会把数组用entries()包一下而不是回头去用for...in。4.4 while与break/continue的配合while循环适合不知道具体循环多少次只知道满足条件就继续的场景比如轮询接口返回状态、连续读取数据包。break用于跳出整个循环continue用于跳过本次循环、进入下一次。写while最怕的是无限循环条件永远为真程序卡死。遇到疑似无限循环第一件事是检查循环体里有没有更新条件的语句第二件事是打开调试工具在循环里打一个计数变量超过一定次数就手动停。这个排查思路新手阶段一定要养成不然很难发现自己写的循环为什么让浏览器标签页直接无响应。5. 字符串、正则与JSON数据处理的三驾马车5.1 字符串方法每天都在用但经常用混字符串在JavaScript里属于基本类型但它又能调用方法刚接触的人往往会觉得这很神奇——其实是因为引擎在调用方法时临时把它包装成了字符串对象。日常开发里重点掌握这几个就够用trim()去首尾空格、includes()判断包含、startsWith()/endsWith()判断首尾、toUpperCase()/toLowerCase()转换大小写、split()按分隔符拆数组、replace()替换、slice()截取。还有一个强烈推荐的是模板字符串用反引号加${}插值拼接HTML时比一串号舒服太多const user { name: 林晓, city: 成都 }; const msg 你好${user.name}欢迎来自${user.city};模板字符串还能直接换行并保留格式这在生成多行文本时特别省心。5.2 正则表达式别背语法掌握组合拳正则让很多人头疼一看到那一堆符号就想绕道。我的建议是入门阶段不用背完整语法只掌握三类核心要素就够了字符类\d表示数字、\w表示字母数字下划线、\s表示空白、.表示任意字符量词*表示0次或多次、表示1次或多次、?表示0次或1次、{n,m}表示n到m次边界^表示开头、$表示结尾。有了这三类就能拼出绝大多数实用判断。最简单的例子校验手机号以1开头、第二位是3到9、共11位数字const phone 13812345678; const reg /^1[3-9]\d{9}$/; console.log(reg.test(phone)); // true记住一个口诀字符定是什么量词定有多少边界定从哪到哪。平时写复杂的正则先用在线工具验证确认匹配结果再贴进代码比硬着头皮在代码里试快得多。5.3 JSON前后端通信的通用语言JSON不是JavaScript专属的格式但JavaScript对它的支持是最顺手的。JSON.parse()把JSON字符串变成对象或数组JSON.stringify()把对象或数组变成JSON字符串接口联调时几乎每天都在用。这里有几个坑很多人踩过提前告诉你免得浪费一晚上排查JSON.stringify无法序列化函数和undefined对象里如果存在会被直接丢弃Date对象会被转成一个字符串接收端要自己解析成时间对象循环引用时比如a.self a直接调用stringify会报错。const obj { name: 张三, score: 98, say: function() {} }; console.log(JSON.stringify(obj)); // 输出 {name:张三,score:98}say被丢弃5.4 Math、日期两个被低估的标准库模块Math和Date在标准库里存在感不高但用到的频率一点不低。Math.round()四舍五入、Math.floor()向下取整、Math.ceil()向上取整、Math.random()生成0到1之间的随机数、Math.max()/Math.min()找极值做抽奖、分页、图表都能用上。日期处理入门阶段直接new Date()再用getFullYear()、getMonth()这些getter方法取值就行。只是要牢记一个怪规矩月份从0开始所以getMonth()返回5其实代表六月。涉及到复杂的时区、格式化、天数计算我不建议自己封装用day.js或date-fns这类体积很小的库比手写一堆边界条件靠谱得多。6. 事件处理与异常兜底从会写到能扛中间差这两块6.1 事件流冒泡与捕获事件不止是鼠标点了按钮按钮执行函数这么简单。一次click事件发生后会经历三个阶段从window往下到目标元素的捕获阶段到达目标元素再往上冒泡回window的冒泡阶段。addEventListener的第三个参数传true就是捕获不传或传false就是冒泡。理解冒泡机制最大的价值在于它催生了事件委托这个经典优化方案。假设一个列表里有1000个按钮挨个绑定监听器既耗时又占内存。正确做法是把监听器绑在它们的父元素上利用冒泡机制通过e.target去判断到底点了哪个按钮。这在面试里经常出现真实项目里更是性能优化的重要一环。6.2 常用事件与事件对象开发中离不开的事件大致是有数的click点击、input输入、change变更、submit表单提交、keydown键盘按下、scroll滚动、mouseenter鼠标进入等。每个事件处理函数都会收到一个事件对象e三个属性最常用e.target是触发事件的元素e.preventDefault()能阻止默认行为比如阻止表单提交后刷新页面e.stopPropagation()能阻止冒泡。平时容易踩的一个坑是mouseover和mouseout它们在鼠标移入子元素时也会触发导致闪烁或重复执行。所以进入离开类的事件推荐用mouseenter和mouseleave这两个不会冒泡行为更符合直觉。6.3 运行时报错不是洪水猛兽学会读报错卡片很多新人一看到红色报错就慌了其实大多数报错信息已经把答案写在脸上了报错含义排查方向ReferenceError变量未定义检查变量名拼写、是否漏了声明TypeError把非函数当函数调用多半是值为undefined打印出来确认RangeError超出调用栈上限检查递归是否缺终止条件SyntaxError语法错误运行前就会暴露检查括号、分号排查报错有个固定套路先看浏览器控制台红色部分的第一行上面会告诉你哪个文件哪一行出问题点进去看代码再在关键位置用console.log打印中间值基本能解决九成问题。剩下的一成用debugger语句或者控制台打断点单步调试。6.4 try/catch与Promise防御性编程的入门try/catch是捕获同步异常的经典方式把可能出错的代码包在try块里一旦抛出异常就进入catch块拿到错误信息finally块则无论是否出错都会执行适合做清理工作。但有个细节特别容易坑人异步代码里抛出的异常单纯靠外层try/catch不一定能捕获到。比如setTimeout回调里报错、Promise里reject了都必须用对应的机制去处理。对Promise用.catch对async函数用try/catch把await包起来。网上那么多javascript运行时报错的搜索很多都是因为错误发生在异步回调里结果没有被捕获异常悄悄溜走了。7. 过来人的大实话这样学JavaScript才能少走弯路7.1 先忘掉框架把原生能力打扎实每次有人问我能不能直接学Vue我都会泼一盆冷水框架只是工具箱工具用得好不好取决于你对语言本身的理解。闭包不会this搞不清事件冒泡没概念框架一报错你根本没有排查思路。我见过太多框架操作挺熟、三行原生代码写不出来的简历真项目一上强度就露馅。在框架上省下的学习时间迟早会在调试上加倍还回去。反过来原生基础打扎实了学任何框架都会快很多因为框架只是帮你封装了那些你本来就会的东西。7.2 用浏览器控制台做实验田浏览器自带的控制台是我认为最好的JavaScript练习场。F12打开控制台你就能随手输入表达式看结果、定义函数立即调用、修改页面元素试试DOM操作。不用装任何重型IDE打开浏览器就能开始学。甚至有些好玩的小操作也可以从这里开始尝试。比如在网页上看到一个横屏视频想临时旋转过来可以在控制台输入一行JS选中视频元素设置旋转属性页面立刻就会变化。这种即时反馈的乐趣是抽象地刷语法书完全给不了的。7.3 带着问题学而不是抄手册网上有很多JavaScript学习手册系列从简介、数据类型到函数、JSON、事件处理一章章写得很全适合当字典查询。但如果只是跟着抄一遍大概率记不住。我的经验是先拿一个真实小项目逼自己上阵比如做一个待办列表立刻就会用到变量、条件、循环、函数、事件、DOM操作遇到不会的再去查手册对应章节。带着问题翻手册和漫无目的地刷十遍教程记忆留存率完全不是一个量级。7.4 调试比写代码更需要练接口数据不对、事件没触发、写好的页面效果没出来……新手阶段一半的时间可能都花在调试上。这里分享几个固定套路先确认问题边界是数据问题还是渲染问题再在关键步骤打印日志逐步缩小范围还定位不了就用debugger在可疑位置停住单步执行看每一步的结果。记住一句话报错信息不是打击是提示。它已经帮你划定了问题的大致范围你要做的不是慌是顺着它指的方向一步步找到真正的原因。最后再分享一点个人体会吧。我带过不少人入门JavaScript发现真正拉开差距的从来不是天赋而是敢不敢把代码敲出来跑一遍。变量类型搞不懂敲一遍。函数this分不清敲一遍。事件冒泡想不明白写个小Demo点一下看看控制台里输出什么。JavaScript这门语言看起来遍地是坑但它最大的优点就是反馈快、试错成本极低。你给它一个console.log它还你一个真相这种学习对象只要你肯动手就一定能学会。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。