JavaScript基础语法详解:变量、函数、闭包与常见陷阱
发布时间:2026/9/24 21:56:37来源:尧图网络
JavaScript这门语言搁在十年前可能还只是网页上跑跑小特效的“脚本小子玩具”但放到今天它已经成了整个前端生态的地基。不管你是想学Vue、React还是想用Node.js做后端甚至是想玩点Three.js、WebGPU这种3D可视化JS的基础语法都是绕不过去的第一道门。我见过太多人一上来就冲框架结果被原型链、闭包、this指向来回折磨其实就是地基没打牢。这篇就把JavaScript的基础语法系统拆一遍从变量、数据类型到函数、对象、异步该讲的坑一个不落适合完全零基础的新手也适合学了一半感觉哪里不对、想回炉重铸的初学者。1. 入门之前的三个认知JavaScript到底在做什么1.1 浏览器里的“翻译官”你先记住一句话JavaScript是运行在浏览器里的脚本语言。它的核心职责就是让你打开的网页从“死”的变成“活”的。你点按钮弹窗、下拉刷新、轮播图自动播放、表单提交前校验背后全是JS在干活。区别于HTML和CSS一个管结构、一个管样式JavaScript管的是行为。HTML像是毛坯房的水泥墙和承重柱CSS是装修刷漆JS就是水电——管你按下开关灯亮不亮、拧开水龙头水出不出。没有JS的页面只能看有了JS的页面才能“用”。这里要额外说一句题外话常有人把JavaScript和Java搞混两者除了名字有点像关系基本等于“老婆饼和老婆”。JavaScript由网景公司1995年发明当时纯粹是为了在浏览器里做动态交互后来因为Java太火才故意起了个碰瓷的名字。你学JS压根不用先学Java两者语法思路也有很大差别。1.2 解释执行和编译执行的差别JavaScript是一门解释型语言写一行、执行一行不需要像C或Java那样先编译成机器码再运行。浏览器里内置了JS引擎最常见的是V8引擎它读一行源码、翻译一行、跑一行。这带来的直接好处是你新建一个HTML文件随手写几行script标签用浏览器打开就能立刻看到结果学习反馈极其快。但也因为这一点JS对代码写的顺序很敏感。你在一行里用了后面才声明的变量很可能直接报错挂掉不像编译语言那样有前置检查的过程。所以初学阶段一定养成一个习惯JS代码是从上往下、逐行解释执行的写完一行可以先想一下这一行会不会蹦出个错误。1.3 运行环境不只有浏览器记住“JavaScript只能在浏览器里跑”这句话放在今天已经不够准确了。2009年Node.js项目发布之后JS就能脱离浏览器直接在操作系统上运行做后端服务、爬虫、命令行工具都行。不过底层那套核心语法变量、函数、对象、循环完全一样变的只是周围的环境API。所以用一句话概括JavaScript的核心语法是一套独立于环境的“通用能力”——它教你怎么描述数据、计算流程、组织逻辑而浏览器提供了window、document这类对象Node.js提供了fs、http这类模块。这篇主要讲核心语法但我会在合适的地方提一下两者差异免得你跑到Node环境里用document好一通找不着北。2. 变量与数据类型先把手里的材料理清楚2.1 let、const、var到底怎么选第一次接触JS最先遇到的一定是声明变量。老教程会让你写var但现在的实际开发中var基本退居二线主流的推荐是默认用const明确知道这个值要重新赋值再换let。var能不用就不用。为什么var有两个坑一是没有块级作用域只有函数级作用域循环里一不小心就污染外部变量二是存在变量提升你可以先使用后声明结果拿到一个undefined排查起来特别恶心。// var 的典型坑循环里 i 到处乱跑 for (var i 0; i 3; i) { setTimeout(() console.log(i), 100); } // 输出3 3 3而不是 0 1 2而let和const是ES6以后引入的块级作用域变量代码写进哪个大括号就只在大括号内生效。const声明的变量不能被重新赋值但注意它只是锁定了“变量指向的那个地址”如果这个地址是对象或数组对象内部的属性、数组里的元素依然可以修改。新手特别容易在这踩坑以为const声明的数组就完全不能动了其实arr.push()照样能跑。2.2 七种数据类型快速过一遍JS有七种内置数据类型number数字、string字符串、boolean布尔、undefined未定义、null空值、symbol符号、bigint大整数。另外再算上引用类型object对象口袋里的材料就这些。我用一个简单示例让每种类型都现身let age 18; // number let name 张三; // string let isOk true; // boolean let nothing; // undefined声明了但没有赋值 let empty null; // null主动设置成空 let sym Symbol(id); // symbol一般用来做唯一标识 let bigNum 10n; // bigint在数字后面加n let person { name: 李四 }; // object这里面最容易混淆的是undefined和null。简单记undefined是JS自己给的表示“这个变量根本没有值”null是你主动塞进去的表示“这个位置我故意清空了”。想检测类型统一用typeof操作符但它有个著名的bugtypeof null返回的是object而不是null这是初版设计遗留问题官方没有修你只需要在面试题里见到它不慌就行。2.3 类型转换与隐式转换的坑JS是弱类型语言变量可以随时从字符串变成数字类型之间的转换很自由但自由意味着容易出一些“意想不到”的结果。最常见的是运算符的双重身份在数字之间是加法但一旦有一方是字符串就会变成拼接。console.log(3 4); // 34字符串拼接 console.log(3 - 1); // 2减号没有拼接语义所以转成数字计算 console.log(3 * 2); // 6全部转数字我当年第一次看到3 - 1输出2的时候整个人愣了一下。这里有个规律见到字符串就干拼接其他运算符减、乘、除、取余会尽量把字符串转成数字再计算。如果你要用表单输入的值做算术一定记得先转换Number(3)转成数字String(3)转成字符串或者用隐式技巧3也能变成数字3。3. 运算符与控制流让代码学会做判断3.1 运算符优先级别硬背用小括号JS运算符有几十个优先级从高到低排了一长串表。新手与其背那张表不如记一条土办法拿不准的地方一律加小括号。小括号在JS里优先级最高想先算什么就先算什么。举个例子let result 2 3 * 4; // 14乘法优先 let result2 (2 3) * 4; // 20小括号先算这句话听着像废话但实际写代码时非常管用。我见过线上事故就是因为某个运算符优先级记错导致位运算结果和预期差了十万八千里。另外有个特别常见的坑是赋值是宽松相等是严格相等。会先比较类型、再比较值类型不同直接false强烈推荐一律用。而会在比较前做一堆隐式转换比如0 false居然返回true这种诡异的行为只会给项目埋雷。控制台里经常有人执行javascript:void(o)来避免页面跳转其实就是利用了void运算符——它负责执行一段表达式并永远返回undefined浏览器默认不会对undefined做任何跳转处理这属于运算符冷知识的实战应用。3.2 条件语句if、switch、三元表达式条件判断是代码做决策的入口。最基础的是if...else if...else结构适合分支不太多的情况。当分支特别多比如要根据状态码返回不同文案用switch会更清晰它可以避免一长串else if恶心的嵌套。ES6之后还出现一种更简练的写法三元表达式条件 ? 值A : 值B适合“条件成立给A否则给B”这种单行逻辑。let score 85; // if 写法 if (score 90) { console.log(优秀); } else if (score 60) { console.log(及格); } else { console.log(不及格); } // switch 写法 let status 2; switch (status) { case 1: console.log(待审核); break; case 2: console.log(已通过); break; default: console.log(未知状态); } // 三元表达式写法 let tip score 60 ? 通过 : 未通过;初次接触三元表达式可能觉得它别扭用熟了之后你会发现它的语义非常紧凑尤其适合在函数返回值、给变量赋初值时使用。但嵌套三元表达式一定少写比如a ? b : c ? d : e这种性能没有区别纯粹是阅读体验灾难维护你代码的人会很想打你。3.3 循环语句for、while、for...of怎么选要重复执行某段逻辑就需要循环。JS里有多种循环单人推荐按需求选型。最通用的是for循环适合明确知道循环次数的情况比如遍历数组索引。如果不知道次数、只知道结束条件用while更顺手。比如让用户不停输入直到输入正确为止就很适合while。ES6以后遍历数组首选for...of它可以直接拿到数组里的元素不需要管索引代码简洁很多。顺便提一个几乎必考的面试题for...in和for...of的区别。简单记for...in遍历的是对象的键名或者数组的下标for...of遍历的是可迭代对象的值。你要是用for...in遍历数组拿到的都是字符串形式的索引“0”“1”“2”做加法运算时容易踩坑所以遍历数组老老实实用for...of。let arr [html, css, js]; // 老式 for 循环 for (let i 0; i arr.length; i) { console.log(arr[i]); } // for...of 更推荐 for (let item of arr) { console.log(item); }4. 函数把重复代码装进盒子4.1 三种函数写法搞清楚各自的血统函数是JS里最重要的组织代码的方式把一段可以重复用的逻辑封装起来起个名字需要时再调用。JS里有三种常见写法// 写法一函数声明 function add(a, b) { return a b; } // 写法二函数表达式 const add function (a, b) { return a b; }; // 写法三箭头函数ES6 const add (a, b) a b;初学者经常纠结选哪种。我的建议是箭头函数是现代开发的主流代码短且在this处理上更符合直觉先重点掌握它函数声明适合需要提升的场景因为函数声明会被整体提升到作用域顶部所以你在声明之前调用也不会报错函数表达式则介于两者之间但记住它不会提升必须先定义后调用。函数另一个核心概念是作用域。函数内部用let声明的变量外部访问不到而函数外部声明的全局变量函数内部可以读。这个规则像楼层房间楼上能看到楼下的东西楼下看不见楼上的。用好了作用域可以避免大量命名冲突。4.2 参数、剩余参数与默认值函数参数是调用函数时传进去的“原料”。JS在参数上有个比较自由的设计你声明了两个参数但只传一个另一个也不会报错自动变成undefined。这种自由适合做容错但也容易导致bug所以ES6引入了默认参数function greet(name 朋友) { console.log(你好${name}); } greet(); // 你好朋友 greet(张三); // 你好张三...三个点也就是剩余参数是另一个高频考点。它可以把多个参数“收集”成一个数组非常适合写参数个数不确定的函数。举个例子写一个求任意数字总和的函数function sum(...nums) { return nums.reduce((total, n) total n, 0); } sum(1, 2, 3, 4); // 10这种写法在处理动态数量数据时非常香而且...还能做数组展开在ES6里属于“万能三点符”后面讲数组时还会碰到。4.3 作用域与闭包的基础认知闭包是JS里让新手头疼指数最高的概念。说人话当一个函数返回了另一个函数并且内部函数引用了外部函数的变量时就产生了闭包——外部函数即便已经执行完毕那个被引用的变量也不会被垃圾回收而是被“封存”在内部函数的环境里。function createCounter() { let count 0; return function () { count; return count; }; } const counter createCounter(); console.log(counter()); // 1 console.log(counter()); // 2闭包的实际价值是让某些变量拥有“私有”属性外部不能随便改只能通过函数暴露出去的方法操作。初学阶段不用强求彻底理解闭包但要对它有个印象因为后面的防抖节流、柯里化、模块化全都会碰到它。等你在实际项目里踩过一次闭包变量被意外共享的坑你自然就懂它了。5. 数组与对象最常用的两个数据结构5.1 数组的增删改查与遍历数组是一串有序数据的集合。JS里创建一个数组很简单let arr [1, 2, 3]。往尾部追加元素用push删除尾部用pop删除头部用shift在头部插入用unshift。这四个方法都能改变原数组日常增删就靠它们。查找方面indexOf()可以返回元素位置找不到返回-1ES6的includes()返回布尔值更直观。而更现代的做法是find、filter这种“声明式”方法比如从一个数组里找出所有偶数let numbers [12, 5, 8, 130, 44]; let evens numbers.filter((n) n % 2 0); // [12, 8, 130, 44]几乎每本教程都会强调的map和reduce也属于必学方法map把一个数组映射成另一个数组长度不变适合做数据转换reduce把整个数组归并成一个值适合做累加、聚合。我在工作中碰到的大量数据处理逻辑到最后都能落在这几个方法上。遍历数组最推荐的还是for...of但遇到需要对每个元素做加工并生成新数组时map会比你手写循环优雅得多。5.2 对象的创建、读取与this指向对象在JS里就是一组“键值对”用大括号括起来。你可以把对象想象成一张档案表键是字段名值是具体内容。最基础的操作有三个创建、读值、改值。let person { name: 李四, age: 25, skills: [JS, Python], sayHi() { console.log(你好我是${this.name}); } }; // 读取 person.name; // 李四 person[age]; // 25也可以用方括号 // 修改 person.age 26; // 新增 person.city 上海;这里有个绕不过去的点this指向。以person.sayHi()这种方式调用对象方法时方法内部的this指向person对象本身可以拿到this.name但如果把这个方法单独取出来赋值给一个变量再调用this就会指向window或undefined运行时结果就完全变了。这也是高频面试里常考的对象方法this指向问题。解决思路是用箭头函数或者在调用时用bind绑定。5.3 JSON与对象的互相转换JSON是一种轻量级数据交换格式长得非常像JS对象但有一些区别它的键名必须加双引号字符串也必须用双引号不能写函数和undefined。前端从后端拿数据最常见的就是JSON字符串你用JSON.parse()把它转成JS对象用JSON.stringify()把JS对象转成JSON字符串传给后端这两个方法就是前后端数据通信的“翻译官”。let jsonStr {name:张三,age:20}; let obj JSON.parse(jsonStr); // 变成对象 console.log(obj.name); // 张三 let str JSON.stringify(obj); // 变回字符串 console.log(str); // {name:张三,age:20}初学阶段容易把对象和JSON混在一起记住一句口诀对象是内存里的“活数据”JSON是网络传输的“文本数据”。转换过程中如果遇到格式不正确JSON.parse会直接抛出异常所以建议在解析时用try...catch包一下。6. 字符串、正则与日期处理日常数据的工具箱6.1 字符串的常用方法与模板字符串字符串是前端打交道最多的数据类型从接口返回的文案到用户输入的内容全是字符串。ES6以前拼接字符串靠又繁琐又容易漏ES6推出了模板字符串用反引号包住内容${}直接嵌入变量let name 张三; let score 90; console.log(姓名${name}成绩${score}); // 姓名张三成绩90字符串本身自带大量方法优先记住这几个string.length拿到长度string.toUpperCase()/toLowerCase()转大小写string.trim()去掉首尾空格表单校验必备string.includes(关键词)判断是否包含某个子串string.split(分隔符)把字符串拆成数组string.replace(旧, 新)替换第一次匹配到的内容顺带一提热搜里有人问“javascript 合并字符串”其实就是把两个字符串拼接成一个。最简单的方式是或模板字符串但如果字符串很多用数组join更高效[a, b, c].join(-)得到a-b-c这个技巧在处理路径、拼接URL参数时非常实用。6.2 正则表达式一个字符匹配家族正则表达式是处理字符串的“高级模式匹配工具”。很多人一看到那堆/^...$/就头皮发麻但它其实不难。它的作用就两件事一是判断某个字符串符不符合规则二是把匹配到的内容提取或替换掉。JS里创建一个正则最简单是直接字面量写法let reg /^1[3-9]\d{9}$/。这段匹配的就是国内手机号以1开头第二位是3到9后面9位任意数字总共11位。用reg.test(手机号)返回true或false就能立即做表单校验。常用元字符可以整理成一张速查表元字符含义示例\d数字字符等价于[0-9]/\d/能匹配 1\w单词字符包括数字字母下划线/\w/能匹配 a\s空白字符空格、换行、tab/\s/能匹配 .除换行外的任意字符/a.c/匹配 abc*前一个字符出现0次或多次/ab*c/匹配 ac、abc前一个字符出现1次或多次/abc/匹配 abc?前一个字符出现0次或1次/ab?c/匹配 ac、abc^匹配开头/^a/匹配 abc里的a$匹配结尾/c$/匹配 abc里的c刚开始不用记全用的时候查表就行。重点是理解思路正则就是把你要匹配的“形状”用符号描述出来。常见场景比如手机号、邮箱、URL、身份证号校验网上都能搜到模板拿过来稍微改改就能用。反过来如果正则写得太复杂还会拖慢性能实在复杂的场景可以用拆分成多个正则逐一判断代码反而更清晰。6.3 Math、日期与异常处理JS里处理数值计算和日期主要靠内置的Math对象和Date对象。Math没法用new创建它就是一个全局工具类。常用的有Math.round(4.6)四舍五入到整数得到5Math.floor(4.9)向下取整得到4Math.ceil(4.1)向上取整得到5Math.random()生成0到1之间的随机数左闭右开Math.max(1, 9, 3)取最大值生成随机整数有一个通用公式Math.floor(Math.random() * (max - min 1)) min左闭右闭区间。做抽奖、随机点名都靠它。日期方面let now new Date()就能拿到当前时间。要注意月份从0开始所以new Date(2024, 0, 1)是2024年1月1日。格式化成“YYYY-MM-DD”要自己拼let now new Date(); let year now.getFullYear(); let month String(now.getMonth() 1).padStart(2, 0); let day String(now.getDate()).padStart(2, 0); console.log(${year}-${month}-${day});异常处理用try...catch...finally。代码在try里出错了catch能接住错误不打断整个程序运行finally里面的代码无论是否出错都会执行。这个机制在解析JSON、调用接口、加载外部资源这种外部因素较多的地方特别重要因为你没法保证外部数据永远符合预期。try { let data JSON.parse({name:张三); } catch (err) { console.log(解析失败兜底处理, err.message); }7. 运行时错误与调试把报错当成提示7.1 常见报错类型与含义新手最容易在“报错”面前手足无措其实报错是JS给你的提示而不是惩罚。看懂报错能力就涨一半。最常见的几类报错SyntaxError语法错误。少写了括号、引号没闭合代码根本没法解析。这种错最直接打开控制台看哪一行标红了。ReferenceError引用错误。用了某个不存在的变量常见于拼写错误或者变量还没声明就使用。TypeError类型错误。最常见的是把一个基本类型当函数调用或者读取undefined的属性。比如let a; a.b 1就会报这个。前端开发中报Cannot read properties of undefined基本就是这货排查思路是看看对象有没有真正拿到数据。RangeError范围错误。比如递归调用太深超出最大调用栈大小。控制台的报错信息里红色的错误类型可以帮你快速定位方向后面的message是具体描述再点右边的文件名和行号能直接跳转到源码位置。这套“读报错”的流程建议从一开始就养成。7.2 控制台调试技巧比console.log更高级的方式日常调试大家最顺手的是console.log()到处打点但打点只能看不能改遇到复杂逻辑效率很低。这里分享几个实用技巧第一个是用console.table()打印数组或对象它在控制台以表格形式展示一眼就能看到结构比log一长串清晰很多。第二个是用console.time()和console.timeEnd()统计代码运行时间用来排查性能问题秒级搞定。第三个是浏览器开发者工具的断点调试在源代码面板点击行号加断点刷新页面后代码会停在断点处鼠标悬停在变量上能看到当前值配合单步执行Step Over、Step Into能精确追踪每一行。这个方法在处理复杂循环、递归时效率远超打点。还有一个比较“野”的技巧直接在浏览器控制台写代码操作页面。比如有些人会用一行document.querySelector(video)找到视频元素再给它绑定事件或修改样式来实现一些页面上的自定义效果。但注意这只是本地调试行为不应该写到正式代码里否则会带来安全和维护问题。7.3 常见问题速查表现象可能原因排查方向页面提示please enable javascript to continue.浏览器禁用了JavaScript不是代码问题检查浏览器设置把JS开关打开控制台报xxx is not defined变量未声明或拼写错误搜一下变量名看是不是少了个字母用变量方法时报undefined is not a function变量不是函数或者函数属性拼错了打印变量类型确认它到底是个啥数组遍历里用了for...in输出的是索引不是值遍历方式选错改用for...of或forEach后端返回的数据解析失败JSON字符串格式不对或网络返回的是HTML先把接口返回内容打印出来看看this的值不是预期的对象调用方式导致this指向变化箭头函数、bind或手动改成普通函数调用页面白屏没有任何报错可能是JS加载失败或某段代码抛异常被吞在Network面板看脚本是否加载成功全局搜try、catch这张表是从大量实际报错里浓缩出来的新学JS的读者可以先存着遇到问题对着查。最后分享一点实战体会我学JavaScript的过程走了不少弯路。最早照着书敲代码敲完也不知道每一行到底是为了解决什么问题后来开始直接做小项目遇到报错就查查完写笔记进步速度反而比死磕教程快得多。所以我不太建议你把基础语法从头到尾背完再动手更实用的路径是先把这篇文章里涉及的语法概念过一遍然后立刻找一个小目标——比如做一个待办清单、做一个随机抽奖页面——边写边补知识。语法这个东西用三个月自然会内化光看是看不会的。顺带再提醒一点很多人在入门阶段容易把Python的语法习惯带到JS里比如缩进、变量命名风格。语言之间互相借鉴没问题但写JS时最好照着JS社区的约定来变量和函数用驼峰命名常量用全大写每个语句结尾加不加分号看团队规范但别一会儿加一会儿不加。代码是写给人看的规范统一比“能跑”更重要。希望这篇基础语法全解析能帮你把根扎稳后面学DOM、异步、框架的时候你会感谢现在的自己。
网站建设高端定制企业官网