语法
标识符
标识符指的是用来识别各种值(变量、函数、属性、类)的合法名称。
- 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号(
$)和下划线(_) - 第二个字符及后面的字符,除了上述字符外,还可以用数字
0-9
const $el = document.querySelector("#app"); // $ 开头合法,常见于 jQuery/DOM 引用
const _private = 1; // _ 开头常表示"内部使用"
const camelCase = 1; // 驼峰是 JS 主流命名风格
const 2foo = 2; // ❌ SyntaxError:数字不能开头
const if = 1; // ❌ SyntaxError:不能使用保留字
JavaScript 有一些保留字(如
class、let、return、enum、await等),不能用作标识符。完整列表见 MDN 保留字
变量
声明
let。声明块级作用域的变量,可重新赋值const。声明常量绑定,声明后不能再重新赋值;但对对象/数组等引用类型,其内部属性仍可修改(若想冻结需用Object.freeze())var。声明函数作用域(全局作用域)的变量,已被let/const取代,仅在维护老代码时遇到
const obj = { a: 1 };
obj.a = 2; // ✅ 允许(修改属性)
obj = {}; // ❌ TypeError: Assignment to constant variable(不能重新绑定)
const frozen = Object.freeze({ a: 1 });
frozen.a = 2; // ⚠️ 静默失败(严格模式下抛 TypeError)
提升与暂时性死区(TDZ)
var 声明的变量会经历"提升(Hoisting)":在声明语句之前就能访问,值为 undefined。
let / const 也会被"提升",但不会初始化——从块级作用域开始到声明语句之间的区域就是暂时性死区(TDZ),访问会抛 ReferenceError。
// var:声明提升 + 初始化为 undefined
console.log(a); // undefined
var a = 1;
// let/const:仅提升"绑定",未初始化前处于 TDZ
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 1;
console.log(c); // ReferenceError
const c = 1; // const 还必须在声明时立即赋值
选型建议
| 场景 | 选择 | 原因 |
|---|---|---|
| 默认情况 | const | 让"不可变"成为承诺,最易重构 |
| 需要重新赋值(循环计数、累加器) | let | 比 const 多一次写权限 |
| 维护 IE10- 老代码 | var 兜底 | 新项目不推荐使用 |
Q&A
下面的输出是什么,如何修改才能正确输出 0-4?
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 输出: 5, 5, 5, 5, 5
修改方案 1:将 var 替换为 let(利用块级作用域)。
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
原理解析(伪代码): 为什么
let可以,而var不行? 因为var是函数作用域/全局作用域,循环 5 次实际上修改的是同一个内存地址的i。而let是块级作用域,JS 引擎在每次循环时,都会为let重新创建一个新的词法环境(Lexical Environment),把当前的i值保存下来供闭包使用。引擎底层的处理逻辑类似于下面的伪代码:// var 的底层情况:全局只有一个 i
var i = 0;
setTimeout(() => console.log(i), 0);
i = 1;
setTimeout(() => console.log(i), 0);
// ...当所有 setTimeout 真正执行时,i 早就变成了 5
// let 的底层情况:每次循环都生成了独立的作用域块
{
let i = 0;
setTimeout(() => console.log(i), 0);
}
{
let i = 1;
setTimeout(() => console.log(i), 0);
}
{
let i = 2;
setTimeout(() => console.log(i), 0);
}
// ...当 setTimeout 执行时,它们沿着各自的块级作用域链向上找,找到了当时被锁住的独立变量 i
修改方案 2:使用 IIFE(立即调用的函数表达式)形成闭包。
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
}, 0);
})(i);
}
关键字
按用途分组(详细分类见 MDN 关键字与保留字):
- 声明类:
var、let、const、function、class、async function、function*(生成器) - 控制流:
if、else、switch、for、while、do、break、continue、return、throw、try、catch、finally - 对象/上下文:
this、super、new、delete、in、instanceof、typeof、void - 异步:
async、await - 模块:
import、export - 其他:
yield、debugger、with(严格模式禁用)
面试高频:箭头函数与普通函数的区别
在 ES6 中引入了箭头函数(Arrow Function),它不仅仅是普通函数的语法糖,在底层行为上有极其严格的区别。这是前端面试中几乎必问的考点。
| 特性 | 普通函数 (function) | 箭头函数 (() => {}) |
|---|---|---|
this 的指向 | 动态绑定。由调用方式决定(如作为对象方法调用、call/apply、new 等)。 | 静态绑定(词法作用域)。没有自己的 this,始终继承自外层执行环境的 this,且永远无法被 call/apply/bind 改变。 |
arguments 对象 | 有自己的 arguments 类数组对象,包含所有传入的参数。 | 没有 arguments 对象。如果要获取不定参数,必须使用剩余参数(...args)。 |
作为构造函数 (new) | 可以被 new 调用,生成实例对象。 | 不能被 new 调用,会抛出 TypeError。因为箭头函数没有 [[Construct]] 方法和 prototype 属性。 |
prototype 属性 | 有 prototype 属性。 | 没有 prototype 属性。 |
yield 关键字 | 可以作为 Generator 函数使用(function*)。 | 不能用作 Generator 函数,内部不能使用 yield 关键字。 |
常见陷阱题:
const obj = {
name: "Jackson",
sayHi: () => {
console.log(this.name);
},
};
obj.sayHi(); // 输出什么?答案:输出
undefined(或全局的 name)。因为对象字面量{}不产生作用域,箭头函数的this会向上寻找到全局作用域(浏览器中是window),而不是obj。如果要在对象方法中使用this,必须使用普通函数。
表达式
表达式是有返回值的代码单元。常见分类:
- 基本表达式:字面量(
1、"abc"、true)、标识符(x)、this - 属性访问:
obj.a、obj["a"] - 对象/数组初始化:
{}、[] - 正则表达式字面量:
/ab+c/i - 模板字符串:
`Hello, ${name}`(支持插值与标签模板) - 解构赋值:
const [a, b] = [1, 2]、const { x, y } = obj - 箭头函数表达式:
() => x * 2 - async / 生成器表达式:
async () => 1、function* () { yield 1 } - 展开 / 剩余:
[...arr, 4, 5]、(...args) => args - 一元 / 二元 / 三元运算表达式
- 逗号表达式:
(a, b)返回最后一个值 - new 表达式:
new Foo()
运算符
优先级总表(高 → 低)
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 19 | ()、[]、.、.?.、new Foo(args)、() 函数调用 | 成员访问与函数调用 |
| 18 | ++、--、!、+(单目)、-(单目)、typeof、void、delete、 await | 一元运算 |
| 17 | ** | 幂运算(右结合) |
| 16 | *、/、% | 乘除模 |
| 15 | +(双目)、-(双目) | 加减 |
| 14 | <<、>>、>>> | 移位 |
| 13 | <、<=、>、>=、in、instanceof | 比较 |
| 12 | ==、!=、===、!== | 相等 |
| 11 | & | 按位与 |
| 10 | ^ | 按位异或 |
| 9 | \| | 按位或 |
| 8 | && | 逻辑与 |
| 7 | \|\|、?? | 逻辑或 / 空值合并(不可混用,需括号隔离) |
| 6 | ?: | 三元 |
| 5 | =、+=、-=、**=、\|\|=、&&=、??= | 赋值 |
| 4 | ...(展开) | 数组/对象展开 |
| 1 | , | 逗号 |
速记口诀:单目 > 算术 > 比较 > 相等 > 位运算 > 逻辑 > 三元 > 赋值 > 展开 > 逗号。掌握到这个粒度,90% 的优先级题都能过。
常用运算符分类
- 算术:
+、-、*、/、%、** - 赋值:
=、复合赋值(+=、-=、*=、/=、%=、**=) - 自增减:
++、--(注意前缀 vs 后缀的求值时机) - 比较:
<、<=、>、>=、==、!=、===、!== - 逻辑:
&&、||、!、?? - 三元:
?: - 展开 / 剩余:
...
+ 的两重身份
- 算术加法
- 字符串拼接(任一侧为字符串就走拼接):
5 + ""→"5" - 单目
+是Number()的语法糖:+'5'→5,+false→0,+null→0,+undefined→NaN
&& / || / ?? 的短路与陷阱
// || 短路:左侧为 falsy 时返回右侧
let name = a || "default"; // 注意:0 / "" 也会被替换
// ?? 空值合并:仅在左侧为 null / undefined 时返回右侧
0 ?? 10; // 0 (0 不是 null/undefined)
"" ?? "x"; // "" (同上)
null ?? "x"; // "x"
// ⚠️ ?? 不能与 || 或 && 直接混用,必须用括号隔离
// a || b ?? c → SyntaxError
// (a || b) ?? c ✅
现代运算符速览(ES2020+)
| 运算符 | 用途 | 引入版本 |
|---|---|---|
?. 可选链 | a?.b?.c 中途遇 null/undefined 就停下,不抛错 | ES2020 |
?? 空值合并 | 仅在左侧为 null/undefined 时返回右侧 | ES2020 |
\|\|=、&&=、??= 逻辑赋值 | a ??= b 等价于 a = a ?? b | ES2021 |
** 幂运算 | 2 ** 10 === 1024 | ES2016 |
| 数字分隔符 | 1_000_000 | ES2021 |
// 可选链
const city = user?.address?.city; // 中间任意一环为 null/undefined 都返回 undefined
// 逻辑赋值(少写一行,性能也更优)
let count = 0;
count ??= 10; // 0 ?? 10 = 0,count 不变
count ||= 10; // 0 || 10 = 10,count 变 10
展开 vs 剩余(同一个 ...)
- 展开(Spread):把"可迭代对象 / 对象"拆成独立元素
- 剩余(Rest):把"零散元素"收集成数组 / 对象
// 展开
const arr2 = [...arr1, 4, 5];
const obj2 = { ...obj1, x: 1 };
// 剩余
function f(...args) {
/* args 是数组 */
}
const { a, ...rest } = obj;
new 与 . 的优先级(面试陷阱)
new 表达式根据括号位置有两种解析方式:
// 形式 A:new 后面跟一个带括号的函数调用 → 整体优先级高
new Foo().getName(); // 等价于 (new Foo()).getName()
// 形式 B:new 后面跟一个成员访问 → 成员访问优先级高
new Foo.getName(); // 等价于 new (Foo.getName)()
本质是运算符优先级:
new Foo(args)优先级高(带括号),而new单目(无括号)优先级低于.。
以上只是罗列了一些常用表达式和运算符,更详细的参考 表达式和运算符
语句
- 条件:
if / else if / else、switch - 循环:
for、while、do-while - 遍历:
for...in(遍历对象的键,含原型链)、for...of(遍历可迭代对象的值)、for await...of(异步迭代器) - 跳转:
break、continue、return、throw - 标签:
label(配合break/continue实现多层跳出) - 异常捕获:
try / catch / finally - 调试:
debugger(触发断点,前提是 DevTools 已打开)
// for...of:遍历值(Array / String / Map / Set / Generator)
for (const v of [1, 2, 3]) console.log(v); // 1 2 3
// for...in:遍历键(普通对象慎用,会带原型链上的可枚举属性)
for (const k in { a: 1, b: 2 }) console.log(k); // a b
// for await...of:异步迭代
for await (const chunk of fetchStream()) {
console.log(chunk);
}
面试高频:
for...invsfor...of
for...in拿到的是键(字符串),可枚举属性都会被遍历,原型链上的属性也算上for...of拿到的是值,要求对象实现了Symbol.iterator- 遍历对象请用
Object.keys()/Object.entries()+for...of,避开for...in的原型链陷阱
注释
- 单行:
// comment - 多行:
/* comment */ - JSDoc:
/** 函数/类型注释 */,被 VSCode / TSC 解析,可以提供类型提示与静态检查
// 单行注释
/* 多行注释 */
/**
* 计算两数之和
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}