想象你家的厨房还是三十年前的老样子:所有调料敞口放在灶台上,用得久了互相串味;写菜谱要剪纸条、抹胶水,想改个菜名得撕掉重写;蒸馒头每次用手工捏形状,熟手和生手做出来的大小不一。这就是用旧版 JavaScript 写代码的日常——变量到处都是还容易互相覆盖,拼接字符串要拿加号把一段段文字粘起来,写个简单函数得敲一长串 function 和 return。
ES6+ 正是给这间“老厨房”做的一次现代化改造:密封调料罐(let/const)让每种调料各归各位;磁吸标签菜谱(模板字符串)随手一贴就能改;智能厨具(箭头函数)一键完成操作;冰箱分类抽屉(解构赋值)按标签取料;整袋食材直接下锅(扩展运算符);标准模具做点心(class 语法)。最后还有一台“老灶台翻译器”(Babel),把新菜谱翻译成老灶台听得懂的语言,让所有浏览器都能做出新菜。
这篇文章带你参观这间改造后的“新厨房”,每个核心语法都讲清楚用法、对比旧写法、说明适用场景,读完你就能在实际项目中直接上手。
无论你是前端入门、想从旧写法升级,还是面试需要扎实的 ES6 基础,掌握这些现代语法都能让你写出更简洁、更可靠的代码。在 AI 时代的前端工程、可视化大屏和 Node.js 服务开发中,这套基本功更是绕不开的基石。
目录
可视化演示
动态展示选择排序每轮查找最小值并交换到前方的过程,直观呈现分治策略与原地排序的特性。
本节配有交互动效(进入视口后自动加载,约 3KB)。
选择排序 - 第1轮 扫描未排序部分查找最小值 将最小值交换到已排序末尾 min=a[k] 每轮选最小元素
1. let 与 const——密封调料罐,各归各位
老厨房里,所有调料都敞口放在灶台上,辣椒粉的气味能飘到白糖罐里,盐罐和糖罐经常傻傻分不清楚。这就是 var 的处境——它只有函数作用域和全局作用域,没有“块”的概念。在 if 或 for 的代码块里用 var 声明的变量,代码块结束后依然在外面“飘着”,就像辣椒粉的味道传遍整间厨房,你根本管不住它。
ES6 给出的解决方案是 let 和 const 两个“密封调料罐”。它们把变量的可见范围锁定在所在的花括号 {} 内部,这个范围在 JavaScript 里叫“块级作用域”。一旦代码块结束,变量就像密封罐的盖子合上,味道一丝都不会漏出去。let 是“可换罐”——还能倒掉重装新的调料;const 是“终身锁罐”——一旦装好调料,再也不能灌入其他东西,但罐子里的食材本身(比如对象里某个属性)还是可以取用修改的。
块级作用域可以用集合的语言来理解:一个块内声明的变量属于这个块的集合,外部集合不包含它。公式如下。
把 var 换成 let 或 const,就像给厨房所有调料配上带盖密封罐。代码块之间互不串味,程序的每个部分都可预测、不互相污染。老厨房里最让人头疼的“飘味问题”,从此被一劳永逸地解决。
- 块内集合花括号代码块里声明的所有变量,构成一个“块内集合”,这就是密封罐的罐体空间。
- 外部排除块内变量不属于外部集合,块外代码访问它会报错或得到
undefined,就像调料味被封在罐子里。 - 暂时性死区
let声明之前的区域是“禁区”,提前访问会直接抛出ReferenceError——罐子还没放到架子上,你伸手去够,自然会扑空。
2. 箭头函数——一键智能厨具
老式灶台做饭要好几步:先开煤气阀门,再拧打火旋钮,然后手动调火候,最后还得自己盯着看菜熟没熟。旧版 JavaScript 写函数也一样繁琐——要写 function 关键字,要有 return 语句,一个简单的“输入乘二”要占好几行。ES6 的箭头函数则像一台智能厨具:按下按键(=>),食材进去,成品自动出来,中间过程全部省略。
箭头函数的核心是“映射”思想:给它一个输入,它直接返回一个输出。(x) => x * 2 就是把 x 映射到 x 的两倍,读作“x 映射到 2 倍 x”。当只有一个参数时,连括号都可以省略;当函数体只有一个表达式时,连 return 都可以省略——这个特性叫“隐式返回”,厨具默认就把唯一做好的菜端出来,不用你额外说“端菜”。
箭头函数还有三个重要的“脾气”需要记住。第一,它的 this 是“词法绑定”,在写代码的那一刻就固定继承外层作用域的 this,不会像普通函数那样由调用者决定,相当于智能厨具按出厂设置工作,不受操作者干扰。第二,它不能当作构造函数使用,没有自己的 prototype,就像一键式厨具没有手动模式的零件。第三,它没有自己的 arguments 对象,需要用 ...args 这样的 rest 参数来收集所有传入实参。
从数学角度看,箭头函数的表达更接近函数定义本身,公式如下。
在项目中善用箭头函数,你会发现回调函数、数组遍历、事件处理的代码量肉眼可见地缩水。就像一键智能厨具一样,把做饭流程压缩成一个按键的动作,省时又省心。
=> 和数学上的映射箭头 ↦ 气质相同:左边放输入,右边给输出。这种直白的映射表达让代码读起来像数学公式一样清晰。- 映射符号
x ↦ 2x表示输入 x 输出 2x,这就是箭头函数最本质的行为——处理一个输入,给出一个规律性的输出。 - 等价定义它和普通函数
function f(x) { return x * 2; }完全等价,只是写得更短、视觉负担更小。 - this 的定格箭头函数的
this在定义时就被“拍照定格”,等于外层作用域的this,不再随调用者动态变化。
3. 解构赋值——冰箱抽屉按标签取料
老冰箱的食材全部堆在一个大格子里,想找根葱得翻半天。每次从对象里取值,都要写 obj.name、obj.age,一行一行地取,就像在杂物堆里反复扒拉。ES6 的“解构赋值”则像一台带分类抽屉的新冰箱:每个抽屉贴着标签,你要什么,直接按标签拉开,一次就能把想要的食材全部拿齐。
解构赋值分为两种常见形态。数组解构按“位置”取值:const [a, b] = [1, 2] 会让 a 得到 1、b 得到 2,就像在抽屉里按顺序拿第一件、第二件食材。对象解构按“名字”取值:const {name, age} = obj 会把 obj.name 和 obj.age 自动填进同名变量,就像对着标签 "name"、"age" 直接把对应的抽屉抽出来,不需要关心抽屉在第几层。
解构赋值还支持两个贴心功能:默认值和嵌套。取不到值的时候可以用等号设置兜底,比如 const {age = 18} = obj,如果 obj 里没有 age,就用 18 顶上——相当于抽屉空着时自动放一份备用食材。嵌套解构则像拉开冰箱门后再拉里面的小抽屉:const {info: {city}} = obj 可以一步拿到嵌套深处的数据,省去逐层访问的冗长代码。
解构最有名的一个妙用是交换两个变量的值,看这个优雅的公式。
解构赋值最能体现“按需取料”的优雅。它让代码少了好几行冗余赋值,让数据从“哪来的、怎么取的”变得一看就懂,就像打开冰箱扫一眼标签就能拿到全部食材。
- 左侧模式等号左边的
[a, b]是“模式”,它声明两个变量,并按位置顺序对应右侧元素,相当于告诉冰箱“我要一格和二格的食材”。 - 右侧来源等号右边的
[b, a]作为数据源,把它的第 0 位元素给 a、第 1 位元素给 b,实现一次赋值。 - 交换结果经过赋值,a 的新值是旧的 b,b 的新值是旧的 a。整个过程不需要声明任何临时变量。
4. 模板字符串——磁吸菜谱随手贴
老式厨房写菜谱,得剪一张张小纸条,用胶水贴在菜谱本上。想改一道菜名,得把旧纸条撕下来、重新剪、重新贴,麻烦不说,还容易把本子弄得黏糊糊。旧版 JavaScript 拼字符串正是这个体验:'你好,' + name + '!',加号就像胶水,片段一多,顺序一乱,整行代码就变成一锅粥。
模板字符串把菜谱本换成了磁性小黑板,用一对反引号 ` ` 围起来,中间想放变量或表达式的地方,用 ${}23你好,${name}! 读起来就是一个完整的句子,name 的位置一目了然。磁贴不仅能贴变量,还能贴任意表达式,比如 ${price * 0.8}` 可以直接在字符串里算打折后的价格。
模板字符串的三大优势让写字符串从“手工劳动”升级为“所见即所得”。第一,多行字符串原生支持——反引号内可以随意换行,不用再写 \n 和加号去拼好几行文字,就像在小黑板上长句、短句分行写都顺其自然。第二,支持嵌套模板——${} 里面还可以再写模板字符串,灵活组合出复杂结构,相当于磁贴上可以再叠磁贴。第三,标签模板——在反引号前加一个函数名,就能对模板内容做自定义加工,这是高级玩法,类似在小黑板外用一层滤镜控制最终呈现效果。
模板字符串和旧式拼接在结果上完全等价,但体验天差地别,用公式表达如下。
从此以后,生成 HTML 片段、拼接日志信息、组合接口提示语,都像在小黑板上工整地写字。胶水和纸条的时代落幕了。
\text{'你好,'} + name + \text{'!'} \ \Longleftrightarrow \ \text{\`你好,}\${name}\text{!\`}- 旧式拼接用加号把字符串片段和变量一块块粘起来,片段一多就分不清顺序,就像用胶水粘纸条容易粘错位置。
- 插值磁贴
${}是磁吸位置,括号里的表达式会被求值后换成字符串嵌入,变量名直接出现在句子中。 - 等价关系左式与右式输出完全一致,但右式更直观、容错率更高,尤其适合多段落、多变量的动态文本生成。
5. 扩展运算符——整袋食材倒进锅
做汤的时候,传统做法是一颗颗把土豆、胡萝卜、洋葱夹进锅里,费时又容易漏掉。旧代码里要把数组的每个元素传给函数,得用 apply 或者写循环,笨重得很。ES6 的扩展运算符 ... 就像是把整袋食材直接倒进锅里——干干净净、一次到位、一个不漏。
扩展运算符做的事情是“展开”:把数组或可迭代对象拆成一个个独立的元素。最经典的场景有三个。数组合并:const newArr = [...arr1, ...arr2] 把两个数组的元素全部倒进一个新数组,比 concat 更直观。函数传参:Math.max(...nums) 把数组 nums 的元素铺平成一个个独立参数传入,不需要再绕道 apply。对象合并:const merged = {...obj1, ...obj2} 把两个对象的属性“倒进”一个新对象,后者同名属性会覆盖前者,非常适合做配置合并。
扩展运算符还有一个“反向操作”——rest 参数。当 ... 用在函数参数位置时,它做的事情不是展开而是收拢:把多个参数收集成一个数组。function sum(...nums) { ... } 可以接收任意数量的参数。想象你有一个“收纳袋”,把散落在桌上的食材全部收进袋子里,倒出来是散的(展开),装进去是一袋的(收拢)。
从数学角度看,展开就是把“集合的集合”摊平成直接的元素列表,公式模拟如下。
处理接口返回的数组、合并用户配置、封装不定参数的函数,扩展运算符都是那把“万能倒料勺”——倒下去干净利落,收起来整整齐齐。
... 解开了外壳,元素一个个排列出来,效果等同于直接写出所有参数。这就是“整袋倒进锅”的数学面貌。- 展开动作
...[3, 7, 5]把数组的方括号“脱掉”,只留下里面的元素序列 3、7、5,就像倒出整袋食材。 - 等价调用展开后传入
Math.max与直接写三个参数完全等价,函数收到的是三个独立参数而非一个数组。 - rest 收拢在参数位置,
...args把调用者传入的多个参数收进一个数组,公式上等价于构建一个列表。
6. class 类语法——标准模具做点心
在没有模具的时代,做点心全靠手工捏:先揉面,再塑形,每个厨师捏出来的形状都不一样,还得记住“这个点心要继承那个点心的大小和花纹”这类复杂配方。旧版 JavaScript 用构造函数加原型链模拟面向对象,写法绕来绕去,小白容易看晕。ES6 的 class 语法像引入了一套标准模具——形状、花纹、尺寸一次定制,之后每个点心都按模具批量生产,整齐统一。
class 本质上是一层“语法糖”,底层依然是构造函数加原型链,但表达方式直白了很多。用 class 定义一个类,里面有 constructor 构造函数,在 new 的时候自动执行,负责初始化实例的属性。方法直接写在类体里,自动挂到原型上,所有实例共享,不会每个实例都复制一份。
类之间还能用 extends 建立继承关系,比如 class Dog extends Animal,Dog 自动拥有 Animal 的所有方法。子类的构造函数中必须先调用 super() 才能使用 this,这是为了保证父类的初始化先完成——就像用模具做点心时,必须先放好底模,才能压出上层花纹,顺序错了整个形状就乱了。super 关键字还可以用来调用父类的方法,比如在子类里 super.makeSound() 先让父类发声再做自己的事。
class 继承在底层等价于一次原型链的连接,用公式表示如下。
用 class 组织代码,就像厨房引进了一套标准模具:动物类、机器人类、菜单类……每个类都规规矩矩,继承关系一目了然,团队协作时默契度直线上升。
extends 关键字,底层做的是把子类的原型对象“挂在”父类原型之上,形成一条原型链,就像模具嵌套模具,子模具自动带出父模具的纹路。- 继承声明
class B extends A声明 B 继承 A,B 的实例可以直接使用 A 中定义的方法,相当于模具 B 自动复制了模具 A 的所有花纹。 - 原型连接底层实现是把 B 的 prototype 指向一个“以 A 的 prototype 为原型”的新对象,形成原型链——沿着链条向上找,总能找到父类的方法。
- super 先行子类构造函数中先调用
super()再使用this,等价于先完成父类模具的压模,再做子类的个性化加工。
7. 兼容与转译——老灶台翻译器
厨房改造完毕后,菜谱都换成新式写法,可家里的老灶台听不懂新名词,怎么办?老一辈的办法是另外架一台“翻译器”:把新菜谱逐句翻译成老灶台能执行的旧指令。前端世界里,这台翻译器叫 Babel。它把 ES6+ 的语法翻译成 ES5——箭头函数变成普通 function,let 变成 var 并模拟块级作用域,class 变成构造函数加原型链。老浏览器读完翻译后的代码,照样能跑。
不过语法能翻译,能力没法凭空翻译。新语法还配套了一批“新厨具”——Promise、Map、Array.from 这些 API。翻译器可以把“做菜步骤”翻译成旧语言,但老环境里根本没有这些厨具本身。这时就需要另一个角色出场:polyfill(补丁),它像在旧灶台上加装一个模拟配件,让老环境也能拥有新 API 的行为,比如给不支持 Promise 的浏览器注入一份简易实现。
实际工程中,你不需要手动折腾这些。Webpack、Vite 等构建工具在生产环境打包时,会自动调用 Babel 并引入需要的 polyfill,你只管写新语法,工具链负责“翻译”。浏览器兼容矩阵也由配置统一管理——就像厨房改造后请了专业的监理团队,新菜谱写好,交给他们去适配所有老灶台。
从源码到可运行代码,整个链路可以抽象成一次“翻译加打补丁”的变换,公式如下。
有了翻译器和补丁,你完全不用为了兼容而退回旧写法。新语法带来的开发效率与代码质量,老浏览器照样能“尝到”——这就是现代化厨房惠及每一代灶台的意义。
- 源码输入ES6 源码是新菜谱,语法简洁、结构清晰,但老浏览器不识别。
- 转译动作Babel 将新语法翻译成 ES5 等价写法,箭头函数、模板字符串、class 等都被逐句改写,相当于翻译器逐行转换。
- 补丁注入语法之外的新 API 由 polyfill 注入运行时,老环境得到模拟能力,最终产出可在旧浏览器执行的完整代码。
AI 应用场景
场景一:AI 可视化大屏的前端重构
阶段:前端开发 / 数据可视化工程
解决的问题:AI 模型的预测结果需要在前端动态展示,传统 ES5 代码变量混乱、回调嵌套深、手动拼接 HTML 极易出错。使用 ES6+ 重构后,用解构赋值读取模型返回的数据字段,用箭头函数简化渲染回调,用模板字符串生成图例与弹窗,代码量减少约四成,逻辑清晰可维护。
具体案例:某团队用 ECharts 展示推荐算法实时曲线,原代码有 300 多行面条式拼接。重构后用 const { labels, values } = apiResult 一步取数,渲染函数写成箭头函数,图例用模板字符串动态生成,整段逻辑从 300 行精简到 180 行,Bug 率明显下降。
场景二:Node.js 中搭建 AI 模型服务的 API 层
阶段:后端服务开发
解决的问题:模型推理服务要处理大量请求参数,默认参数与用户参数合并、不定长输入收集是高频需求。传统写法用循环遍历和临时变量,繁琐还容易漏参数。用扩展运算符合并参数对象,用 rest 参数收集不定长的请求列表,用 class 封装模型加载与推理流程,接口层既简洁又健壮。
具体案例:一个推荐模型服务用 class ModelLoader 封装权重加载、预处理、推理三个环节;请求处理函数用 const params = { ...defaults, ...req.query } 快速合并参数;批量预测接口用 async predict(...ids) 接收任意数量的 ID,一行代码完成参数收拢。
场景三:AI 标注工具的前端交互优化
阶段:数据标注工具开发
解决的问题:标注面板需要频繁操作 DOM 状态,大量状态变量互相污染导致标注框错位。改用 let/const 严格约束变量作用域后,状态冲突大幅减少;用 class 封装不同标注类型的面板组件,用解构赋值读取标注坐标数据,交互逻辑清晰可复用。
具体案例:某数据公司的图片标注工具,用 const 定义所有配置常量,标注坐标用 const { x, y, w, h } = currentBox 一步取出,矩形框、多边形、关键点三种标注模式各封装为一个 class,切换模式只需 new RectPanel() / new KeypointPanel(),项目可维护性显著提升。
Python 演示
# -*- coding: utf-8 -*-# ES6 Features Detector - 检测JS代码中的ES6语法使用情况# 直接运行即可看到内置示例的检测报告, 也可把真实JS代码传给 detect_es6() import re # 特征名称与正则匹配规则FEATURES = { "let": r"\blet\s+[A-Za-z_$]", "const": r"\bconst\s+[A-Za-z_$]", "arrow": r"=>", "template": r"`", "destructure": r"(?:\{\s*[A-Za-z_,\s]+\}\s*=|\[\s*[A-Za-z_,\s]*\]\s*=)", "spread": r"\.\.\.", "class": r"\bclass\s+[A-Za-z_$]", "import": r"\bimport\s+[A-Za-z_{}\$]",} def detect_es6(code): """统计各类ES6特性出现的次数, 返回字典""" return { name: len(re.findall(pattern, code)) for name, pattern in FEATURES.items() } def format_report(result): """把统计结果格式化成可读报告""" lines = ["ES6 Feature Report", "=" * 24] total = 0 for name in sorted(result, key=lambda k: -result[k]): count = result[name] if count: lines.append(f"- {name:10s}: {count}") total += count lines.append("=" * 24) lines.append(f"Total ES6 usage: {total}") return "\n".join(lines) if __name__ == "__main__": demo_js = ''' const api = "/ai/predict"; let score = 0; const getInfo = (name) => `hello ${name}`; const [a, b] = [1, 2]; const nums = [1, 2, 3]; const max = Math.max(...nums); class Model { constructor() { this.ready = true; } } import fs from "fs"; ''' print(format_report(detect_es6(demo_js)))
let、const、箭头函数、模板字符串、解构赋值、扩展运算符、class、import 等 ES6 语法的出现次数,帮你快速了解一个项目或一段代码的现代化程度。FEATURES = {...}定义每个 ES6 特性的“传感器”(正则表达式),每种语法对应一种匹配规则。re.findall(pattern, code)在代码文本中扫描并统计该语法出现了多少次,相当于一个一个数出新厨具的数量。detect_es6(code)主检测函数,输入 JS 代码字符串,输出一个包含各类特性次数的字典。format_report(result)把统计结果整理成对齐的美观报告,按数量降序排列,最下面给出总量。demo_js内置一段含多种 ES6 特性的示例代码,你可以换成任意真实代码文件的内容直接使用。
常见误区
误区我之前一直以为用 const 声明变量后,这个变量就彻底锁死了,任何修改都会报错,所以连 const 声明的数组我都不敢 push。
正解const 锁的是“绑定”而不是“值”。它保证变量名不能再指向别的值(不能重新赋值),但变量指向的对象或数组内部是可以变化的。就像密封罐的盖子锁死了,你不能再往罐子里换装另一种调料,但罐子里的食材本身可以夹出来吃。const arr = [1] 后 arr.push(2) 完全合法,而 arr = [2] 才会报错。
为何常见:初学者看到“const 常量”的字面意思,加上很多教程强调“不可变”,就自然理解为整个值都不可变,忽略了 JavaScript 中“绑定”与“值”这两个层面的区分。
误区我一直以为箭头函数就是省了几个字符的 function,写法短一点,其他都一样,所以项目里顺手全换成箭头函数了。
正解箭头函数有三个关键区别:第一,this 在定义时词法绑定,不会随调用者改变;第二,它是匿名函数且没有 prototype,不能作为构造函数使用 new;第三,它没有自己的 arguments 对象。这些差异会导致行为完全不同。就像智能厨具和传统灶台虽然都能做菜,但工作逻辑完全不同——前者按出厂设置运行,后者取决于你用它的方式。
为何常见:从表面看箭头函数确实只是“瘦身版” function,不深入理解 this 绑定机制和原型链,很难察觉到行为层面的本质差异,直到在事件回调或对象方法中踩坑。
误区网上说 let 和 const 没有变量提升,我一直以为它们在声明之前使用会像访问不存在的变量一样返回 undefined。
正解事实上 let/const 同样存在提升,但被“暂时性死区”(TDZ)保护——声明之前的区域不可访问,一旦访问立即抛出 ReferenceError 而不是返回 undefined。这像罐子的标签已经挂上去了,但罐子还没放到架上,你去拿就扑个空且会报警。因此“提升”的不是“变量可用”,而是“变量已被登记但不可触碰”。
为何常见:许多教程为了简化和 var 对比,用“没有变量提升”来概括,导致很多人误以为 TDZ 区域访问会得到 undefined,实际报错与不报错是面试和调试中非常关键的差异。
误区我以为解构赋值就是 `const [a, b] = arr` 和 `const {x} = obj` 两种,其他地方都用不上。
正解解构赋值的用途远不止这两处:它可以用在函数参数上(直接解构传入对象);可以用在函数返回值上(一行接住多个返回值);可以用来交换变量;还可以用在 for 循环里(for (const {id} of list))。凡是“等号左边写模式、右边给数据源”的场景都适用。就像分类抽屉的取料逻辑,只要一侧提供食材、一侧声明标签,就能完成按需取用。
为何常见:教材通常先讲数组和对象两个基础形态,学习者容易形成“解构 = 那两种写法”的惯性思维,忽略了它是一种通用的赋值模式。
误区我总是以为只要把代码交给 Babel 转译成 ES5,就万事大吉了,什么新特性在老浏览器都能跑。
正解Babel 只能转译语法层面的内容(箭头函数、模板字符串、class 等),对于 API 层面的能力(Promise、Map、Array.from 这些新内置对象和方法),语法转译无法凭空创造,必须额外引入 polyfill 来补足。就像翻译器能把“开抽屉”翻译成旧语言,但旧灶台本身没有抽屉这个零件,你得额外装一个。正确做法是 Babel 负责语法转译,再按需引入 @babel/polyfill 或 core-js 补 API。
为何常见:人们容易把“转译”理解为“完全兼容”,忽略了语法与运行时 API 是两个不同层面,直到线上在 IE 中白屏才发现区分。
面试题
Q1. 以下哪种变量声明方式存在“暂时性死区”(TDZ)?
- A.
var - B.
let← 正确答案 - C.全局变量
x = 1 - D.
function声明
答案:B。let 和 const 存在暂时性死区——声明之前的区域访问变量会抛出 ReferenceError。相当于密封罐的标签先挂上但罐子还没放上架,此时伸手去拿会扑空并报警。var 虽然提升但没有 TDZ,访问只会得到 undefined。
Q2. 箭头函数与普通 function 最大的区别是什么?
- A.箭头函数写法更短
- B.箭头函数不能有返回值
- C.箭头函数的 this 继承外层作用域且不能当构造函数← 正确答案
- D.箭头函数不能传参
答案:C。箭头函数的核心差异有两点:this 是词法绑定(定义时就确定,不随调用者改变),且没有 prototype 不能 new。写法短只是表面现象。就像智能厨具按出厂设置运行,不受操作者影响。
Q3. 以下哪些写法是合法的解构赋值?
- A.
const {a, b} = obj;← 正确答案 - B.
const [x, y] = arr;← 正确答案 - C.
const [a, b] = {a: 1, b: 2}; - D.
const {x = 10} = obj;← 正确答案
答案:ABD。A 是对象解构按名取值,B 是数组解构按位取值,D 是带默认值的对象解构,三者都合法。C 不合法——左侧是数组模式,右侧必须提供可迭代对象,普通对象没有迭代器,不能用于数组解构。就像你不能用“按顺序取”的标签去取一个没有顺序的冰箱抽屉。
Q4. 执行以下代码,最终控制台输出什么?
- A.3 3 3
- B.0 1 2← 正确答案
- C.1 2 3
- D.抛出 ReferenceError
答案:B。let 为每次循环创建独立的块级作用域,每个 setTimeout 回调捕获的都是当次迭代的 i,所以输出 0、1、2。如果用 var 声明,所有回调共享同一个变量,最终输出 3 3 3。这正体现了密封罐“各归各位”——每次循环都是独立的小罐子。
Q5. const 声明的数组,以下哪个操作是允许的?
- A.
const arr = [1]; arr = [2]; - B.
const arr = [1]; arr.push(2);← 正确答案 - C.
const arr; arr = [1]; - D.
const arr = null; arr = [1];
答案:B。const 锁的是变量名与值的绑定关系,不允许重新赋值,所以 A 和 D 都会报错;C 中 const 声明时必须初始化,不能先声明后赋值。B 的 push 是修改数组内部,属于允许的操作——就像密封罐盖子锁死了,但罐子里的食材仍然可以动。
Q6. 关于模板字符串,以下说法正确的是?
- A.可以使用
${}嵌入变量或表达式← 正确答案 - B.支持直接书写多行字符串← 正确答案
- C.
${}内部可以嵌套使用模板字符串← 正确答案 - D.模板字符串会自动执行内部包含的 HTML 脚本
答案:ABC。模板字符串支持插值、多行、嵌套模板,A/B/C 都正确。D 错误——模板字符串只是普通字符串,不会自动执行 HTML 或脚本,它是“磁吸小黑板”,只负责内容展示,不具备执行能力。
Q7. 扩展运算符 ... 的正确作用是什么?
- A.删除数组中的元素
- B.将数组或可迭代对象展开为多个独立元素← 正确答案
- C.将两个字符串连接成一个
- D.创建一个新的作用域
答案:B。扩展运算符的核心动作是“展开”——把数组、对象等可迭代结构拆成独立元素,可以用在数组合并、对象拷贝、函数传参等场景。它不负责删除、连接字符串或创建作用域。就像把整袋食材倒进锅里,而不是做其他事。
Q8. 关于 class 语法,以下说法正确的是?
- A.class 是 ES6 提供的语法糖,底层仍是构造函数和原型链← 正确答案
- B.class 声明会提升并自动初始化
- C.子类构造函数中必须先调用 super() 才能使用 this← 正确答案
- D.class 内部定义的方法默认挂载在原型对象上← 正确答案
答案:ACD。A 正确,class 的底层机制还是构造函数加原型链;C 正确,super() 先保证父类初始化;D 正确,类体中的方法自动放在 prototype 上供实例共享。B 错误——class 和 let/const 一样存在暂时性死区,声明前使用会报错,不会初始化。
Q9. 非严格模式下,以下代码中箭头函数内的 this 指向什么?
- A.obj 对象
- B.undefined
- C.window← 正确答案
- D.抛出 TypeError
答案:C。箭头函数的 this 在定义时就由外层作用域决定,这里定义时外层是全局作用域,因此 this 指向 window(非严格模式下)。调用者 obj 不会影响箭头函数的 this。这就像智能厨具的出厂设置不因操作者改变。若想得到 obj.name,必须使用普通函数 getName() { return this.name; }。
Q10. 要让 ES6+ 代码兼容老浏览器,工程上最通用的做法是什么?
- A.让用户手动升级浏览器
- B.用 Babel 转译为 ES5,并按需引入 polyfill← 正确答案
- C.用 ESLint 把所有新语法标记为错误
- D.用 TypeScript 直接编译为机器码
答案:B。Babel 负责把 ES6 语法翻译成 ES5 等价代码,polyfill 补全新 API 的能力,是行业标准方案。浏览器升级不受开发者控制;ESLint 只检查不转换;TypeScript 编译目标是 JavaScript 而非机器码。这就像用翻译器加补丁让老灶台也能做新菜。