2JavaScript: The Good Parts

语法

语法基础

空白注释数字字符串标识符

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第2章:语法(Grammar)

一、导读

1.1 本章学习目标

第2章深入讲解 JavaScript 的语法细节。虽然 JavaScript 的语法看起来与 Java 和 C 类似,但其中有很多微妙之处。本章帮助你理解 JavaScript 语法的精华与陷阱。

本章的核心学习目标包括:

  • 理解 JavaScript 的数字系统:掌握 IEEE 754 浮点数的特性。
  • 掌握字符串的操作:学会使用字符串方法和正则表达式。
  • 理解语句的语义:掌握各种控制流语句的特点。
  • 认识 JavaScript 的表达式:理解运算符的优先级和结合性。
  • 避免常见的语法陷阱:如分号自动插入、浮点精度等。

1.2 为什么第二章如此重要

语法是编程的基础。对语法的深入理解能让你避免很多常见的 bug,写出更加可靠的代码。JavaScript 的语法有一些独特的特性(如分号自动插入、宽松的类型系统),如果不理解这些特性,很容易写出错误的代码。

1.3 核心问题引导

JavaScript 的数字为什么会有精度问题?如何避免?

分号自动插入机制是什么?它会导致什么问题?

typeof 运算符有哪些 surprising 的结果?

字符串的 charCodeAt 和 fromCharCode 有什么用途?

为什么 for in 循环不适合遍历数组?


二、核心概念详解

2.1 数字

JavaScript 只有一种数字类型——双精度64位浮点数(IEEE 754)。

javascript
// 所有数字都是浮点数
typeof 42;        // "number"
typeof 3.14;      // "number"
typeof NaN;       // "number"
typeof Infinity;  // "number"

// 精度问题
0.1 + 0.2;                    // 0.30000000000000004
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;  // true

// 整数运算安全
// 安全整数范围:-(2^53 - 1) 到 2^53 - 1
Number.MAX_SAFE_INTEGER;  // 9007199254740991
Number.MIN_SAFE_INTEGER;  // -9007199254740991

// 整数运算方法
Math.floor(3.7);   // 3
Math.ceil(3.2);    // 4
Math.round(3.5);   // 4
Math.trunc(3.7);   // 3

// 数字方法
var n = 42;
n.toString(2);   // "101010"(二进制)
n.toString(16);  // "2a"(十六进制)
n.toFixed(2);    // "42.00"

2.2 字符串

JavaScript 的字符串是不可变的16位字符序列。

javascript
// 字符串长度(按 UTF-16 代码单元计算)
"Hello".length;  // 5
"中文".length;   // 2

// 字符串方法
var s = "Hello, World!";
s.charAt(0);       // "H"
s.charCodeAt(0);   // 72
String.fromCharCode(72);  // "H"

s.indexOf("World");    // 7
s.lastIndexOf("l");    // 10
s.includes("World");   // true
s.startsWith("Hello"); // true
s.endsWith("!");       // true

s.slice(0, 5);     // "Hello"
s.substring(7, 12); // "World"
s.trim();          // 去除两端空白

s.toUpperCase();   // "HELLO, WORLD!"
s.toLowerCase();   // "hello, world!"

s.replace("World", "JavaScript");  // "Hello, JavaScript!"
s.split(", ");     // ["Hello", "World!"]

2.3 语句

2.3.1 表达式语句

javascript
// 表达式语句
x = y + 1;
func();
obj.prop;  // 合法但无意义

2.3.2 复合语句(块)

javascript
// 块语句
{
    var x = 1;
    var y = 2;
    // var 没有块作用域(let/const 有)
}
console.log(x);  // 1(var 泄露到外部)

// 使用 let 获得块作用域
{
    let a = 1;
    const b = 2;
}
// console.log(a);  // ReferenceError

2.3.3 if 语句

javascript
// if 语句
if (condition) {
    // ...
} else if (otherCondition) {
    // ...
} else {
    // ...
}

// 注意:falsy 值
// false, 0, "", null, undefined, NaN 都是 falsy
if (0) { }      // 不执行
if ("") { }     // 不执行
if (null) { }   // 不执行

