第2章:词法结构(Lexical Structure)
一、导读
1.1 本章学习目标
第2章介绍JavaScript的词法结构——这是语言的最底层语法。理解词法结构是正确编写JavaScript代码的前提。本章的核心学习目标包括:
- 了解JavaScript的字符集:Unicode和UTF-16编码
- 掌握标识符的命名规则:什么名字是合法的?
- 认识关键字和保留字:哪些名字不能使用?
- 理解各种字面量:数字、字符串、布尔值、数组、对象
- 掌握注释的写法:单行注释和多行注释
- 理解自动分号插入(ASI)机制:JavaScript何时自动加分号?
1.2 为什么本章如此重要
词法结构是语言的"语法规则的语法"。虽然大多数开发者不会每天思考这些问题,但理解它们能帮助你避免一些诡异的bug。例如,自动分号插入(ASI)机制经常导致意外行为,理解它的工作原理可以避免很多陷阱。
1.3 核心问题引导
JavaScript的标识符可以包含哪些字符?中文可以做变量名吗?
为什么有些关键字被"保留"了但从未使用?
自动分号插入(ASI)在哪些情况下会导致bug?
模板字面量和普通字符串有什么本质区别?
为什么 0.1 + 0.2 !== 0.3?JavaScript如何处理浮点数?
二、核心概念详解
2.1 字符集
JavaScript使用Unicode字符集,源文件通常以UTF-8编码保存。这意味着:
// 中文变量名是合法的
let 名字 = "Alice";
let 年龄 = 20;
console.log(`${名字}的年龄是${年龄}`);
// 希腊字母也可以
let π = 3.14159;
let α = 0.1, β = 0.2;
console.log(α + β);2.2 标识符
标识符用于命名变量、函数、类等。规则如下:
- 第一个字符必须是字母、下划线(_)或美元符号($)
- 后续字符可以是字母、数字、下划线或美元符号
- 不能使用关键字和保留字
- 区分大小写
// 合法的标识符
let myVariable = "valid";
let _private = "valid";
let $jquery = "valid";
let camelCase = "valid";
let PascalCase = "valid";
let snake_case = "valid";
// 命名约定
let userName = "Alice"; // 变量:camelCase
let MAX_SIZE = 100; // 常量:UPPER_SNAKE_CASE
function calculateTotal() {} // 函数:camelCase
class UserProfile {} // 类:PascalCase2.3 关键字和保留字
// 关键字(不能使用)
// break, case, catch, class, const, continue, debugger, default,
// delete, do, else, export, extends, finally, for, function, if,
// import, in, instanceof, new, return, super, switch, this, throw,
// try, typeof, var, void, while, with, yield
// 未来保留字
// enum, implements, interface, let, package, private, protected,
// public, static, await
// 特殊值(也不能用作标识符)
// null, true, false2.4 字面量
字面量是直接写在代码中的值:
// 数字字面量
let num = 42;
let float = 3.14;
let hex = 0xFF;
let binary = 0b1010;
let octal = 0o12;
let bigint = 9007199254740991n;
// 字符串字面量
let str1 = "hello";
let str2 = 'world';
let str3 = `template`; // 模板字面量
// 布尔字面量
let bool = true;
// 空值字面量
let empty = null;
let undef = undefined;
// 数组字面量
let arr = [1, 2, 3];
// 对象字面量
let obj = {name: "Alice", age: 20};
// 正则表达式字面量
let regex = /pattern/gi;2.5 模板字面量
模板字面量(ES6)使用反引号,支持多行字符串和表达式插值:
let name = "Alice";
let age = 20;
// 表达式插值
let greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// 多行字符串
let html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// 标签模板
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
let val = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + str + val;
}, '');
}
let result = highlight`Hello ${name}, you are ${age} years old.`;
console.log(result);2.6 自动分号插入(ASI)
JavaScript解析器会在某些位置自动插入分号。这可能导致意外行为:
// 这些情况会自动插入分号
let x = 1 // 自动插入
let y = 2 // 自动插入
// 危险情况
let a = 1
let b = a
(function() {
// 这里会被解析为 a(...) 调用!
// 而不是 a; (function() {...})
})();
// 解决方案:总是使用分号
let c = 1;
let d = c;
(function() {
console.log("safe");
})();
// 另一个危险情况
// return语句后面的值如果换行,会自动插入分号
function getValue() {
return // 这里自动插入分号!
{
value: 42
};
}
console.log(getValue()); // undefined
// 正确写法
function getValueFixed() {
return {
value: 42
};
}三、实战案例
3.1 标识符和命名规范
// 好的命名实践
const MAX_RETRY_COUNT = 3;
const userName = "Alice";
const isUserLoggedIn = true;
const getUserProfile = (id) => { /* ... */ };
class UserProfileComponent { /* ... */ }
// 避免的命名
const a = 42; // 无意义
const data = []; // 太模糊
const d = new Date(); // 单字母(循环变量除外)
const temp = 42; // 临时变量应该有更具体的名字3.2 模板字面量的高级用法
// 条件表达式
let isLoggedIn = true;
let message = `Welcome${isLoggedIn ? '' : ' back'}!`;
// 函数调用
let items = [1, 2, 3];
let list = `Items: ${items.join(', ')}`;
// 嵌套模板
let users = [
{name: "Alice", age: 20},
{name: "Bob", age: 25}
];
let html = `
<ul>
${users.map(u => `
<li>${u.name} (${u.age})</li>
`).join('')}
</ul>
`;
console.log(html);四、本章小结
第2章介绍了JavaScript的词法结构:
字符集:JavaScript使用Unicode,支持中文等非ASCII字符作为标识符。
标识符:遵循camelCase(变量/函数)和PascalCase(类)的命名约定。
关键字:有约40个关键字和若干保留字不能用作标识符。
字面量:数字、字符串、布尔值、数组、对象、正则等都有字面量语法。
模板字面量:支持表达式插值和多行字符串,是现代JavaScript的重要特性。
ASI:自动分号插入机制在某些情况下会导致意外行为,推荐总是使用分号。
五、思考与练习
思考题
JavaScript的标识符规则与Java/C有什么异同?
为什么ES6引入了 let 和 const 来替代 var?
自动分号插入(ASI)的设计初衷是什么?它带来了什么问题?
模板字面量相比字符串拼接有什么优势?
为什么 typeof NaN 返回 "number"?
编程练习
编写代码验证各种标识符命名是否合法。
使用模板字面量生成一段HTML代码。
找出3个自动分号插入导致bug的例子并解释原因。