2.3.4 switch 语句

javascript
// switch 使用严格相等
switch (value) {
    case "1":
        // 不会匹配数字 1
        break;
    case 1:
        // 匹配数字 1
        break;
}

// 注意:没有 break 会 fall through
switch (value) {
    case 1:
        console.log("one");
        // 没有 break!
    case 2:
        console.log("two");  // 也会执行
        break;
}

2.3.5 for 语句

javascript
// 传统 for 循环
for (var i = 0; i < 10; i++) { }

// for in(遍历对象的可枚举属性)
var obj = {a: 1, b: 2, c: 3};
for (var key in obj) {
    console.log(key + ": " + obj[key]);
}
// 注意:for in 也会遍历原型链上的属性
// 应该使用 hasOwnProperty 过滤
for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(key + ": " + obj[key]);
    }
}

// for of(遍历可迭代对象的值)
var arr = [10, 20, 30];
for (var value of arr) {
    console.log(value);  // 10, 20, 30
}

2.4 表达式

2.4.1 运算符

javascript
// 三元运算符
var result = condition ? value1 : value2;

// 逻辑运算符
var a = true && "hello";   // "hello"
var b = false || "world";  // "world"
var c = !true;             // false

// 空值合并运算符
var value = nullable ?? "default";

// 可选链
var name = user?.profile?.name;

2.4.2 类型转换

javascript
// 隐式类型转换(容易出错)
"3" + 4;       // "34"(字符串拼接)
"3" - 4;       // -1(数字运算)
"3" * "4";     // 12
true + 1;      // 2
null + 1;      // 1
undefined + 1; // NaN

// 显式类型转换
Number("42");     // 42
Number("");       // 0
Number(" ");      // 0
Number("abc");    // NaN
parseInt("42px"); // 42
parseFloat("3.14em");  // 3.14
String(42);       // "42"
Boolean(0);       // false
Boolean("");      // false
Boolean(null);    // false

三、实战案例

3.1 安全的数字比较

javascript
// 浮点数比较工具
function nearlyEqual(a, b, epsilon) {
    epsilon = epsilon || Number.EPSILON;
    return Math.abs(a - b) < epsilon;
}

nearlyEqual(0.1 + 0.2, 0.3);  // true

// 整数安全的货币计算(以分为单位)
function addCents(a, b) {
    return Math.round(a * 100 + b * 100) / 100;
}

addCents(0.1, 0.2);  // 0.3

3.2 字符串模板

javascript
// 模板字符串(ES6)
var name = "World";
var greeting = `Hello, ${name}!`;

// 多行字符串
var html = `
    <div>
        <h1>${title}</h1>
        <p>${content}</p>
    </div>
`;

// 标签模板
function safeHtml(strings, ...values) {
    var result = "";
    strings.forEach(function(str, i) {
        result += str;
        if (i < values.length) {
            result += escapeHtml(values[i]);
        }
    });
    return result;
}

function escapeHtml(str) {
    return String(str)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}

var userInput = "<script>alert('xss')</script>";
var safe = safeHtml`<div>${userInput}</div>`;

四、本章小结

第2章深入讲解了 JavaScript 的语法细节:

数字:所有数字都是浮点数,注意精度问题。

字符串:不可变的字符序列,提供丰富的操作方法。

语句:理解 for in 和 for of 的区别,注意分号自动插入。

表达式:注意隐式类型转换的陷阱。

类型转换:优先使用显式转换,避免隐式转换的意外行为。

理解语法细节是避免 bug 的基础。JavaScript 的语法看似简单,但其中有很多微妙的陷阱需要警惕。


五、思考与练习

思考题

为什么 JavaScript 没有独立的整数类型?

for in 和 for of 有什么区别?各自适用于什么场景?

分号自动插入机制在什么情况下会导致 bug?

如何安全地比较两个浮点数?

parseInt 和 Number 有什么区别?

编程练习

实现精确的货币计算:避免浮点精度问题。

实现字符串工具函数:包括 capitalize、camelCase、kebabCase。

实现安全的类型转换:处理各种边界情况。


2/9语法
0%