DAY_17深度博客:CSS 响应式布局 · BFC · JavaScript 完全指南(下)

五、数据类型完全指南

5.1 类型分类

JavaScript 数据类型
原始类型 Primitive
对象类型 Object
number 数字
string 字符串
boolean 布尔
null 空
undefined 未定义
symbol ES6+
bigint ES2020+
Array 数组
Function 函数
Object 对象
Date 日期
RegExp 正则
Map/Set ES6+
存储在栈内存 Stack
存储在堆内存 Heap

名词解释:原始类型 vs 对象类型
对比维度 原始类型(Primitive) 对象类型(Object)
存储位置 栈内存(Stack) 堆内存(Heap)+ 栈中存引用
赋值方式 值复制 引用复制
比较方式 比较值 比较引用地址
可变性 不可变(immutable) 可变(mutable)
示例 42'hello'true [1,2,3]{name:'张三'}

5.2 typeof 运算符

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>typeof 完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 10px 14px; font-size: 14px; text-align: left; }
        th { background: #3498db; color: white; }
        tr:nth-child(even) { background: #f8f9fa; }
        .type-tag {
            display: inline-block;
            padding: 2px 10px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: bold;
        }
        .t-number { background: #d5f5e3; color: #1e8449; }
        .t-string { background: #d6eaf8; color: #1a5276; }
        .t-boolean { background: #fdebd0; color: #784212; }
        .t-undefined { background: #f2f3f4; color: #7f8c8d; }
        .t-object { background: #f4ecf7; color: #6c3483; }
        .t-function { background: #fef9e7; color: #7d6608; }
        .warn { color: #e74c3c; font-size: 13px; }
    </style>
</head>
<body>
    <h2>typeof 运算符完整示例</h2>
    
    <table>
        <tr>
            <th>表达式</th>
            <th>typeof 结果</th>
            <th>说明</th>
        </tr>
        <tr>
            <td><code>typeof 42</code></td>
            <td><span class="type-tag t-number">"number"</span></td>
            <td>整数</td>
        </tr>
        <tr>
            <td><code>typeof 3.14</code></td>
            <td><span class="type-tag t-number">"number"</span></td>
            <td>浮点数</td>
        </tr>
        <tr>
            <td><code>typeof NaN</code></td>
            <td><span class="type-tag t-number">"number"</span></td>
            <td>NaN 是 number 类型</td>
        </tr>
        <tr>
            <td><code>typeof Infinity</code></td>
            <td><span class="type-tag t-number">"number"</span></td>
            <td>Infinity 是 number 类型</td>
        </tr>
        <tr>
            <td><code>typeof "hello"</code></td>
            <td><span class="type-tag t-string">"string"</span></td>
            <td>字符串</td>
        </tr>
        <tr>
            <td><code>typeof ''</code></td>
            <td><span class="type-tag t-string">"string"</span></td>
            <td>空字符串也是 string</td>
        </tr>
        <tr>
            <td><code>typeof true</code></td>
            <td><span class="type-tag t-boolean">"boolean"</span></td>
            <td>布尔值</td>
        </tr>
        <tr>
            <td><code>typeof undefined</code></td>
            <td><span class="type-tag t-undefined">"undefined"</span></td>
            <td>未定义</td>
        </tr>
        <tr>
            <td><code>typeof null</code></td>
            <td><span class="type-tag t-object">"object"</span> <span class="warn">⚠️ 历史 Bug!</span></td>
            <td>null 的 typeof 结果是 "object",这是 JS 早期的历史遗留 Bug</td>
        </tr>
        <tr>
            <td><code>typeof {}</code></td>
            <td><span class="type-tag t-object">"object"</span></td>
            <td>普通对象</td>
        </tr>
        <tr>
            <td><code>typeof []</code></td>
            <td><span class="type-tag t-object">"object"</span></td>
            <td>数组也是 object(用 Array.isArray() 判断数组)</td>
        </tr>
        <tr>
            <td><code>typeof function(){}</code></td>
            <td><span class="type-tag t-function">"function"</span></td>
            <td>函数(function 是 object 的子类型)</td>
        </tr>
    </table>
    
    <div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 6px; padding: 14px; margin-top: 16px; font-size: 14px;">
        <strong>⚠️ 经典陷阱:<code>typeof null === "object"</code></strong><br>
        这是 JavaScript 的历史 Bug(由于早期实现中 null 的类型标签是 0,与 object 相同)。<br>
        判断是否为 null 应使用:<code>value === null</code>
    </div>

    <script>
        // 验证上表中的结论
        console.log(typeof 42);           // "number"
        console.log(typeof 3.14);         // "number"
        console.log(typeof NaN);          // "number"
        console.log(typeof Infinity);     // "number"
        console.log(typeof "hello");      // "string"
        console.log(typeof '');           // "string"
        console.log(typeof true);         // "boolean"
        console.log(typeof undefined);    // "undefined"
        console.log(typeof null);         // "object" ← 历史 Bug!
        console.log(typeof {});           // "object"
        console.log(typeof []);           // "object"
        console.log(typeof function(){}); // "function"
        
        // typeof 的嵌套使用
        console.log(typeof typeof 42);    // "string" ← typeof 返回字符串,所以 typeof "number" = "string"
    </script>
</body>
</html>
💡 代码解释:typeof 运算符

一、typeof 的基本用法

javascript 复制代码
// 语法:typeof 操作数
typeof 42;           // "number"
typeof "hello";      // "string"

// 也可以用括号(但不必要)
typeof(42);          // "number"
typeof(true);        // "boolean"

二、typeof 返回值完整列表

typeof 操作数 返回值 说明
数字(整数/浮点数) "number" 包括NaN、Infinity
字符串 "string" 包括空字符串
布尔值 "boolean" true 或 false
undefined "undefined" 未定义
null "object" ⚠️ 历史Bug
对象 "object" 包括数组
函数 "function" 函数是特殊的对象
Symbol (ES6+) "symbol" 唯一值
BigInt (ES2020+) "bigint" 大整数

三、代码中的关键案例分析

案例1:NaN 和 Infinity 的类型

javascript 复制代码
console.log(typeof NaN);       // "number"(虽然 NaN = Not a Number)
console.log(typeof Infinity);  // "number"

// 原因:NaN 和 Infinity 都是 number 类型的特殊值
// NaN 表示"非数字"但本身是 number 类型
// Infinity 表示无穷大,也是 number 类型

案例2:typeof null 的历史Bug

javascript 复制代码
console.log(typeof null);  // "object" ← 错误!应该是 "null"

// 原因:JavaScript 最初实现时的Bug
// 在第一版 JavaScript 中,值用32位表示:
// - 对象的类型标签是 0
// - null 被表示为全0(0x00)
// 因此 null 被误判为 object 类型

正确判断 null 的方法:

javascript 复制代码
var value = null;

// ❌ 错误:typeof 无法准确判断 null
if (typeof value === 'object') {
    // 这里会误判 null 和真正的对象
}

// ✅ 正确:直接比较
if (value === null) {
    console.log('value 是 null');
}

// ✅ 正确:同时判断 null 和 undefined
if (value == null) {  // 使用 ==(不是 ===)
    console.log('value 是 null 或 undefined');
}

案例3:数组的typeof是"object"

javascript 复制代码
console.log(typeof []);          // "object"
console.log(typeof [1, 2, 3]);  // "object"

// 原因:数组本质上是对象,只是特殊的对象
// 正确判断数组的方法:
Array.isArray([]);                // true
Array.isArray({ name: '张三' }); // false

// 或者使用 instanceof:
[] instanceof Array;  // true

案例4:typeof 的嵌套使用

javascript 复制代码
console.log(typeof typeof 42);  // "string"

// 执行过程:
// 1. typeof 42 → "number"(字符串)
// 2. typeof "number" → "string"("number"是字符串)

四、typeof 的典型应用场景

场景1:参数验证

javascript 复制代码
function calculateArea(width, height) {
    // 检查参数类型
    if (typeof width !== 'number' || typeof height !== 'number') {
        throw new Error('参数必须是数字');
    }
    return width * height;
}

calculateArea(10, 20);      // ✅ 200
// calculateArea('10', 20); // ❌ 报错

场景2:判断变量是否存在

javascript 复制代码
// ✅ 正确:使用 typeof 检查未声明的变量不会报错
if (typeof someUndeclaredVariable === 'undefined') {
    console.log('变量不存在');
}

// ❌ 错误:直接访问会报错
// if (someUndeclaredVariable === undefined) {  // ReferenceError

场景3:特性检测(Feature Detection)

javascript 复制代码
// 检查浏览器是否支持某个API
if (typeof fetch === 'function') {
    // 支持 fetch API
    fetch('/api/data').then(...);
} else {
    // 不支持,使用备用方案(如 XMLHttpRequest)
    var xhr = new XMLHttpRequest();
    // ...
}

// 检查是否有 localStorage
if (typeof localStorage !== 'undefined') {
    localStorage.setItem('key', 'value');
}

场景4:区分函数和其他值

javascript 复制代码
function executeCallback(callback) {
    if (typeof callback === 'function') {
        callback();  // 是函数,执行它
    } else {
        console.log('callback 不是函数');
    }
}

executeCallback(function() { console.log('执行了'); });  // ✅ 执行
executeCallback('not a function');  // ❌ 不执行,输出提示

五、typeof 的局限性

限制 示例 说明
无法区分 null 和对象 typeof null === "object" 需要用 === null
无法区分数组和对象 typeof [] === "object" 需要用 Array.isArray()
无法区分不同类型的对象 typeof new Date() === "object" 需要用 instanceof

六、typeof 的替代方案

javascript 复制代码
// 方法1:Object.prototype.toString(最准确)
Object.prototype.toString.call([]);        // "[object Array]"
Object.prototype.toString.call({});        // "[object Object]"
Object.prototype.toString.call(null);      // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(new Date()); // "[object Date]"

// 方法2:instanceof(检查原型链)
[] instanceof Array;        // true
{} instanceof Object;       // true
new Date() instanceof Date; // true

// 方法3:Array.isArray()(检查数组)
Array.isArray([]);    // true
Array.isArray({});    // false

七、真实项目示例

示例1:React Prop Types 验证

javascript 复制代码
function UserCard(props) {
    // 类型检查
    if (typeof props.name !== 'string') {
        console.warn('UserCard: name 应该是字符串');
    }
    if (typeof props.age !== 'number') {
        console.warn('UserCard: age 应该是数字');
    }
    return <div>{props.name}, {props.age}岁</div>;
}

示例2:工具函数

javascript 复制代码
// 检查是否为对象(排除 null)
function isObject(value) {
    return typeof value === 'object' && value !== null;
}

isObject({});        // true
isObject([]);        // true
isObject(null);      // false
isObject(undefined); // false

5.3 number 数值类型

整数的三种进制表示

JavaScript 整数进制
十进制 Decimal
八进制 Octal
十六进制 Hexadecimal
二进制 Binary ES6+
764 → 764
012 → 10(0开头)
0x12 → 18(0x开头)
0b1010 → 10(0b开头)

浮点精度问题(IEEE 754)
复制代码
JavaScript 使用 IEEE 754 双精度浮点数(64位)表示所有数字。
十进制小数无法被精确地转换为二进制浮点数,导致运算误差。

0.1 + 0.2 = 0.30000000000000004   ← 不是 0.3!

解决方案:
1. toFixed(2) --- 保留小数位(返回字符串)
2. Math.round(result * 100) / 100 --- 四舍五入
3. 使用 decimal.js 等第三方库处理精确计算(如金融场景)
NaN 与 Infinity 完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>number 数值类型完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; background: #f0f4f8; }
        .section {
            background: white;
            border-radius: 10px;
            padding: 20px 24px;
            margin-bottom: 20px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
        }
        h3 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 8px; }
        .result-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 12px;
            margin-top: 12px;
        }
        .result-item {
            background: #f8f9fa;
            border-radius: 6px;
            padding: 10px 14px;
            font-family: monospace;
            font-size: 13px;
        }
        .result-item .expr { color: #2980b9; }
        .result-item .arrow { color: #7f8c8d; margin: 0 6px; }
        .result-item .val { color: #27ae60; font-weight: bold; }
        .result-item .warn { color: #e74c3c; font-weight: bold; }
        .tip { background: #e8f4fd; border-radius: 6px; padding: 10px 14px; font-size: 13px; margin-top: 10px; }
    </style>
</head>
<body>
    <div class="section">
        <h3>① 整数进制表示</h3>
        <div class="result-grid">
            <div class="result-item">
                <span class="expr">764</span><span class="arrow">→</span>
                <span class="val">764</span>(十进制)
            </div>
            <div class="result-item">
                <span class="expr">012</span><span class="arrow">→</span>
                <span class="val">10</span>(八进制:1×8 + 2 = 10)
            </div>
            <div class="result-item">
                <span class="expr">0x12</span><span class="arrow">→</span>
                <span class="val">18</span>(十六进制:1×16 + 2 = 18)
            </div>
            <div class="result-item">
                <span class="expr">0xFF</span><span class="arrow">→</span>
                <span class="val">255</span>(十六进制,颜色常用)
            </div>
        </div>
    </div>

    <div class="section">
        <h3>② 浮点数与精度问题</h3>
        <div class="result-grid">
            <div class="result-item">
                <span class="expr">1 + 2</span><span class="arrow">→</span>
                <span class="val">3</span>(整数加法正确)
            </div>
            <div class="result-item">
                <span class="expr">0.1 + 0.2</span><span class="arrow">→</span>
                <span class="warn">0.30000000000000004</span>⚠️ 精度问题
            </div>
            <div class="result-item">
                <span class="expr">(0.1+0.2).toFixed(1)</span><span class="arrow">→</span>
                <span class="val">"0.3"</span>(字符串形式)
            </div>
            <div class="result-item">
                <span class="expr">Math.round(0.1+0.2, 10)</span><span class="arrow">→</span>
                <span class="val">0</span>→ 使用parseFloat(n.toFixed(10))
            </div>
        </div>
        <div class="tip">💡 金融计算中必须处理精度问题,推荐使用 <code>decimal.js</code> 库或将金额转换为整数(分)进行运算。</div>
    </div>

    <div class="section">
        <h3>③ 科学计数法</h3>
        <div class="result-grid">
            <div class="result-item">
                <span class="expr">1.3e4</span><span class="arrow">→</span>
                <span class="val">13000</span>(1.3 × 10⁴)
            </div>
            <div class="result-item">
                <span class="expr">2.3e-2</span><span class="arrow">→</span>
                <span class="val">0.023</span>(2.3 × 10⁻²)
            </div>
            <div class="result-item">
                <span class="expr">1.67e78</span><span class="arrow">→</span>
                <span class="val">1.67e+78</span>(极大数)
            </div>
        </div>
    </div>

    <div class="section">
        <h3>④ NaN(Not a Number)</h3>
        <div class="result-grid">
            <div class="result-item">
                <span class="expr">typeof NaN</span><span class="arrow">→</span>
                <span class="warn">"number"</span>(NaN 是 number 类型!)
            </div>
            <div class="result-item">
                <span class="expr">NaN * 0</span><span class="arrow">→</span>
                <span class="warn">NaN</span>(与任何数运算都是NaN)
            </div>
            <div class="result-item">
                <span class="expr">NaN == NaN</span><span class="arrow">→</span>
                <span class="warn">false</span>(NaN 不等于自己!)
            </div>
            <div class="result-item">
                <span class="expr">isNaN(NaN)</span><span class="arrow">→</span>
                <span class="val">true</span>
            </div>
            <div class="result-item">
                <span class="expr">isNaN(250)</span><span class="arrow">→</span>
                <span class="val">false</span>
            </div>
            <div class="result-item">
                <span class="expr">isNaN('hello')</span><span class="arrow">→</span>
                <span class="val">true</span>('hello' 转 number 失败,得 NaN)
            </div>
            <div class="result-item">
                <span class="expr">Number.isNaN(NaN)</span><span class="arrow">→</span>
                <span class="val">true</span>(ES6:不会转换,更精确)
            </div>
            <div class="result-item">
                <span class="expr">Number.isNaN('hello')</span><span class="arrow">→</span>
                <span class="val">false</span>(ES6:字符串不是 NaN)
            </div>
        </div>
        <div class="tip">
            💡 <strong>NaN 的三大特点:</strong><br>
            1. NaN 是 number 类型(<code>typeof NaN === "number"</code>)<br>
            2. NaN 与任何数字运算结果都是 NaN<br>
            3. NaN 不等于任何值,包括自身(<code>NaN !== NaN</code>)
        </div>
    </div>

    <div class="section">
        <h3>⑤ 数字的有效范围</h3>
        <div class="result-grid">
            <div class="result-item">
                <span class="expr">Number.MAX_VALUE</span><span class="arrow">→</span>
                <span class="val">1.7976931348623157e+308</span>
            </div>
            <div class="result-item">
                <span class="expr">Number.MIN_VALUE</span><span class="arrow">→</span>
                <span class="val">5e-324</span>(最小正数)
            </div>
            <div class="result-item">
                <span class="expr">3e400</span><span class="arrow">→</span>
                <span class="warn">Infinity</span>(超出范围)
            </div>
            <div class="result-item">
                <span class="expr">-3e400</span><span class="arrow">→</span>
                <span class="warn">-Infinity</span>
            </div>
            <div class="result-item">
                <span class="expr">isFinite(100)</span><span class="arrow">→</span>
                <span class="val">true</span>
            </div>
            <div class="result-item">
                <span class="expr">isFinite(Infinity)</span><span class="arrow">→</span>
                <span class="val">false</span>
            </div>
            <div class="result-item">
                <span class="expr">isFinite(NaN)</span><span class="arrow">→</span>
                <span class="val">false</span>
            </div>
            <div class="result-item">
                <span class="expr">1 / 0</span><span class="arrow">→</span>
                <span class="warn">Infinity</span>(JS中除以0不报错!)
            </div>
        </div>
    </div>

    <script>
        // 整型进制
        console.log(764);    // 764
        console.log(012);    // 10
        console.log(0x12);   // 18
        console.log(0xFF);   // 255

        // 浮点精度
        console.log(0.1 + 0.2);                        // 0.30000000000000004
        console.log((0.1 + 0.2).toFixed(1));           // "0.3"

        // 科学计数法
        console.log(1.3e4);   // 13000
        console.log(2.3e-2);  // 0.023

        // NaN
        console.log(typeof NaN);   // "number"
        console.log(NaN * 0);      // NaN
        console.log(NaN == NaN);   // false
        console.log(isNaN(NaN));   // true
        console.log(isNaN('hello')); // true

        // 有效范围
        console.log(Number.MAX_VALUE);  // 1.7976931348623157e+308
        console.log(3e400);             // Infinity
        console.log(isFinite(3e400));   // false
        console.log(1 / 0);            // Infinity
    </script>
</body>
</html>
💡 代码解释:number 数值类型核心要点

一、整数进制速查

进制 前缀 示例 换算十进制 常用场景
十进制 764 764 日常计算
八进制 0 012 10(1×8+2) 文件权限(Linux)
十六进制 0x 0xFF 255(15×16+15) CSS颜色、编码
二进制(ES6) 0b 0b1010 10 位运算、权限掩码
javascript 复制代码
// 十六进制在CSS颜色中最常用
var red = 0xFF0000;    // CSS: #FF0000
var white = 0xFFFFFF;  // CSS: #FFFFFF

// 进制转换
(255).toString(16);  // "ff" → 转十六进制
(10).toString(2);    // "1010" → 转二进制
parseInt('FF', 16);  // 255 → 十六进制转十进制

二、浮点精度问题(IEEE 754)深度理解

javascript 复制代码
// ❌ 精度丢失
0.1 + 0.2  // 0.30000000000000004

// 原因:十进制小数 → 二进制时无限循环
// 0.1 = 0.0001100110011... (二进制无限循环)
// 0.2 = 0.0011001100110... (二进制无限循环)
// 两者相加出现舍入误差

// ✅ 解决方案1:toFixed(四舍五入,返回字符串)
(0.1 + 0.2).toFixed(2)   // "0.30"
parseFloat((0.1 + 0.2).toFixed(2))  // 0.3 (数值)

// ✅ 解决方案2:整数计算(推荐金融场景)
// 将金额转为"分"(整数),避免浮点误差
var price1 = 0.1 * 100;  // 10分
var price2 = 0.2 * 100;  // 20分
var total = (price1 + price2) / 100;  // 0.3 ✅

// ✅ 解决方案3:Number.EPSILON(ES6)
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON  // true

三、NaN 的三大特性(面试必考)

javascript 复制代码
// 特性1:NaN 的 typeof 是 "number"
typeof NaN === "number"  // true(反直觉!)

// 特性2:NaN 与任何值都不相等,包括自身
NaN === NaN   // false(唯一一个不等于自身的值)
NaN == NaN    // false

// 特性3:任何与 NaN 的运算结果都是 NaN
NaN + 1       // NaN
NaN * 0       // NaN
NaN ** 0      // 1(唯一例外:任何数的0次方是1)

⚠️ 注意:isNaN 与 Number.isNaN 的区别

javascript 复制代码
// isNaN():先尝试转换为 number,再判断
isNaN(NaN)      // true
isNaN('hello')  // true('hello' 转 number 得 NaN)
isNaN('123')    // false('123' 转 number 得 123)

// Number.isNaN()(ES6):不转换,只判断是否严格为 NaN
Number.isNaN(NaN)      // true
Number.isNaN('hello')  // false(字符串不是NaN)
Number.isNaN(undefined) // false

// ✅ 推荐使用 Number.isNaN(),更精确

四、Infinity 与数值范围

javascript 复制代码
// 正负无穷
1 / 0       // Infinity
-1 / 0      // -Infinity
Infinity + 1  // Infinity
Infinity * 0  // NaN

// ⚠️ JavaScript 除以零不报错!(不像 Java/C++)
1 / 0       // Infinity(不抛异常)

// 安全整数范围(2^53 - 1)
Number.MAX_SAFE_INTEGER  // 9007199254740991
Number.MIN_SAFE_INTEGER  // -9007199254740991

// 超出安全范围,整数计算不精确
9007199254740991 + 1  // 9007199254740992 ✅
9007199254740991 + 2  // 9007199254740992 ❌(应为 ...993)

// 解决大整数问题:使用 BigInt
9007199254740991n + 2n  // 9007199254740993n ✅

五、科学计数法

javascript 复制代码
// 表示极大/极小数
1.3e4   // 13000   (1.3 × 10^4)
2.3e-2  // 0.023   (2.3 × 10^-2)
1.5e308 // 接近最大值

// 实际场景
var BYTES_PER_GB = 1e9;       // 1,000,000,000 字节
var LIGHT_SPEED = 3e8;        // 3×10^8 米/秒
var ATOM_SIZE = 1e-10;        // 原子直径(米)

六、常见数值陷阱总结

陷阱 错误期望 实际结果 正确处理
0.1 + 0.2 0.3 0.30000000000000004 toFixed(2)
NaN === NaN true false Number.isNaN()
typeof NaN "nan" "number" 直接与 NaN 比较不可靠
1 / 0 报错 Infinity isFinite() 检查
超大整数+1 精确 可能不精确 使用 BigInt

5.4 string 字符串类型

名词解释:字符串(String)

字符串是由零个或多个字符组成的字符序列。在 JavaScript 中,字符串是不可变的原始类型,一旦创建,其内容无法改变(对字符串的操作会返回新字符串)。

转义字符表
转义序列 含义 示例 输出
\n 换行(Newline) '第一行\n第二行' 换行显示
\t 制表符(Tab) '名字\t年龄' 水平对齐
\' 单引号 'it\'s ok' it's ok
\" 双引号 "他说\"你好\"" 他说"你好"
\\ 反斜杠本身 'C:\\Users' C:\Users
\uXXXX Unicode 字符 '\u0041' A
\r 回车(Carriage Return) '\r\n' Windows换行
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>字符串完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; background: #f0f4f8; }
        .section { background: white; border-radius: 10px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
        h3 { color: #2c3e50; border-bottom: 2px solid #27ae60; padding-bottom: 8px; }
        pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
        .tag { color: #e06c75; }
        .str { color: #98c379; }
        .kw { color: #c678dd; }
        .num { color: #d19a66; }
        .cmt { color: #5c6370; font-style: italic; }
        .output-box {
            background: #1e2227;
            color: #d4d4d4;
            padding: 14px;
            border-radius: 6px;
            font-family: monospace;
            font-size: 13px;
            margin-top: 10px;
            white-space: pre-wrap;
        }
        .tip { background: #e8f4fd; border-radius: 6px; padding: 12px; font-size: 13px; margin-top: 10px; }
    </style>
</head>
<body>
    <div class="section">
        <h3>① 字符串的三种定界符</h3>
        <pre><span class="cmt">// 单引号</span>
<span class="kw">var</span> str1 = <span class="str">'Hello, JavaScript!'</span>;

<span class="cmt">// 双引号(内容中包含单引号时使用双引号)</span>
<span class="kw">var</span> str2 = <span class="str">"it's a beautiful day"</span>;

<span class="cmt">// 模板字符串(ES6,反引号 ` ,支持变量插值和多行)</span>
<span class="kw">var</span> name = <span class="str">'JavaScript'</span>;
<span class="kw">var</span> str3 = `Hello, ${name}!`;    <span class="cmt">// 模板字符串</span>
<span class="kw">var</span> str4 = `第一行
第二行
第三行`;                          <span class="cmt">// 模板字符串支持多行</span></pre>
        <div class="tip">💡 ES6 的模板字符串(backtick)功能强大,推荐在需要字符串拼接时使用。</div>
    </div>

    <div class="section">
        <h3>② 转义字符实战</h3>
        <div id="escape-demo"></div>
    </div>

    <div class="section">
        <h3>③ 字符串常用属性和方法预览</h3>
        <pre><span class="kw">var</span> str = <span class="str">'Hello, JavaScript!'</span>;

str.length          <span class="cmt">// 18 --- 字符串长度</span>
str.toUpperCase()   <span class="cmt">// 'HELLO, JAVASCRIPT!'</span>
str.toLowerCase()   <span class="cmt">// 'hello, javascript!'</span>
str.indexOf(<span class="str">'Script'</span>) <span class="cmt">// 12 --- 查找子串位置</span>
str.includes(<span class="str">'Java'</span>)  <span class="cmt">// true --- 是否包含子串(ES6)</span>
str.slice(0, 5)     <span class="cmt">// 'Hello' --- 截取子串</span>
str.split(<span class="str">', '</span>)     <span class="cmt">// ['Hello', 'JavaScript!'] --- 分割</span>
str.trim()          <span class="cmt">// 去除首尾空格</span>
str.replace(<span class="str">'Hello'</span>, <span class="str">'Hi'</span>) <span class="cmt">// 'Hi, JavaScript!'</span></pre>
    </div>

    <script>
        // 字符串基础
        var msg01 = 'Hello"高小乐';
        var msg02 = "你好'老头乐";
        console.log(msg01, typeof msg01);
        console.log(msg02, typeof msg02);

        // 转义字符
        var poem = '锄禾日当午\n汗滴禾下土\n水质盘中餐\n粒粒皆辛苦';
        console.log(poem);

        var msg03 = '使用\'可以在单引号字符串里包含单引号';
        var msg04 = "使用\"可以在双引号字符串里包含双引号";
        console.log(msg03);
        console.log(msg04);

        var msg05 = '在 JS 中,\\n 是转义字符,表示换行';
        console.log(msg05);

        console.log('\u0041');   // A
        console.log('\u4e2d');   // 中

        // 渲染转义字符演示
        var demo = document.getElementById('escape-demo');
        var escapeExamples = [
            { code: "'Hello\\nWorld'", result: 'Hello\nWorld', desc: '\\n 换行' },
            { code: "'it\\'s ok'", result: "it's ok", desc: "\\' 单引号" },
            { code: '"say \\"hi\\""', result: 'say "hi"', desc: '\\" 双引号' },
            { code: "'C:\\\\Users'", result: 'C:\\Users', desc: '\\\\ 反斜杠' },
            { code: "'\\u4e2d\\u6587'", result: '\u4e2d\u6587', desc: '\\uXXXX Unicode字符' },
        ];
        
        var html = '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">';
        escapeExamples.forEach(function(ex) {
            html += '<div style="background:#f8f9fa;padding:10px;border-radius:6px;font-family:monospace;font-size:13px;">' +
                '<div style="color:#2980b9">' + ex.code + '</div>' +
                '<div style="color:#7f8c8d;font-size:11px;margin:4px 0">' + ex.desc + '</div>' +
                '<div style="color:#27ae60;font-weight:bold;white-space:pre">' + ex.result + '</div>' +
                '</div>';
        });
        html += '</div>';
        demo.innerHTML = html;
    </script>
</body>
</html>
💡 代码解释:string 字符串类型核心要点

一、三种定界符的选择原则

javascript 复制代码
// 单引号 vs 双引号:功能完全相同,选择一种保持统一
var str1 = 'Hello, JavaScript!';   // 推荐(更简洁)
var str2 = "Hello, JavaScript!";   // 同样合法

// 嵌套引号:内外使用不同引号,避免转义
var str3 = "it's a beautiful day";   // ✅ 内含单引号,用双引号包裹
var str4 = '他说 "你好"';              // ✅ 内含双引号,用单引号包裹
var str5 = 'it\'s ok';               // ✅ 转义,但不如上面直观

// 模板字符串(ES6,反引号 ` ):推荐用于拼接
var name = 'JavaScript';
var greeting = `Hello, ${name}!`;    // Hello, JavaScript!

二、转义字符完整用法

javascript 复制代码
// \n 换行(最常用)
console.log('第一行\n第二行');
// 输出:
// 第一行
// 第二行

// \t 制表符(对齐)
console.log('姓名\t年龄\t城市');
console.log('张三\t25\t北京');
// 输出:
// 姓名    年龄    城市
// 张三    25     北京

// 引号转义
var msg1 = '我说\'你好\'';      // 我说'你好'
var msg2 = "他说\"再见\"";      // 他说"再见"

// 反斜杠转义(\本身)
var path = 'C:\\Users\\zhangsan';  // C:\Users\zhangsan

// Unicode 转义
console.log('\u0041');  // A
console.log('\u4e2d');  // 中
console.log('\u6587');  // 文

// 实际代码中使用示例
var filePath = 'C:\\Program Files\\MyApp\\config.json';
var poem = '床前明月光,\n疑是地上霜。\n举头望明月,\n低头思故乡。';

三、字符串不可变性(重要!)

javascript 复制代码
var str = 'Hello';
str[0] = 'J';              // 尝试修改第一个字符
console.log(str);          // 'Hello'(没有变!)
console.log(str[0]);       // 'H'(没有变!)

// 字符串是不可变的(immutable)
// 对字符串的操作都会返回新字符串,原字符串不变

var original = 'hello';
var upper = original.toUpperCase();
console.log(original);  // 'hello'(原字符串不变)
console.log(upper);     // 'HELLO'(新字符串)

四、模板字符串(Template Literals)的强大功能

javascript 复制代码
var name = '张三';
var age = 25;
var city = '北京';

// ❌ 传统字符串拼接(繁琐)
var msg1 = '姓名:' + name + ',年龄:' + age + ',城市:' + city;

// ✅ 模板字符串(简洁)
var msg2 = `姓名:${name},年龄:${age},城市:${city}`;

// 表达式插值(不只是变量)
var a = 10, b = 20;
var result = `${a} + ${b} = ${a + b}`;   // "10 + 20 = 30"
var upper = `${name.toUpperCase()} 的年龄是 ${age > 18 ? '成年' : '未成年'}`;

// 多行字符串(模板字符串的独特优势)
var html = `
  <div class="user-card">
    <h3>${name}</h3>
    <p>年龄:${age}</p>
    <p>城市:${city}</p>
  </div>
`;

// 传统多行字符串(麻烦)
var html2 = '<div class="user-card">\n' +
            '  <h3>' + name + '</h3>\n' +
            '  <p>年龄:' + age + '</p>\n' +
            '</div>';

五、字符串常用方法速查

javascript 复制代码
var str = 'Hello, JavaScript!';

// 长度
str.length           // 18

// 大小写
str.toUpperCase()    // 'HELLO, JAVASCRIPT!'
str.toLowerCase()    // 'hello, javascript!'

// 查找
str.indexOf('Java')        // 7(返回索引,找不到返回-1)
str.lastIndexOf('a')       // 15(从后往前找)
str.includes('Java')       // true(ES6,推荐)
str.startsWith('Hello')    // true(ES6)
str.endsWith('!')          // true(ES6)

// 截取
str.slice(0, 5)            // 'Hello'
str.slice(-1)              // '!'(负数从末尾算)
str.substring(7, 17)       // 'JavaScript'

// 分割与合并
'a,b,c'.split(',')         // ['a', 'b', 'c']
['a', 'b', 'c'].join('-')  // 'a-b-c'

// 去空格
'  hello  '.trim()         // 'hello'
'  hello  '.trimStart()    // 'hello  '(ES2019)
'  hello  '.trimEnd()      // '  hello'(ES2019)

// 替换
str.replace('Hello', 'Hi')            // 'Hi, JavaScript!'
str.replace(/javascript/gi, 'JS')     // 正则替换(全局、忽略大小写)
str.replaceAll('l', 'L')              // 'HeLLo, JavaScriptL!'(ES2021)

// 填充(ES2017)
'5'.padStart(3, '0')       // '005'
'5'.padEnd(3, '0')         // '500'

// 重复
'ab'.repeat(3)             // 'ababab'(ES6)

六、字符串比较(、=、localeCompare)

javascript 复制代码
// 严格比较(推荐)
'hello' === 'hello'    // true
'hello' === 'HELLO'    // false(区分大小写)

// 不区分大小写比较
'hello'.toLowerCase() === 'HELLO'.toLowerCase()  // true

// 字典顺序比较
'apple' < 'banana'    // true(按字母顺序)
'b' > 'a'             // true

// localeCompare(支持中文排序)
['张三', '李四', '王五'].sort((a, b) => a.localeCompare(b, 'zh-CN'));
// 按中文拼音排序:['李四', '王五', '张三']

七、⚠️ 常见陷阱与注意事项

javascript 复制代码
// 陷阱1:字符串 + 数字
'10' + 5     // '105'(字符串拼接,不是数学加法!)
10 + '5'     // '105'
'10' - 5     // 5(减号会尝试转换为数字)
'10' * 2     // 20(乘号会尝试转换为数字)

// 陷阱2:字符串比较
'10' > '9'   // false!(字符串按字典序,'1' < '9')
10 > 9       // true(数字比较)

// 陷阱3:访问越界的索引
'hello'[10]  // undefined(不报错)
'hello'.charAt(10)  // ''(空字符串)

// 陷阱4:字符串转数字
Number('123')    // 123 ✅
Number('12.3')   // 12.3 ✅
Number('')       // 0(空字符串转为0!)
Number(' ')      // 0(空白字符串也是0!)
Number('abc')    // NaN

parseInt('123px')   // 123(解析到遇到非数字停止)
parseInt('px123')   // NaN

八、真实应用场景

javascript 复制代码
// 1. 表单验证
function validateEmail(email) {
    return email.includes('@') && email.includes('.');
}

// 2. 手机号脱敏(中间4位隐藏)
function maskPhone(phone) {
    return phone.slice(0, 3) + '****' + phone.slice(-4);
}
maskPhone('13812345678');  // '138****5678'

// 3. URL 参数解析
function getURLParam(url, key) {
    const params = new URLSearchParams(url.split('?')[1]);
    return params.get(key);
}

// 4. 驼峰转连字符
function camelToKebab(str) {
    return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
camelToKebab('borderRadius');  // 'border-radius'

// 5. 模板生成 HTML
function renderProductCard(product) {
    return `
        <div class="card" data-id="${product.id}">
            <h3>${product.name}</h3>
            <span class="price">¥${product.price.toFixed(2)}</span>
        </div>
    `;
}

5.5 boolean 布尔类型

名词解释:布尔(Boolean)

布尔类型只有两个值:true(真)和 false(假),用于逻辑判断。名字来自英国数学家 George Boole,他创立了布尔代数(Boolean Algebra)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>boolean 布尔类型完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 16px; }
        .card { background: #f8f9fa; border-radius: 8px; padding: 16px; }
        .true-card { border-left: 4px solid #27ae60; }
        .false-card { border-left: 4px solid #e74c3c; }
        .truth-table { border-collapse: collapse; width: 100%; margin-top: 16px; }
        .truth-table th, .truth-table td { border: 1px solid #ddd; padding: 8px 12px; text-align: center; font-size: 14px; }
        .truth-table th { background: #2c3e50; color: white; }
        .t { color: #27ae60; font-weight: bold; }
        .f { color: #e74c3c; font-weight: bold; }
    </style>
</head>
<body>
    <h2>boolean 布尔类型</h2>
    
    <div class="grid">
        <div class="card true-card">
            <h3 style="color: #27ae60;">true --- 表示"是/肯定/真"</h3>
            <ul style="margin-top: 10px; font-size: 14px; line-height: 2;">
                <li>用户是否已登录:<code>var isLoggedIn = true</code></li>
                <li>表单是否验证通过:<code>var isValid = true</code></li>
                <li>商品是否有库存:<code>var inStock = true</code></li>
                <li>开关是否打开:<code>var isOpen = true</code></li>
            </ul>
        </div>
        <div class="card false-card">
            <h3 style="color: #e74c3c;">false --- 表示"否/否定/假"</h3>
            <ul style="margin-top: 10px; font-size: 14px; line-height: 2;">
                <li>用户未登录:<code>var isLoggedIn = false</code></li>
                <li>表单验证失败:<code>var isValid = false</code></li>
                <li>商品缺货:<code>var inStock = false</code></li>
                <li>开关关闭:<code>var isOpen = false</code></li>
            </ul>
        </div>
    </div>
    
    <h3 style="margin-top: 20px;">Falsy 值(转为布尔时为 false 的值)</h3>
    <p style="font-size: 14px; color: #666;">以下 6 个值转为布尔值时为 <strong>false</strong>,其他所有值均为 <strong>true</strong>:</p>
    
    <table class="truth-table">
        <tr>
            <th>值</th>
            <th>类型</th>
            <th>Boolean(value)</th>
            <th>说明</th>
        </tr>
        <tr><td><code>false</code></td><td>boolean</td><td class="f">false</td><td>布尔假值本身</td></tr>
        <tr><td><code>0</code></td><td>number</td><td class="f">false</td><td>数字零</td></tr>
        <tr><td><code>-0</code></td><td>number</td><td class="f">false</td><td>负零</td></tr>
        <tr><td><code>""</code> 或 <code>''</code></td><td>string</td><td class="f">false</td><td>空字符串</td></tr>
        <tr><td><code>null</code></td><td>null</td><td class="f">false</td><td>空值</td></tr>
        <tr><td><code>undefined</code></td><td>undefined</td><td class="f">false</td><td>未定义</td></tr>
        <tr><td><code>NaN</code></td><td>number</td><td class="f">false</td><td>非数字</td></tr>
        <tr><td><code>"0"</code></td><td>string</td><td class="t">true</td><td>非空字符串(注意!)</td></tr>
        <tr><td><code>[]</code></td><td>object</td><td class="t">true</td><td>空数组(注意!)</td></tr>
        <tr><td><code>{}</code></td><td>object</td><td class="t">true</td><td>空对象(注意!)</td></tr>
    </table>
    
    <script>
        console.log(Boolean(false));     // false
        console.log(Boolean(0));         // false
        console.log(Boolean(''));        // false
        console.log(Boolean(null));      // false
        console.log(Boolean(undefined)); // false
        console.log(Boolean(NaN));       // false
        
        console.log(Boolean('0'));       // true ← 注意!非空字符串
        console.log(Boolean([]));        // true ← 注意!空数组
        console.log(Boolean({}));        // true ← 注意!空对象
        console.log(Boolean(1));         // true
        console.log(Boolean('hello'));   // true
    </script>
</body>
</html>
💡 代码解释:boolean 布尔类型核心要点

一、布尔值的本质

javascript 复制代码
// 只有两个值
true   // 真、是、1
false  // 假、否、0

// 布尔值参与数学运算会转为数字
true + 1   // 2  (true → 1)
false + 1  // 1  (false → 0)
true * 10  // 10

二、Falsy 值(转布尔为 false 的值)完整记忆

这是 JavaScript 最重要的知识点之一,共 7个 Falsy 值

javascript 复制代码
// 7个 Falsy 值
Boolean(false)     // false --- 布尔 false 本身
Boolean(0)         // false --- 数字零
Boolean(-0)        // false --- 负零(少见)
Boolean(0n)        // false --- BigInt 零(ES2020)
Boolean('')        // false --- 空字符串(长度为0)
Boolean(null)      // false --- 空值
Boolean(undefined) // false --- 未定义
Boolean(NaN)       // false --- 非数字

// 其他所有值都是 Truthy(转布尔为 true)
Boolean('0')       // true ← ⚠️ 非空字符串!
Boolean(' ')       // true ← ⚠️ 含空格的字符串!
Boolean([])        // true ← ⚠️ 空数组!
Boolean({})        // true ← ⚠️ 空对象!
Boolean(-1)        // true ← 负数也是 truthy
Boolean(Infinity)  // true

记忆口诀: false、0、''、null、undefined、NaN 这6类是 falsy,其余都是 truthy。

三、布尔运算符详解

javascript 复制代码
// && 逻辑与:两者都为真才为真
true && true    // true
true && false   // false
false && true   // false
false && false  // false

// || 逻辑或:任一为真则为真
true || true    // true
true || false   // true
false || true   // true
false || false  // false

// ! 逻辑非:取反
!true   // false
!false  // true
!!''    // false(双重取反,转为布尔值)
!!'JS'  // true

四、短路求值(Short-Circuit Evaluation)

这是 JavaScript 中非常实用的特性:

javascript 复制代码
// && 短路:左边为 falsy 则返回左边,否则返回右边
false && '不执行'  // false(左边是 falsy,不求值右边)
0 && '不执行'      // 0
'' && '不执行'     // ''
'hello' && 'world' // 'world'(左边是 truthy,返回右边)

// || 短路:左边为 truthy 则返回左边,否则返回右边
true || '不执行'   // true
'hello' || '默认值' // 'hello'(左边是 truthy,不求值右边)
'' || '默认值'      // '默认值'(左边是 falsy,返回右边)
null || '默认值'    // '默认值'

实际应用(非常常见):

javascript 复制代码
// 1. 默认值模式
var name = userInput || '匿名用户';
// 如果 userInput 为空字符串、null、undefined 等 falsy 值,
// 则 name = '匿名用户'

// 2. 条件渲染(React 中常用)
var isLoggedIn = true;
var element = isLoggedIn && '<div>欢迎回来!</div>';
// isLoggedIn 为 true 时,element = '<div>欢迎回来!</div>'
// isLoggedIn 为 false 时,element = false

// 3. 防御性编程
var user = { profile: { name: '张三' } };
var name = user && user.profile && user.profile.name;
// 避免 Cannot read properties of undefined

// 4. ES2020 可选链(更现代)
var name2 = user?.profile?.name;  // 与上面等价

五、比较运算符返回布尔值

javascript 复制代码
// 所有比较运算符都返回布尔值
5 > 3         // true
5 < 3         // false
5 >= 5        // true
5 <= 4        // false
5 == '5'      // true(宽松比较,会类型转换)
5 === '5'     // false(严格比较,类型不同)
5 != '5'      // false(宽松不等)
5 !== '5'     // true(严格不等,推荐使用)

⚠️ == vs === 的区别(面试必考):

javascript 复制代码
// == 宽松相等:会进行类型转换
0 == false     // true(false 转 0)
'' == false    // true('' 转 false,false 转 0,'' 转 0)
null == undefined  // true
1 == '1'       // true('1' 转数字 1)
[] == false    // true([] 转 '' 转 0,false 转 0)

// === 严格相等:不转换类型(推荐使用!)
0 === false    // false(类型不同)
'' === false   // false(类型不同)
null === undefined  // false(类型不同)
1 === '1'      // false(类型不同)

六、布尔值在条件语句中的应用

javascript 复制代码
// if 语句会自动将条件转为布尔值
var user = null;
if (user) {
    console.log('用户存在');
} else {
    console.log('用户不存在');  // ← 执行这里,因为 null 是 falsy
}

// 利用 falsy 检查
var arr = [];
if (arr.length) {           // arr.length 为 0,是 falsy
    console.log('有数据');
} else {
    console.log('没有数据');  // ← 执行这里
}

// 字符串非空检查
var str = '';
if (!str) {
    console.log('字符串为空');  // ← 执行
}

七、⚠️ 布尔类型常见陷阱

javascript 复制代码
// 陷阱1:空数组和空对象是 truthy!
if ([]) { console.log('空数组也是 truthy!'); }  // 会执行
if ({}) { console.log('空对象也是 truthy!'); }  // 会执行

// 正确检查数组是否为空
if (arr.length === 0) { ... }
if (arr.length) { ... }  // 等价,利用 truthy/falsy

// 陷阱2:字符串 '0' 和 'false' 是 truthy!
Boolean('0')       // true(不是 false!)
Boolean('false')   // true(字符串 'false' 也是 truthy!)

// 陷阱3:== 比较中的隐式转换
null == 0          // false(null 只和 null/undefined 宽松相等)
null == false      // false
undefined == false // false

八、真实应用场景

javascript 复制代码
// 1. 表单验证
function validateForm(data) {
    var errors = {};
    
    if (!data.username) {
        errors.username = '用户名不能为空';
    }
    if (!data.password || data.password.length < 6) {
        errors.password = '密码至少6位';
    }
    if (!data.email || !data.email.includes('@')) {
        errors.email = '邮箱格式不正确';
    }
    
    return Object.keys(errors).length === 0 ? null : errors;
}

// 2. 权限控制
var user = { role: 'admin', isActive: true };
var canEdit = user.role === 'admin' && user.isActive;
var canDelete = user.role === 'admin' && user.isActive && user.isSuperAdmin;

// 3. 开关状态
var isDarkMode = false;
function toggleDarkMode() {
    isDarkMode = !isDarkMode;  // 切换布尔值
    document.body.classList.toggle('dark', isDarkMode);
}

// 4. 加载状态
var isLoading = false;
async function fetchData() {
    isLoading = true;
    renderLoadingSpinner(isLoading);
    try {
        var data = await fetch('/api/data').then(r => r.json());
        renderData(data);
    } finally {
        isLoading = false;
        renderLoadingSpinner(isLoading);
    }
}

5.6 null 与 undefined

名词解释
类型 含义 typeof 使用场景
null null 空,有意为之的"无值" "object" ⚠️ 初始化一个将来会赋值的变量,表示"暂无"
undefined undefined 未定义,意外的"无值" "undefined" 变量声明未赋值;函数无返回值;对象不存在的属性
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>null 与 undefined 完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .compare { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
        .null-card { background: #fef9e7; border: 2px solid #f39c12; border-radius: 8px; padding: 16px; }
        .undef-card { background: #f2f3f4; border: 2px solid #7f8c8d; border-radius: 8px; padding: 16px; }
        pre { background: #282c34; color: #abb2bf; padding: 14px; border-radius: 6px; font-size: 13px; margin-top: 10px; }
        .kw { color: #c678dd; }
        .str { color: #98c379; }
        .cmt { color: #5c6370; }
        .num { color: #d19a66; }
        .warn-box { background: #ffeaea; border: 1px solid #e74c3c; border-radius: 6px; padding: 12px; margin-top: 16px; font-size: 14px; }
    </style>
</head>
<body>
    <h2>null 与 undefined</h2>
    
    <div class="compare">
        <div class="null-card">
            <h3 style="color: #e67e22;">null --- 主动赋予的空值</h3>
            <p style="font-size: 14px; margin-top: 8px;">表示"此处应该有值,但现在故意设为空"。是一种<strong>明确的空值声明</strong>。</p>
            <pre><span class="cmt">// 场景1:初始化,暂时不知道赋什么值</span>
<span class="kw">var</span> userInfo = <span class="num">null</span>;
<span class="cmt">// 等待从服务器获取数据后赋值</span>
userInfo = { name: <span class="str">'张三'</span>, age: 25 };

<span class="cmt">// 场景2:清空一个变量</span>
<span class="kw">var</span> timer = setInterval(fn, 1000);
clearInterval(timer);
timer = <span class="num">null</span>;  <span class="cmt">// 清空引用,方便 GC 回收</span>

<span class="cmt">// typeof null 的历史 Bug</span>
typeof <span class="num">null</span>;    <span class="cmt">// "object" ← 注意!</span>
<span class="num">null</span> === <span class="num">null</span>;  <span class="cmt">// true ← 用 === 判断</span></pre>
        </div>
        
        <div class="undef-card">
            <h3 style="color: #7f8c8d;">undefined --- 无意中的未定义</h3>
            <p style="font-size: 14px; margin-top: 8px;">表示"此处本应有值,但还没有被赋值"。通常是<strong>非预期的状态</strong>。</p>
            <pre><span class="cmt">// 场景1:变量声明但未赋值</span>
<span class="kw">var</span> x;
console.log(x);          <span class="cmt">// undefined</span>

<span class="cmt">// 场景2:函数没有 return 语句</span>
<span class="kw">function</span> doNothing() {}
console.log(doNothing()); <span class="cmt">// undefined</span>

<span class="cmt">// 场景3:访问对象不存在的属性</span>
<span class="kw">var</span> obj = { name: <span class="str">'JS'</span> };
console.log(obj.age);    <span class="cmt">// undefined</span>

<span class="cmt">// 场景4:函数参数未传</span>
<span class="kw">function</span> greet(name) {
    console.log(name);   <span class="cmt">// undefined</span>
}
greet();                 <span class="cmt">// 不传参数</span></pre>
        </div>
    </div>
    
    <div class="warn-box">
        <strong>⚠️ null vs undefined 关键区别:</strong>
        <pre style="background: transparent; padding: 8px 0; margin: 0; color: inherit;">
null == undefined    → true   (宽松相等,JS 认为它们相似)
null === undefined   → false  (严格相等,类型不同)
null + 1             → 1      (null 转 number 为 0)
undefined + 1        → NaN    (undefined 转 number 为 NaN)</pre>
    </div>
    
    <script>
        // null
        var address = null;
        console.log(address);          // null
        console.log(typeof address);   // "object"
        
        // undefined
        var phoneNum;
        console.log(phoneNum);         // undefined
        console.log(typeof phoneNum);  // "undefined"
        
        // 比较
        console.log(null == undefined);   // true
        console.log(null === undefined);  // false
        console.log(null + 1);            // 1
        console.log(undefined + 1);       // NaN
    </script>
</body>
</html>
💡 代码解释:null 与 undefined 核心要点

一、两者的本质区别(一句话概括)

null undefined
语义 有意为之的"空",表示"我知道这里没有值" 无意的"未定义",表示"还没有被赋值"
来源 由程序员主动赋值 由 JavaScript 引擎自动产生
类比 空杯子(知道这是杯子,但里面是空的) 没有杯子(连杯子都不存在)

二、undefined 产生的四种场景

javascript 复制代码
// 场景1:变量声明但未赋值
var x;
console.log(x);           // undefined

// 场景2:函数没有 return 语句
function doNothing() {}
console.log(doNothing()); // undefined

// 场景3:访问对象不存在的属性
var obj = { name: 'JS' };
console.log(obj.age);     // undefined(不是报错)
console.log(obj.foo);     // undefined

// 场景4:函数参数未传值
function greet(name) {
    console.log(name);    // undefined(没有传参)
}
greet();  // 调用时不传参数

三、null 的主动使用场景

javascript 复制代码
// 场景1:初始化一个将来要赋值的变量
var currentUser = null;   // 表示"还没有用户,稍后从服务器获取"
// 用户登录后:
currentUser = { name: '张三', id: 123 };

// 场景2:清空变量,释放引用(帮助垃圾回收)
var largeObject = { /* 大量数据 */ };
// 使用完毕
largeObject = null;  // 让 GC 回收内存

// 场景3:表示"查找无结果"
function findUser(id) {
    // 找到了
    if (id === 1) return { name: '张三' };
    // 没找到
    return null;  // 明确表示"找了但没找到",不返回 undefined
}

var user = findUser(999);
if (user === null) {
    console.log('用户不存在');
}

四、== 与 === 判断差异(重要)

javascript 复制代码
null == undefined    // true(它们被认为是"相似的空值")
null === undefined   // false(类型不同:null vs undefined)

// 利用 == null 同时检查 null 和 undefined(常用技巧)
var value = null;
if (value == null) {           // 同时捕获 null 和 undefined
    console.log('值为空');
}

// 等价于:
if (value === null || value === undefined) {
    console.log('值为空');
}

// 现代写法(ES2020 空值合并运算符)
var result = value ?? '默认值';
// 只有 null 或 undefined 时才使用默认值
// (与 || 不同:|| 对所有 falsy 值都用默认值)

五、数值转换对比

javascript 复制代码
// null 转数值:0
null + 1        // 1(null → 0)
Number(null)    // 0
+null           // 0

// undefined 转数值:NaN
undefined + 1   // NaN
Number(undefined) // NaN
+undefined      // NaN

// 重要区别
null + 1        // 1(可以参与加法)
undefined + 1   // NaN(结果是 NaN)

六、可选链操作符(?.)--- 解决 undefined 报错

在访问可能为 null/undefined 的属性时,旧代码需要冗长的判断:

javascript 复制代码
// ❌ 旧方式(冗长)
var street;
if (user && user.address && user.address.street) {
    street = user.address.street;
}

// ✅ 可选链(ES2020,简洁)
var street = user?.address?.street;
// 如果 user 是 null/undefined,返回 undefined 而不报错
// 如果 user.address 是 null/undefined,返回 undefined
// 否则返回 user.address.street 的值

// 还可以用于函数调用
user?.getAddress?.();  // 如果 getAddress 不存在,不调用也不报错
arr?.[0]               // 安全访问数组元素

七、空值合并运算符(??)

javascript 复制代码
// ?? 与 || 的区别:
// || :左边是 falsy 值时使用右边(包括 0、''、false)
// ?? :左边是 null 或 undefined 时才使用右边

var count = 0;
count || 10   // 10(因为 0 是 falsy)
count ?? 10   // 0  (0 不是 null/undefined,所以保留 0)

var name = '';
name || '匿名' // '匿名'('' 是 falsy)
name ?? '匿名' // ''('' 不是 null/undefined,所以保留 '')

什么时候用 ?? 而不是 ||:

javascript 复制代码
// 场景:用户设置了"每页显示0条"(pageSize = 0是合法的)
var pageSize = userSettings.pageSize ?? 20;
// 如果用 ||,pageSize = 0 时会错误地替换为 20!
// 用 ?? 更安全:只有 null/undefined 时才使用默认值

八、⚠️ 常见误区总结

javascript 复制代码
// 误区1:用 typeof 判断 null
typeof null === 'object'  // true(历史Bug)
// ✅ 正确判断 null:
value === null

// 误区2:认为 undefined 会报错
var obj = {};
obj.foo  // undefined(不报错)
obj.foo.bar  // ❌ TypeError: Cannot read properties of undefined

// 误区3:null 和 undefined 都是"没有值"就不分了
var a = null;
var b = undefined;
a == b   // true(宽松相等)
a === b  // false(严格不等)

九、实际编程规范建议

javascript 复制代码
// ✅ 推荐:用 null 明确表示"空值"
var selectedItem = null;  // 表示还没选择任何项目

// ✅ 推荐:用 === null 判断是否为 null
if (selectedItem === null) { ... }

// ✅ 推荐:用 == null 同时判断 null 和 undefined
if (selectedItem == null) { ... }

// ✅ 推荐:避免手动赋值 undefined
// 让 JavaScript 自动产生 undefined 即可
var x;         // ✅ 声明但不赋值
var y = null;  // ✅ 明确表示"空",不要写 var y = undefined

// ✅ 推荐:函数无结果时返回 null(不是 undefined)
function findItem(id) {
    var found = items.filter(item => item.id === id)[0];
    return found || null;  // 明确返回 null 表示未找到
}

六、知识总结与思维导图

Day01 全部知识点总览

Day01 知识体系
CSS响应式布局
Viewport视口
width=device-width
initial-scale=1.0
媒体查询
min-width 移动优先
max-width 桌面优先
Flexbox弹性布局
display flex
flex-wrap
justify-content
响应式图片
picture + source
img srcset
BFC块级格式上下文
创建方式
overflow hidden
display flow-root
float
position absolute/fixed
解决问题
高度塌陷 清除浮动
外边距塌陷
JavaScript基础
JS概述
动态弱类型解释型
ECMAScript+BOM+DOM
V8引擎 Node.js
引入方式
行内式 不推荐
内嵌式 可用
外链式 推荐
基本语法
注释 单行多行
分号或换行结束
严格区分大小写
输出方法
alert 弹框
document.write 页面
console.log 控制台
变量
var 声明
小驼峰命名
直接量vs变量
数据类型
原始类型
number 数值
string 字符串
boolean 布尔
null 空
undefined 未定义
对象类型
array function object
typeof运算符

CSS 响应式布局常用断点总结

0px
<640px

手机端

单列布局
640px+

平板端

双列布局
1024px+

桌面端

多列布局
1440px+

大屏端

超宽布局

JavaScript 数据类型速查

数据类型判断
typeof value
结果
'number'

数字/NaN/Infinity
'string'

字符串
'boolean'

true/false
'undefined'

未定义
'object'

null/对象/数组
'function'

函数
value === null

判断是否为null
Array.isArray(value)

判断是否为数组


七、经典作业解析

作业一:交换两个变量的值

js 复制代码
var a = 100;
var b = 200;

题目要求: 交换 a 和 b 的值,使 a = 200,b = 100。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>变量交换 - 多种方案</title>
    <style>
        body { font-family: Arial; padding: 20px; background: #f0f4f8; }
        .solution {
            background: white;
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 16px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
        }
        h3 { color: #2c3e50; }
        pre {
            background: #282c34;
            color: #abb2bf;
            padding: 16px;
            border-radius: 6px;
            font-size: 14px;
            line-height: 1.7;
        }
        .kw { color: #c678dd; }
        .num { color: #d19a66; }
        .cmt { color: #5c6370; font-style: italic; }
        .var-name { color: #e06c75; }
        .best { border-left: 5px solid #27ae60; }
        .result-box { background: #e8f8f5; border-radius: 6px; padding: 10px 14px; font-family: monospace; font-size: 14px; margin-top: 10px; }
    </style>
</head>
<body>
    <h2>变量交换的三种方案</h2>
    
    <div class="solution best">
        <h3>✅ 方案一:临时变量(最通用,必须掌握)</h3>
        <pre><span class="kw">var</span> <span class="var-name">a</span> = <span class="num">100</span>;
<span class="kw">var</span> <span class="var-name">b</span> = <span class="num">200</span>;

<span class="cmt">// 第一步:把 a 的值先"备份"到 temp</span>
<span class="kw">var</span> <span class="var-name">temp</span> = <span class="var-name">a</span>;   <span class="cmt">// temp = 100, a = 100, b = 200</span>

<span class="cmt">// 第二步:把 b 的值赋给 a</span>
<span class="var-name">a</span> = <span class="var-name">b</span>;        <span class="cmt">// temp = 100, a = 200, b = 200</span>

<span class="cmt">// 第三步:把 temp(原来 a 的值)赋给 b</span>
<span class="var-name">b</span> = <span class="var-name">temp</span>;    <span class="cmt">// temp = 100, a = 200, b = 100 ✅</span>

console.log(<span class="var-name">a</span>);  <span class="cmt">// 200</span>
console.log(<span class="var-name">b</span>);  <span class="cmt">// 100</span></pre>
        <div class="result-box" id="r1">计算中...</div>
    </div>
    
    <div class="solution">
        <h3>方案二:算术运算(不需要临时变量,仅适用于数字)</h3>
        <pre><span class="kw">var</span> <span class="var-name">a</span> = <span class="num">100</span>, <span class="var-name">b</span> = <span class="num">200</span>;

<span class="var-name">a</span> = <span class="var-name">a</span> + <span class="var-name">b</span>;   <span class="cmt">// a = 300</span>
<span class="var-name">b</span> = <span class="var-name">a</span> - <span class="var-name">b</span>;   <span class="cmt">// b = 300 - 200 = 100</span>
<span class="var-name">a</span> = <span class="var-name">a</span> - <span class="var-name">b</span>;   <span class="cmt">// a = 300 - 100 = 200</span>

console.log(<span class="var-name">a</span>);  <span class="cmt">// 200</span>
console.log(<span class="var-name">b</span>);  <span class="cmt">// 100</span></pre>
        <div class="result-box" id="r2">计算中...</div>
    </div>
    
    <div class="solution">
        <h3>方案三:ES6 解构赋值(最优雅,现代JS推荐)</h3>
        <pre><span class="kw">var</span> <span class="var-name">a</span> = <span class="num">100</span>, <span class="var-name">b</span> = <span class="num">200</span>;

<span class="cmt">// 一行代码完成交换!</span>
[<span class="var-name">a</span>, <span class="var-name">b</span>] = [<span class="var-name">b</span>, <span class="var-name">a</span>];

console.log(<span class="var-name">a</span>);  <span class="cmt">// 200</span>
console.log(<span class="var-name">b</span>);  <span class="cmt">// 100</span></pre>
        <div class="result-box" id="r3">计算中...</div>
    </div>

    <script>
        // 方案一:临时变量
        var a1 = 100, b1 = 200;
        var temp = a1;
        a1 = b1;
        b1 = temp;
        document.getElementById('r1').textContent = '结果:a = ' + a1 + ', b = ' + b1 + '  ✅';

        // 方案二:算术
        var a2 = 100, b2 = 200;
        a2 = a2 + b2;
        b2 = a2 - b2;
        a2 = a2 - b2;
        document.getElementById('r2').textContent = '结果:a = ' + a2 + ', b = ' + b2 + '  ✅';

        // 方案三:解构(ES6)
        var a3 = 100, b3 = 200;
        [a3, b3] = [b3, a3];
        document.getElementById('r3').textContent = '结果:a = ' + a3 + ', b = ' + b3 + '  ✅';
    </script>
</body>
</html>
💡 代码解释:变量交换三种方案对比

一、方案一:临时变量法(必须掌握的基础解法)

javascript 复制代码
var a = 100, b = 200;

// 分步骤理解:想象将两杯水互相倒换,需要第三个杯子
var temp = a;   // 第三个杯子装 a 的水(temp=100, a=100, b=200)
a = b;          // a 的杯子倒 b 的水(temp=100, a=200, b=200)
b = temp;       // b 的杯子倒 temp 的水(temp=100, a=200, b=100)✅

状态跟踪表(面试常用):

步骤 temp a b
初始 --- 100 200
var temp = a 100 100 200
a = b 100 200 200
b = temp 100 200 100
结果 --- 200 100

二、方案二:算术运算法(仅适用于数字)

javascript 复制代码
var a = 100, b = 200;

a = a + b;   // a = 300(包含两者之和)
b = a - b;   // b = 300 - 200 = 100(用和减去原b,得到原a)
a = a - b;   // a = 300 - 100 = 200(用和减去新b,得到原b)

原理分析:

复制代码
设原始值 a₀ = 100, b₀ = 200

步骤1:a = a₀ + b₀ = 300
步骤2:b = (a₀ + b₀) - b₀ = a₀ = 100  ✅
步骤3:a = (a₀ + b₀) - a₀ = b₀ = 200  ✅

⚠️ 局限性:

  • 仅限数字,字符串、对象无法使用
  • 超大数字可能溢出(Number.MAX_SAFE_INTEGER)

三、方案三:ES6 解构赋值(现代最优写法)

javascript 复制代码
let a = 100, b = 200;

// 一行代码完成!
[a, b] = [b, a];

// 原理:
// 右边 [b, a] 先创建新数组 [200, 100]
// 左边 [a, b] 解构这个数组:a = 200, b = 100

扩展:解构赋值的其他用途

javascript 复制代码
// 1. 交换多个变量
let x = 1, y = 2, z = 3;
[x, y, z] = [z, x, y];  // x=3, y=1, z=2

// 2. 函数返回多个值
function getMinMax(arr) {
    return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([5, 3, 8, 1, 9]);  // min=1, max=9

// 3. 获取数组元素
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

四、三种方案综合对比

方案 代码行数 适用类型 兼容性 推荐度
临时变量 3行 所有类型 所有环境 ⭐⭐⭐⭐(基础必会)
算术运算 3行 仅数字 所有环境 ⭐⭐(了解即可)
解构赋值 1行 所有类型 ES6+ ⭐⭐⭐⭐⭐(现代推荐)

五、异或运算法(面试进阶)

javascript 复制代码
// 使用位运算异或(XOR)交换,不需要额外变量
let a = 100, b = 200;

a = a ^ b;  // a = 100 ^ 200
b = a ^ b;  // b = (100 ^ 200) ^ 200 = 100
a = a ^ b;  // a = (100 ^ 200) ^ 100 = 200

console.log(a, b);  // 200, 100 ✅

// 原理:a ^ b ^ b = a(异或两次同一个数等于原值)
// ⚠️ 局限:仅适用于整数

附录:推荐学习资源

资源 链接 说明
MDN Web Docs(官方) https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 最权威的 JS 参考文档
W3C CSS 规范 https://www.w3.org/TR/CSS22/ CSS 官方规范
ECMAScript 规范 https://tc39.es/ecma262/ JS 语言规范(高级)
Can I Use https://caniuse.com/ 浏览器兼容性查询
JavaScript.info https://zh.javascript.info/ 现代 JS 教程(免费、完整)
CSS-Tricks https://css-tricks.com/ CSS 技巧与响应式布局


八、ES6+ 进阶:let、const 与变量提升

8.1 var 的局限性与历史问题

根据 MDN 官方文档和 DigitalOcean 技术文章,var 存在以下问题:

var 的三大问题

var 的问题
函数作用域

无块级作用域
变量提升

Hoisting
可重复声明

易出错
if/for中声明的变量

外部也可访问
声明提升到顶部

初始化前访问返回undefined
同一作用域可多次声明

容易覆盖

完整示例:var 的问题演示
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>var 的问题演示</title>
    <style>
        body { font-family: Arial; padding: 20px; background: #f8f9fa; }
        .problem { background: #fff; border-left: 4px solid #e74c3c; padding: 16px; margin: 16px 0; border-radius: 6px; }
        h3 { color: #e74c3c; }
        pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.6; }
        .kw { color: #c678dd; }
        .cmt { color: #5c6370; }
        .str { color: #98c379; }
    </style>
</head>
<body>
    <h2>var 的三大问题</h2>

    <div class="problem">
        <h3>问题一:无块级作用域</h3>
        <pre><span class="cmt">// var 在 if/for 块中声明,外部也能访问</span>
<span class="kw">if</span> (<span class="kw">true</span>) {
    <span class="kw">var</span> x = <span class="str">'块内声明'</span>;
}
console.log(x);  <span class="cmt">// '块内声明' ← 外部居然能访问!</span>

<span class="kw">for</span> (<span class="kw">var</span> i = 0; i < 3; i++) {
    <span class="cmt">// 循环体</span>
}
console.log(i);  <span class="cmt">// 3 ← 循环变量泄露到外部!</span></pre>
    </div>

    <div class="problem">
        <h3>问题二:变量提升(Hoisting)导致的困惑</h3>
        <pre><span class="cmt">// 变量提升:声明被提升到作用域顶部</span>
console.log(name);  <span class="cmt">// undefined ← 不报错,但返回undefined</span>
<span class="kw">var</span> name = <span class="str">'JavaScript'</span>;

<span class="cmt">// 上述代码等价于(提升后):</span>
<span class="kw">var</span> name;  <span class="cmt">// 声明提升到顶部</span>
console.log(name);  <span class="cmt">// undefined</span>
name = <span class="str">'JavaScript'</span>;  <span class="cmt">// 赋值留在原地</span></pre>
    </div>

    <div class="problem">
        <h3>问题三:可重复声明,容易覆盖</h3>
        <pre><span class="kw">var</span> count = 10;
console.log(count);  <span class="cmt">// 10</span>

<span class="cmt">// 不小心重复声明,直接覆盖</span>
<span class="kw">var</span> count = 20;  <span class="cmt">// ✅ 不报错,直接覆盖</span>
console.log(count);  <span class="cmt">// 20 ← 原来的值丢失了</span></pre>
    </div>

    <script>
        // 实际运行示例
        if (true) {
            var x = '块内声明';
        }
        console.log('问题一:', x);

        console.log('问题二:', typeof name2);
        var name2 = 'JS';

        var count = 10;
        var count = 20;
        console.log('问题三:', count);
    </script>
</body>
</html>
💡 代码解释:var 的三大问题深度分析

问题一:无块级作用域 --- 变量泄露

javascript 复制代码
// var 的作用域是"函数",不是"块"({})
if (true) {
    var x = '块内声明';
    // 这里能访问 x ✅
}
console.log(x);  // '块内声明' ← 泄露到块外!

// for 循环变量泄露(经典问题)
for (var i = 0; i < 3; i++) {
    console.log(i);  // 0, 1, 2
}
console.log(i);  // 3 ← 循环结束后 i 还存在!

// 造成的真实问题:异步循环中的变量共享
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);  // 全部输出 3!(不是 0, 1, 2)
    }, 100);
}
// 原因:所有回调共享同一个 var i,循环完了才执行,此时 i = 3

// ✅ 用 let 解决:
for (let j = 0; j < 3; j++) {
    setTimeout(function() {
        console.log(j);  // 正确输出 0, 1, 2
    }, 100);
}

问题二:变量提升(Hoisting)--- 声明被"搬"到顶部

javascript 复制代码
// 写的代码:
console.log(name);  // 打印什么?
var name = 'JavaScript';

// JavaScript 引擎实际执行的(提升后):
var name;           // 声明提升到顶部,但不赋值
console.log(name);  // undefined(不是 'JavaScript',也不是报错)
name = 'JavaScript'; // 赋值留在原地

// 为什么说这是"问题"?
// → 允许在声明之前访问变量,容易造成逻辑混乱
// → 程序员以为会报错,但实际得到 undefined,难以定位 Bug

// 函数提升(整个函数被提升)
hello();  // '你好!' ← 可以在声明前调用!
function hello() {
    console.log('你好!');
}
// 函数声明整体提升,所以可以在声明前调用

// 函数表达式不会提升
// hi();  // TypeError: hi is not a function
var hi = function() { console.log('Hi!'); };

问题三:可重复声明 --- 意外覆盖

javascript 复制代码
var count = 10;

// ... 很多代码 ...

// 忘了已经声明过 count,再次声明
var count = 20;  // 不报错!直接覆盖了

console.log(count);  // 20(原来的 10 丢失了)

// 实际工程中的危害:
// 1. 大文件中重名变量难以发现
// 2. 多人协作时容易覆盖别人的变量
// 3. 引入第三方库时可能产生冲突

三大问题对比总结

问题 var 行为 影响 let/const 行为
作用域 函数作用域 变量泄露到块外 块级作用域
变量提升 提升且初始化为 undefined 可以在声明前访问 提升但有 TDZ,访问报错
重复声明 允许,静默覆盖 意外丢失值 禁止,报 SyntaxError

8.2 let 和 const:ES6 的解决方案

根据 ECMAScript 2026 规范MDN 官方文档 ,ES6 引入了 letconst 来解决 var 的问题。

let vs const vs var 对比表
特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 提升且初始化为undefined 提升但处于暂时性死区 提升但处于暂时性死区
可重复声明 ✅ 可以 ❌ 报错 ❌ 报错
可重新赋值 ✅ 可以 ✅ 可以 ❌ 不可以(常量)
声明时必须初始化 ❌ 不需要 ❌ 不需要 必须
最佳实践 ⚠️ 避免使用 ✅ 可变数据用 ✅✅ 优先使用
暂时性死区(Temporal Dead Zone, TDZ)

代码执行
进入块作用域
TDZ 开始
let/const 声明行
TDZ 结束
变量可用
访问变量 → ReferenceError
访问变量 → 正常使用

let 和 const 完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>let 和 const 完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .solution { background: #e8f8f5; border-left: 4px solid #27ae60; padding: 16px; margin: 16px 0; border-radius: 6px; }
        h3 { color: #27ae60; }
        pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.6; }
        .table { border-collapse: collapse; width: 100%; margin: 16px 0; }
        .table th, .table td { border: 1px solid #ddd; padding: 10px; font-size: 14px; }
        .table th { background: #27ae60; color: white; }
    </style>
</head>
<body>
    <h2>let 和 const:ES6 的解决方案</h2>

    <div class="solution">
        <h3>✅ let:块级作用域的可变变量</h3>
        <pre><span class="cmt">// 解决问题一:真正的块级作用域</span>
<span class="kw">if</span> (<span class="kw">true</span>) {
    <span class="kw">let</span> x = <span class="str">'块内'</span>;
    console.log(x);  <span class="cmt">// '块内'</span>
}
console.log(x);  <span class="cmt">// ReferenceError: x is not defined ✅</span>

<span class="cmt">// for 循环中的 let</span>
<span class="kw">for</span> (<span class="kw">let</span> i = 0; i < 3; i++) {
    console.log(i);  <span class="cmt">// 0, 1, 2</span>
}
console.log(i);  <span class="cmt">// ReferenceError ✅ 不会泄露</span>

<span class="cmt">// 解决问题二:暂时性死区(TDZ),访问未声明变量报错</span>
console.log(name);  <span class="cmt">// ReferenceError ✅</span>
<span class="kw">let</span> name = <span class="str">'ES6'</span>;

<span class="cmt">// 解决问题三:不能重复声明</span>
<span class="kw">let</span> count = 10;
<span class="kw">let</span> count = 20;  <span class="cmt">// SyntaxError ✅</span></pre>
    </div>

    <div class="solution">
        <h3>✅ const:块级作用域的常量</h3>
        <pre><span class="cmt">// 必须在声明时初始化</span>
<span class="kw">const</span> PI;  <span class="cmt">// SyntaxError: Missing initializer</span>
<span class="kw">const</span> PI = 3.14159;  <span class="cmt">// ✅ 正确</span>

<span class="cmt">// 不能重新赋值</span>
PI = 3.14;  <span class="cmt">// TypeError: Assignment to constant variable</span>

<span class="cmt">// ⚠️ 对象常量:引用不可变,但内容可变</span>
<span class="kw">const</span> user = { name: <span class="str">'张三'</span> };
user.name = <span class="str">'李四'</span>;  <span class="cmt">// ✅ 允许修改属性</span>
user.age = 25;       <span class="cmt">// ✅ 允许添加属性</span>
user = { name: <span class="str">'王五'</span> };  <span class="cmt">// ❌ TypeError: 不能重新赋值整个对象</span>

<span class="cmt">// 冻结对象:使用 Object.freeze()</span>
<span class="kw">const</span> config = Object.freeze({ api: <span class="str">'https://api.example.com'</span> });
config.api = <span class="str">'xxx'</span>;  <span class="cmt">// ❌ 严格模式下报错,非严格模式静默失败</span></pre>
    </div>

    <h3>经典使用场景对比</h3>
    <table class="table">
        <tr>
            <th>场景</th>
            <th>使用</th>
            <th>原因</th>
        </tr>
        <tr>
            <td>配置常量、API地址</td>
            <td><code>const API_URL = '...'</code></td>
            <td>不应被修改</td>
        </tr>
        <tr>
            <td>循环计数器</td>
            <td><code>for (let i = 0; ...)</code></td>
            <td>块级作用域,不泄露</td>
        </tr>
        <tr>
            <td>对象/数组</td>
            <td><code>const user = {}</code></td>
            <td>引用不变,内容可变</td>
        </tr>
        <tr>
            <td>普通变量</td>
            <td><code>let count = 0</code></td>
            <td>需要重新赋值</td>
        </tr>
        <tr>
            <td>函数参数</td>
            <td><code>function fn(value)</code></td>
            <td>自动为局部变量</td>
        </tr>
    </table>
</body>
</html>
💡 代码解释:let 和 const 核心要点

一、let 解决了 var 的三大问题

javascript 复制代码
// 解决1:块级作用域
{
    let blockVar = '块内变量';
    console.log(blockVar);  // ✅ 块内可以访问
}
// console.log(blockVar);  // ❌ ReferenceError:块外无法访问

// 解决2:暂时性死区(TDZ)--- 声明前访问报错
// console.log(name);  // ❌ ReferenceError(不再是 undefined)
let name = 'ES6';
console.log(name);     // ✅ 'ES6'

// 解决3:不能重复声明
let count = 10;
// let count = 20;  // ❌ SyntaxError: Identifier 'count' already been declared
count = 20;         // ✅ 可以重新赋值(只是不能再次 let)

二、const 的完整规则

javascript 复制代码
// 规则1:必须在声明时初始化
// const PI;       // ❌ SyntaxError: Missing initializer
const PI = 3.14159;  // ✅

// 规则2:不能重新赋值(针对变量绑定)
// PI = 3.14;      // ❌ TypeError: Assignment to constant variable

// 规则3:const 保护的是"引用",不是"值"
// 对象/数组的内容可以改变!
const user = { name: '张三', age: 25 };
user.name = '李四';   // ✅ 修改属性允许
user.city = '北京';   // ✅ 添加属性允许
// user = { name: '王五' };  // ❌ 不能重新赋值整个对象

const arr = [1, 2, 3];
arr.push(4);          // ✅ 修改数组内容允许
arr[0] = 100;         // ✅ 修改元素允许
// arr = [4, 5, 6];   // ❌ 不能重新赋值整个数组

三、暂时性死区(TDZ)深度理解

javascript 复制代码
// TDZ(Temporal Dead Zone)= 从进入作用域到声明语句之间的区域
{
    // ← TDZ 开始(let name 还没执行)
    console.log(name);  // ❌ ReferenceError(在 TDZ 中)
    let name = 'ES6';   // ← TDZ 结束,name 变量可用
    console.log(name);  // ✅ 'ES6'
}

// TDZ 的设计目的:
// 避免 var 的"声明前访问得到 undefined"的困惑
// 让代码行为更可预测,更容易发现 Bug

四、const 冻结对象(完全不可变)

javascript 复制代码
// 普通 const:对象内容可变
const config = { theme: 'dark', lang: 'zh' };
config.theme = 'light';  // ✅ 可以修改

// Object.freeze():冻结对象,内容不可变
const frozenConfig = Object.freeze({ theme: 'dark', lang: 'zh' });
frozenConfig.theme = 'light';  // ❌(严格模式报错,非严格模式静默失败)
console.log(frozenConfig.theme);  // 'dark'(没有改变)

// ⚠️ freeze 是浅冻结,嵌套对象仍然可变
const obj = Object.freeze({ nested: { value: 1 } });
obj.nested.value = 2;  // ✅(嵌套对象未被冻结)

五、let/const 最佳使用原则

javascript 复制代码
// 原则1:默认用 const,需要改变才用 let
const SITE_NAME = '学习平台';     // 配置不变,用 const
const config = { debug: false };   // 对象引用不变,用 const
let currentPage = 1;               // 会改变,用 let
let isLoading = false;             // 会改变,用 let

// 原则2:循环计数器用 let
for (let i = 0; i < 10; i++) { ... }  // ✅ 用 let
for (const item of items) { ... }     // ✅ 也可以用 const(每次迭代都是新绑定)

// 原则3:不再使用 var(除非需要支持非常老的代码)
// ❌ var x = 10;
// ✅ const x = 10;  或  let x = 10;

六、实际项目应用示例

javascript 复制代码
// 实际项目中的典型用法

// 配置常量(const)
const API_URL = 'https://api.example.com';
const MAX_RETRIES = 3;
const DEFAULT_PAGE_SIZE = 20;

// DOM 元素引用(const,引用不变)
const btn = document.querySelector('#submit-btn');
const form = document.querySelector('#login-form');

// 状态变量(let,会改变)
let currentUser = null;
let cartItems = [];
let isSubmitting = false;

// 函数内的局部变量
function fetchUsers(page) {
    const url = `${API_URL}/users?page=${page}`;  // 不变
    let retries = 0;                                // 会变
    
    // ...
}

8.3 Symbol 和 BigInt:ES6+ 新增的原始类型

根据 MDN 官方文档ECMAScript 2027 规范,JavaScript 新增了两种原始类型。

Symbol:唯一标识符(ES2015)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Symbol 完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .card { background: #fff; border-radius: 8px; padding: 20px; margin: 16px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
        pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.7; }
    </style>
</head>
<body>
    <h2>Symbol:ES6 唯一标识符</h2>

    <div class="card">
        <h3>Symbol 的特点</h3>
        <pre><span class="cmt">// 创建 Symbol</span>
<span class="kw">const</span> sym1 = Symbol();
<span class="kw">const</span> sym2 = Symbol(<span class="str">'描述信息'</span>);

<span class="cmt">// 特点一:每个 Symbol 都是唯一的</span>
Symbol() === Symbol();  <span class="cmt">// false</span>
Symbol(<span class="str">'foo'</span>) === Symbol(<span class="str">'foo'</span>);  <span class="cmt">// false</span>

<span class="cmt">// 特点二:typeof 返回 'symbol'</span>
<span class="kw">typeof</span> Symbol();  <span class="cmt">// 'symbol'</span>

<span class="cmt">// 特点三:不能与其他类型运算</span>
Symbol() + 1;  <span class="cmt">// TypeError</span>
Symbol() + <span class="str">''</span>;  <span class="cmt">// TypeError</span></pre>
    </div>

    <div class="card">
        <h3>Symbol 的经典应用场景</h3>
        <pre><span class="cmt">// 场景一:对象的唯一属性名(避免属性名冲突)</span>
<span class="kw">const</span> user = {
    name: <span class="str">'张三'</span>,
    age: 25
};

<span class="cmt">// 第三方库想添加属性,但担心覆盖原有属性</span>
<span class="kw">const</span> idSymbol = Symbol(<span class="str">'id'</span>);
user[idSymbol] = 12345;  <span class="cmt">// ✅ 不会与 name/age 冲突</span>

console.log(user);  <span class="cmt">// { name: '张三', age: 25, [Symbol(id)]: 12345 }</span>
console.log(user[idSymbol]);  <span class="cmt">// 12345</span>

<span class="cmt">// 场景二:定义常量(确保值唯一)</span>
<span class="kw">const</span> STATUS = {
    PENDING: Symbol(<span class="str">'pending'</span>),
    FULFILLED: Symbol(<span class="str">'fulfilled'</span>),
    REJECTED: Symbol(<span class="str">'rejected'</span>)
};

<span class="kw">let</span> state = STATUS.PENDING;
<span class="kw">if</span> (state === STATUS.PENDING) {
    console.log(<span class="str">'等待中...'</span>);
}</pre>
    </div>

    <script>
        const sym1 = Symbol('test');
        const sym2 = Symbol('test');
        console.log('Symbol示例:', sym1 === sym2);  // false

        const user = { name: '张三' };
        const id = Symbol('id');
        user[id] = 999;
        console.log('Symbol作为属性:', user);
    </script>
</body>
</html>
💡 代码解释:Symbol 核心要点

一、Symbol 解决了什么问题?

在 Symbol 出现之前,如果多个代码库想给同一个对象添加属性,很可能产生名字冲突:

javascript 复制代码
// 问题:属性名冲突
var user = { name: '张三' };

// 第一个库添加 id
user.id = 1001;

// 第二个库也添加 id(覆盖了!)
user.id = 'USER_ABC';  // 覆盖了上面的 id!

// ✅ 用 Symbol 解决(每个 Symbol 都是唯一的)
const lib1Id = Symbol('id');
const lib2Id = Symbol('id');

user[lib1Id] = 1001;        // 不会冲突
user[lib2Id] = 'USER_ABC';  // 不会冲突

console.log(user[lib1Id]);  // 1001
console.log(user[lib2Id]);  // 'USER_ABC'
console.log(lib1Id === lib2Id);  // false(即使描述相同,也是不同的 Symbol)

二、Symbol 的创建方式

javascript 复制代码
// 方式1:不带描述
const sym1 = Symbol();
console.log(sym1);          // Symbol()
console.log(typeof sym1);   // 'symbol'

// 方式2:带描述(便于调试)
const sym2 = Symbol('用户ID');
console.log(sym2);            // Symbol(用户ID)
console.log(sym2.description); // '用户ID'(ES2019)

// ⚠️ Symbol 不能用 new(不是构造函数)
// const sym3 = new Symbol();  // ❌ TypeError: Symbol is not a constructor

三、Symbol 的关键特性

javascript 复制代码
// 特性1:每个 Symbol 都唯一
Symbol() === Symbol()             // false(即使无描述)
Symbol('foo') === Symbol('foo')   // false(即使描述相同)

// 特性2:不能参与数学/字符串运算
Symbol('a') + '';   // ❌ TypeError
Symbol('a') + 1;    // ❌ TypeError
Symbol('a').toString();  // 'Symbol(a)'(可以转字符串)
String(Symbol('a'));     // 'Symbol(a)'

// 特性3:Symbol 属性不出现在 for...in 和 Object.keys() 中
const obj = { name: '张三', [Symbol('id')]: 123 };
Object.keys(obj);        // ['name'](不包含 Symbol 属性)
for (let key in obj) { console.log(key); }  // 只输出 'name'

// ✅ 获取 Symbol 属性
Object.getOwnPropertySymbols(obj);  // [Symbol(id)]
Reflect.ownKeys(obj);               // ['name', Symbol(id)]

四、Symbol 的实际应用场景

javascript 复制代码
// 应用1:定义枚举常量(确保值唯一)
const Direction = {
    NORTH: Symbol('north'),
    SOUTH: Symbol('south'),
    EAST: Symbol('east'),
    WEST: Symbol('west')
};

// 不能被伪造:
Direction.NORTH === Symbol('north')  // false!每个 Symbol 都唯一
// 所以这种枚举不会被意外匹配

function move(direction) {
    if (direction === Direction.NORTH) {
        console.log('向北走');
    }
}

// 应用2:Promise 状态枚举
const STATUS = {
    PENDING: Symbol('pending'),
    RESOLVED: Symbol('resolved'),
    REJECTED: Symbol('rejected')
};

// 应用3:内置 Symbol(Well-known Symbols)
// JavaScript 内部使用 Symbol 来定义对象行为
class MyArray {
    [Symbol.iterator]() {  // 定义迭代器
        let index = 0;
        const data = [1, 2, 3];
        return {
            next() {
                return { value: data[index++], done: index > data.length };
            }
        };
    }
}

for (const item of new MyArray()) {
    console.log(item);  // 1, 2, 3
}

五、Symbol.for() --- 全局注册表

javascript 复制代码
// Symbol.for():在全局注册表中查找/创建 Symbol
const s1 = Symbol.for('shared');
const s2 = Symbol.for('shared');
console.log(s1 === s2);  // true(同一个 Symbol!)

// 区别:Symbol() 每次都是新的,Symbol.for() 使用共享注册
const a = Symbol('test');
const b = Symbol('test');
const c = Symbol.for('test');
const d = Symbol.for('test');

a === b  // false(普通 Symbol,每次新建)
c === d  // true(注册 Symbol,共享复用)

BigInt:大整数(ES2020)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BigInt 完整示例</title>
    <style>
        body { font-family: Arial; padding: 20px; }
        .card { background: #fff; border-radius: 8px; padding: 20px; margin: 16px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
        pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.7; }
    </style>
</head>
<body>
    <h2>BigInt:ES2020 大整数</h2>

    <div class="card">
        <h3>为什么需要 BigInt?</h3>
        <pre><span class="cmt">// Number 类型的安全整数范围限制</span>
console.log(Number.MAX_SAFE_INTEGER);  <span class="cmt">// 9007199254740991 (2^53 - 1)</span>
console.log(Number.MIN_SAFE_INTEGER);  <span class="cmt">// -9007199254740991</span>

<span class="cmt">// 超出安全范围,计算出错</span>
<span class="kw">const</span> max = Number.MAX_SAFE_INTEGER;
console.log(max + 1);  <span class="cmt">// 9007199254740992</span>
console.log(max + 2);  <span class="cmt">// 9007199254740992 ← 错误!应该是 9007199254740993</span>
console.log(max + 1 === max + 2);  <span class="cmt">// true ← 错误!</span></pre>
    </div>

    <div class="card">
        <h3>BigInt 的使用</h3>
        <pre><span class="cmt">// 创建 BigInt:在数字后加 n</span>
<span class="kw">const</span> big1 = 123456789012345678901234567890n;
<span class="kw">const</span> big2 = BigInt(<span class="str">'123456789012345678901234567890'</span>);
<span class="kw">const</span> big3 = BigInt(999);

console.log(<span class="kw">typeof</span> big1);  <span class="cmt">// 'bigint'</span>

<span class="cmt">// BigInt 可以精确计算大整数</span>
<span class="kw">const</span> huge = 9007199254740991n;
console.log(huge + 1n);  <span class="cmt">// 9007199254740992n ✅</span>
console.log(huge + 2n);  <span class="cmt">// 9007199254740993n ✅</span>
console.log(huge + 1n === huge + 2n);  <span class="cmt">// false ✅</span>

<span class="cmt">// 运算:+, -, *, /, %, **</span>
10n + 20n;  <span class="cmt">// 30n</span>
10n * 5n;   <span class="cmt">// 50n</span>
10n ** 100n;  <span class="cmt">// 非常大的数</span>

<span class="cmt">// ⚠️ 不能与 Number 混用</span>
10n + 5;  <span class="cmt">// TypeError: Cannot mix BigInt and other types</span>

<span class="cmt">// 转换:必须显式转换</span>
10n + BigInt(5);  <span class="cmt">// 15n ✅</span>
Number(10n) + 5;  <span class="cmt">// 15 ✅</span></pre>
    </div>

    <div class="card">
        <h3>BigInt 的经典应用场景</h3>
        <pre><span class="cmt">// 场景一:处理超大数字(如区块链、加密货币)</span>
<span class="kw">const</span> satoshis = 2100000000000000n;  <span class="cmt">// 比特币总量(聪)</span>

<span class="cmt">// 场景二:精确的 ID 生成(如 Twitter 的 Snowflake ID)</span>
<span class="kw">const</span> tweetId = 1453892349283749384n;

<span class="cmt">// 场景三:科学计算(如阶乘)</span>
<span class="kw">function</span> factorial(n) {
    <span class="kw">let</span> result = 1n;
    <span class="kw">for</span> (<span class="kw">let</span> i = 2n; i <= n; i++) {
        result *= i;
    }
    <span class="kw">return</span> result;
}
console.log(factorial(20n));  <span class="cmt">// 2432902008176640000n(Number无法精确表示)</span></pre>
    </div>

    <script>
        const big = 9007199254740991n;
        console.log('BigInt示例:', big + 1n, big + 2n);
        console.log('是否相等:', big + 1n === big + 2n);  // false
    </script>
</body>
</html>
💡 代码解释:BigInt 核心要点

一、为什么需要 BigInt?

javascript 复制代码
// Number 类型的安全整数范围
Number.MAX_SAFE_INTEGER  // 9007199254740991 (2^53 - 1,约9千兆)

// 超出安全范围的计算会出错!
const max = Number.MAX_SAFE_INTEGER;
console.log(max + 1 === max + 2);  // true(本应是 false)

// 实际问题场景
// 1. 数据库的 bigint 主键(超过 JS Number 范围)
// 2. 加密货币中的精确金额(比特币 Satoshi)
// 3. 社交媒体的 ID(Twitter Snowflake ID)
// 4. 科学计算(大阶乘、大质数)

二、BigInt 的语法

javascript 复制代码
// 方式1:数字后加 n
const big1 = 9007199254740993n;  // 普通Number无法精确表示的数
const big2 = 0n;                  // 零
const big3 = -100n;               // 负数

// 方式2:BigInt() 构造函数
const big4 = BigInt(9007199254740991);     // 从 Number 转换
const big5 = BigInt('12345678901234567890'); // 从字符串转换(推荐大数)
const big6 = BigInt('0xFF');               // 十六进制字符串

// typeof
typeof 42n  // 'bigint'

三、BigInt 运算规则

javascript 复制代码
// ✅ 支持的运算:+、-、*、/、%、**(整除,不保留小数)
10n + 20n    // 30n
100n - 1n    // 99n
5n * 3n      // 15n
10n / 3n     // 3n(整除!不是 3.333...)
10n % 3n     // 1n(余数)
2n ** 10n    // 1024n

// ⚠️ 不能与 Number 混用
10n + 5      // ❌ TypeError: Cannot mix BigInt and other types
10n + 5n     // ✅ 15n

// 需要混用时,显式转换
Number(10n) + 5    // ✅ 15(BigInt → Number)
10n + BigInt(5)    // ✅ 15n(Number → BigInt)

// ⚠️ 不支持小数
5n / 2n     // 2n(整除,丢弃小数部分)
// 没有 2.5n 这种写法

// 比较运算(可以和 Number 比较)
10n == 10    // true(宽松相等,类型不同)
10n === 10   // false(严格相等,类型不同)
10n > 9      // true(可以和 Number 比较大小)
10n < 20     // true

四、BigInt 的使用限制

javascript 复制代码
// ❌ 不支持 Math 方法
Math.max(10n, 20n)   // TypeError
Math.sqrt(4n)        // TypeError
// ✅ 替代方案:手动实现
function bigIntMax(a, b) { return a > b ? a : b; }

// ❌ 不能直接转为 JSON
JSON.stringify(10n)   // TypeError: Do not know how to serialize a BigInt
// ✅ 替代方案
JSON.stringify({ id: 10n }, (key, value) =>
    typeof value === 'bigint' ? value.toString() : value
);

// ❌ 不能和浮点数混用
10.5n   // SyntaxError(BigInt 没有小数点)

五、实际应用场景

javascript 复制代码
// 1. 处理 Twitter/微博 ID(Snowflake ID 超过 2^53)
const tweetId = 1569953993819185152n;  // 精确存储
const nextId = tweetId + 1n;           // 精确计算

// 2. 加密货币(比特币 Satoshi)
const BTC_TOTAL_SATOSHI = 2100000000000000n;  // 2100万比特币的聪
const satoshiPerBTC = 100000000n;              // 1 BTC = 1亿 Satoshi

// 3. 阶乘计算(Number 很快溢出)
function factorial(n) {
    if (n <= 1n) return 1n;
    return n * factorial(n - 1n);
}
console.log(factorial(50n));  // 准确的50!(Number 无法表示)

// 4. 密码学大质数
const p = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;

九、综合实战:购物车计算器

结合所学知识点,创建一个综合实战项目。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>购物车计算器 - 综合实战</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; background: #f0f2f5; padding: 20px; }
        
        .container { max-width: 800px; margin: 0 auto; }
        h1 { color: #2c3e50; margin-bottom: 20px; }
        
        .cart-item {
            background: white;
            border-radius: 8px;
            padding: 16px;
            margin-bottom: 12px;
            display: flex;
            align-items: center;
            gap: 16px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.08);
        }
        
        .item-image {
            width: 80px;
            height: 80px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 32px;
        }
        
        .item-info { flex: 1; }
        .item-name { font-size: 16px; font-weight: bold; color: #333; margin-bottom: 4px; }
        .item-price { font-size: 18px; color: #e74c3c; font-weight: bold; }
        
        .quantity-control {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .qty-btn {
            width: 32px;
            height: 32px;
            border: 1px solid #ddd;
            background: white;
            border-radius: 4px;
            cursor: pointer;
            font-size: 18px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .qty-btn:hover { background: #f0f0f0; }
        .qty-input { width: 50px; text-align: center; font-size: 16px; border: 1px solid #ddd; border-radius: 4px; padding: 4px; }
        
        .summary {
            background: white;
            border-radius: 8px;
            padding: 20px;
            margin-top: 20px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        
        .summary-row {
            display: flex;
            justify-content: space-between;
            padding: 10px 0;
            font-size: 15px;
        }
        
        .summary-row.total {
            border-top: 2px solid #eee;
            font-size: 20px;
            font-weight: bold;
            color: #e74c3c;
            margin-top: 10px;
            padding-top: 16px;
        }
        
        .checkout-btn {
            width: 100%;
            padding: 14px;
            background: #27ae60;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            margin-top: 16px;
        }
        
        .checkout-btn:hover { background: #229954; }
        
        .tag { display: inline-block; padding: 2px 8px; background: #3498db; color: white; border-radius: 4px; font-size: 11px; margin-left: 8px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🛒 购物车计算器 <span class="tag">综合实战</span></h1>
        
        <div id="cartItems"></div>
        
        <div class="summary">
            <div class="summary-row">
                <span>商品总数:</span>
                <span id="totalItems">0 件</span>
            </div>
            <div class="summary-row">
                <span>商品小计:</span>
                <span id="subtotal">¥0.00</span>
            </div>
            <div class="summary-row">
                <span>运费:</span>
                <span id="shipping">¥0.00</span>
            </div>
            <div class="summary-row total">
                <span>应付总额:</span>
                <span id="total">¥0.00</span>
            </div>
            <button class="checkout-btn" onclick="checkout()">立即结算</button>
        </div>
    </div>

    <script>
        // 使用 const 定义常量(商品数据)
        const products = [
            { id: 1, name: '无线蓝牙耳机', price: 299, emoji: '🎧', quantity: 1 },
            { id: 2, name: '智能手表', price: 899, emoji: '⌚', quantity: 1 },
            { id: 3, name: '移动电源', price: 149, emoji: '🔋', quantity: 2 }
        ];
        
        const FREE_SHIPPING_THRESHOLD = 500;  // 满500免运费
        const SHIPPING_FEE = 15;  // 运费
        
        // 渲染购物车
        function renderCart() {
            const cartContainer = document.getElementById('cartItems');
            cartContainer.innerHTML = '';
            
            // 使用 for...of 遍历(ES6)
            for (const product of products) {
                const itemHTML = `
                    <div class="cart-item">
                        <div class="item-image">${product.emoji}</div>
                        <div class="item-info">
                            <div class="item-name">${product.name}</div>
                            <div class="item-price">¥${product.price.toFixed(2)}</div>
                        </div>
                        <div class="quantity-control">
                            <button class="qty-btn" onclick="changeQuantity(${product.id}, -1)">−</button>
                            <input type="number" class="qty-input" value="${product.quantity}" 
                                   onchange="updateQuantity(${product.id}, this.value)" min="1">
                            <button class="qty-btn" onclick="changeQuantity(${product.id}, 1)">+</button>
                        </div>
                    </div>
                `;
                cartContainer.innerHTML += itemHTML;
            }
            
            updateSummary();
        }
        
        // 修改数量
        function changeQuantity(productId, delta) {
            // 使用 find() 查找商品(ES6)
            const product = products.find(p => p.id === productId);
            if (product) {
                product.quantity = Math.max(1, product.quantity + delta);
                renderCart();
            }
        }
        
        // 更新数量
        function updateQuantity(productId, newQuantity) {
            const quantity = parseInt(newQuantity);
            if (isNaN(quantity) || quantity < 1) {
                alert('数量必须大于0');
                renderCart();
                return;
            }
            
            const product = products.find(p => p.id === productId);
            if (product) {
                product.quantity = quantity;
                renderCart();
            }
        }
        
        // 更新汇总信息
        function updateSummary() {
            // 使用 reduce() 计算总数和小计(ES6)
            let totalItems = 0;
            let subtotal = 0;
            
            for (const product of products) {
                totalItems += product.quantity;
                subtotal += product.price * product.quantity;
            }
            
            // 计算运费(满500免运费)
            const shipping = subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_FEE;
            const total = subtotal + shipping;
            
            // 更新 DOM(精度处理:使用 toFixed(2))
            document.getElementById('totalItems').textContent = `${totalItems} 件`;
            document.getElementById('subtotal').textContent = `¥${subtotal.toFixed(2)}`;
            document.getElementById('shipping').textContent = shipping === 0 ? 
                '免运费 ✅' : `¥${shipping.toFixed(2)}`;
            document.getElementById('total').textContent = `¥${total.toFixed(2)}`;
        }
        
        // 结算
        function checkout() {
            const totalItems = products.reduce((sum, p) => sum + p.quantity, 0);
            const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);
            
            alert(`感谢购买!\n商品数量:${totalItems} 件\n实付金额:¥${total.toFixed(2)}`);
        }
        
        // 初始化
        renderCart();
    </script>
</body>
</html>

项目涉及的知识点:

  • const 常量定义
  • ✅ 模板字符串(ES6)
  • for...of 循环(ES6)
  • ✅ 箭头函数(ES6)
  • find()reduce() 数组方法(ES6)
  • ✅ 浮点精度处理(toFixed(2)
  • isNaN() 数据验证
  • ✅ DOM 操作
  • ✅ 响应式布局(Flexbox)
💡 代码解释:购物车计算器综合实战解析

这个项目综合运用了本章所有核心知识点,是最佳的综合练习案例。

一、数据结构设计

javascript 复制代码
// 使用 const 定义不变的配置常量(CONSTANT_CASE 命名)
const SHIPPING_FEE = 15;               // 运费
const FREE_SHIPPING_THRESHOLD = 500;   // 免运费门槛

// 使用 const 定义商品数组(数组内容可变,引用不变)
const products = [
    { 
        id: 1,              // 唯一标识(整数)
        name: 'iPhone...',  // 商品名称(字符串)
        price: 6999,        // 单价(数字,使用整数避免浮点误差)
        quantity: 1,        // 数量(可变)
        emoji: '📱'         // 表情图标(字符串)
    },
    // ...
];

二、模板字符串生成 HTML

javascript 复制代码
function renderCart() {
    const cartContainer = document.getElementById('cart-items');
    cartContainer.innerHTML = '';  // 清空后重新渲染
    
    for (const product of products) {  // for...of 遍历数组
        const itemHTML = `
            <div class="cart-item">
                <div class="item-image">${product.emoji}</div>
                <!--                  ↑ 模板字符串插值 -->
                <div class="item-name">${product.name}</div>
                <div class="item-price">¥${product.price.toFixed(2)}</div>
                <!--                         ↑ toFixed(2) 保留两位小数 -->
                <button onclick="changeQuantity(${product.id}, -1)">−</button>
                <!--              ↑ 将 product.id 插入事件处理属性 -->
            </div>
        `;
        cartContainer.innerHTML += itemHTML;
    }
    
    updateSummary();
}

三、数组 find() 方法(ES6)

javascript 复制代码
function changeQuantity(productId, delta) {
    // find() 查找满足条件的第一个元素
    // p => p.id === productId 是箭头函数(ES6)
    const product = products.find(p => p.id === productId);
    //                         ↑ 箭头函数:p 是每个商品,返回 id 匹配的那个
    
    if (product) {
        // Math.max(1, 数量 + 变化量) 确保数量最少为 1
        product.quantity = Math.max(1, product.quantity + delta);
        renderCart();  // 重新渲染
    }
}

// 等价的传统写法(理解对比)
var product = products.find(function(p) {
    return p.id === productId;
});

四、数组 reduce() 方法(ES6)

javascript 复制代码
function checkout() {
    // reduce() 将数组归并为单个值
    const totalItems = products.reduce(
        (sum, p) => sum + p.quantity,  // 累加每个商品的数量
        0                              // 初始值为 0
    );
    
    const total = products.reduce(
        (sum, p) => sum + p.price * p.quantity,  // 累加每个商品的小计
        0
    );
    
    // 等价的传统写法
    let total2 = 0;
    for (var i = 0; i < products.length; i++) {
        total2 += products[i].price * products[i].quantity;
    }
}

五、浮点精度处理

javascript 复制代码
// 商品价格使用整数(元),避免浮点误差
// 但显示时需要保留两位小数
document.getElementById('total').textContent = `¥${total.toFixed(2)}`;
//                                                     ↑ toFixed(2) 返回字符串,如 "99.00"

// 实际注意事项
0.1 + 0.2           // 0.30000000000000004(浮点误差)
(0.1 + 0.2).toFixed(2)  // "0.30"(格式化后正确显示)

// 更安全的金融计算:转为分再计算
var priceCents = 6999 * 100;  // 699900 分
var total = priceCents * 2 / 100;  // 13998 元

六、输入验证

javascript 复制代码
function updateQuantity(productId, newQuantity) {
    const quantity = parseInt(newQuantity);  // 转为整数
    
    // 多重验证
    if (isNaN(quantity) || quantity < 1) {
        alert('数量必须大于0');  // 用户提示
        renderCart();            // 重置显示
        return;                  // 提前退出函数
    }
    
    // 验证通过,更新数据
    const product = products.find(p => p.id === productId);
    if (product) {
        product.quantity = quantity;
        renderCart();
    }
}

七、响应式布局(Flexbox)在实战中的应用

css 复制代码
/* 购物车条目:Flexbox 横向布局 */
.cart-item {
    display: flex;           /* 创建 Flex 容器 */
    align-items: center;     /* 垂直居中 */
    gap: 16px;              /* 项目间距 */
    padding: 16px;
}

/* 商品名称占据剩余空间 */
.item-info {
    flex: 1;               /* flex-grow: 1,撑满剩余空间 */
}

/* 数量控件不被压缩 */
.quantity-control {
    flex-shrink: 0;        /* 不缩小 */
    display: flex;
    align-items: center;
    gap: 8px;
}

八、项目架构总结

复制代码
购物车计算器 架构图:

数据层(Data)
  ├── products[]        → 商品列表(含数量)
  ├── SHIPPING_FEE      → 运费常量
  └── FREE_SHIPPING_THRESHOLD → 免运费门槛

视图层(View)
  ├── renderCart()      → 渲染购物车列表
  └── updateSummary()   → 更新价格汇总

交互层(Controller)
  ├── changeQuantity()  → 增减数量
  ├── updateQuantity()  → 输入修改数量
  └── checkout()        → 结算

数据流:
用户操作 → 修改 products 数组 → renderCart() 重新渲染 → 用户看到新界面

九、可扩展的改进方向

javascript 复制代码
// 1. 添加"删除商品"功能
function removeProduct(id) {
    const index = products.findIndex(p => p.id === id);
    if (index !== -1) {
        products.splice(index, 1);  // 从数组中删除
        renderCart();
    }
}

// 2. 添加"清空购物车"功能
function clearCart() {
    products.splice(0, products.length);  // 清空数组
    renderCart();
}

// 3. 使用 reduce 计算总价(更函数式)
const subtotal = products.reduce((sum, p) => sum + p.price * p.quantity, 0);

// 4. 保存到 localStorage(持久化)
function saveCart() {
    localStorage.setItem('cart', JSON.stringify(products));
}
function loadCart() {
    const saved = localStorage.getItem('cart');
    if (saved) {
        const data = JSON.parse(saved);
        products.splice(0, products.length, ...data);
    }
}

本文总结:

本博客基于 MDN 官方文档W3C 规范ECMAScript 2026/2027 规范CSS-TricksDigitalOcean 技术文章,深度解析了 CSS 响应式布局(媒体查询、Flexbox、响应式图片)、BFC 块级格式上下文的原理与实战、JavaScript 基础入门知识(语法、变量、数据类型),以及 ES6+ 新特性(let/const/Symbol/BigInt、变量提升)。每个知识点均配有可运行的 HTML 示例、mermaid 图示、官方定义和真实网站使用案例。掌握这些基础知识,是走向前端工程师的第一步。


十、全章注意事项汇总 ⚠️

10.1 CSS 响应式布局注意事项

序号 注意点 错误写法 正确写法
1 viewport meta 标签必须写 不写,移动端布局错乱 <meta name="viewport" content="width=device-width, initial-scale=1.0">
2 移动优先(min-width),性能更好 max-width 从大到小 min-width 从小到大
3 pictureimg 是必须的 不写 img,降级不生效 最后一个子元素必须是 <img>
4 flex: 1 的含义 以为是 flex-grow: 1 等价于 flex: 1 1 0(grow/shrink/basis)
5 媒体查询断点使用一致的单位 px 和 rem 混用 统一用 px 或统一用 rem
6 display: flex !important 覆盖 忘记 !important 导致规则被覆盖 展开导航时加 !important

10.2 BFC 注意事项

序号 注意点 说明
1 overflow: hidden 会裁剪溢出内容 使用前确认没有需要溢出显示的元素(如 tooltip)
2 display: flow-root 是最佳选择 专为创建 BFC 设计,无副作用,但 IE 不支持
3 兄弟元素外边距塌陷通常不需要处理 这是规范预期行为,不是 Bug
4 position: absolute 也创建 BFC 但脱离文档流 会影响其他元素布局,谨慎使用
5 float 已不推荐用于布局 使用 Flexbox/Grid 代替,float 仅用于文字环绕

10.3 JavaScript 语法注意事项

javascript 复制代码
// ⚠️ 注意1:严格区分大小写
alert(100);    // ✅
Alert(100);    // ❌ ReferenceError

// ⚠️ 注意2:字符串比较区分大小写
'hello' === 'Hello'  // false(不同!)

// ⚠️ 注意3:== 会进行类型转换(尽量使用 ===)
0 == false    // true(转换后相等)
0 === false   // false(类型不同)

// ⚠️ 注意4:typeof null 是 "object"(历史Bug)
typeof null === 'object'  // true,但 null 不是对象
// 正确判断:value === null

// ⚠️ 注意5:NaN 不等于自身
NaN === NaN   // false
// 正确判断:Number.isNaN(value)

// ⚠️ 注意6:浮点数精度问题
0.1 + 0.2 !== 0.3  // true(计算有误差)
// 正确处理:(0.1 + 0.2).toFixed(2) 或转整数运算

// ⚠️ 注意7:字符串拼接 vs 数学加法
'5' + 3     // '53'(字符串拼接)
'5' - 3     // 2(数字运算,-会转换类型)
5 + '3'     // '53'(字符串拼接,顺序无关)

// ⚠️ 注意8:var 变量提升
console.log(x);  // undefined(不报错)
var x = 10;

// ⚠️ 注意9:document.write() 在页面加载后调用会清空页面
// 页面加载完成后不要使用 document.write()

// ⚠️ 注意10:空数组/空对象是 truthy!
Boolean([])   // true(不是 false!)
Boolean({})   // true(不是 false!)

10.4 ES6+ 注意事项

javascript 复制代码
// ⚠️ const 声明的对象,内容可以修改
const user = { name: '张三' };
user.name = '李四';   // ✅ 允许(修改属性)
// user = {};        // ❌ 报错(不能重新赋值)

// ⚠️ let/const 有暂时性死区(TDZ)
// console.log(x);  // ❌ ReferenceError
let x = 10;
// 不能在声明前访问(与 var 不同,var 返回 undefined)

// ⚠️ BigInt 不能与 Number 混用
10n + 5     // ❌ TypeError
10n + 5n    // ✅
Number(10n) + 5  // ✅ 显式转换

// ⚠️ Symbol 每次调用都创建新的唯一值
Symbol('a') === Symbol('a')  // false(不相等!)
Symbol.for('a') === Symbol.for('a')  // true(注册表复用)

十一、知识点速查总结

11.1 CSS 响应式布局速查

css 复制代码
/* 1. 必备 viewport meta */
<meta name="viewport" content="width=device-width, initial-scale=1.0">

/* 2. 移动优先媒体查询模板 */
/* 手机端(默认样式) */
.container { width: 100%; }

/* 平板端 */
@media (min-width: 640px) {
    .container { max-width: 640px; }
}

/* 桌面端 */
@media (min-width: 1024px) {
    .container { max-width: 1200px; }
}

/* 3. Flexbox 常用模板 */
.flex-center {
    display: flex;
    justify-content: center;  /* 水平居中 */
    align-items: center;      /* 垂直居中 */
}

.flex-between {
    display: flex;
    justify-content: space-between;  /* 两端对齐 */
    align-items: center;
}

/* 4. 响应式网格 */
.grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);  /* 2列 */
    gap: 16px;
}
@media (min-width: 768px) {
    .grid { grid-template-columns: repeat(3, 1fr); }  /* 3列 */
}

11.2 JavaScript 数据类型速查表

类型 典型值 typeof 转布尔 特殊注意
number 42, 3.14, NaN, Infinity "number" 0/NaN→false NaN≠NaN
string 'hello', "", ${x} "string" ""→false 不可变
boolean true, false "boolean" false→false ---
null null "object" ⚠️ false typeof Bug
undefined undefined "undefined" false ---
symbol Symbol() "symbol" true 每次唯一
bigint 42n "bigint" 0n→false 不混number
object {}, [], null "object" true(除null) 引用类型
function function(){} "function" true function是object子类

11.3 var/let/const 三分钟速记

复制代码
var  → 函数作用域 + 可重复声明 + 有提升(值为undefined)→ 不推荐
let  → 块级作用域 + 不可重复声明 + TDZ → 用于可变值
const→ 块级作用域 + 不可重复声明 + TDZ + 不可重新赋值 → 优先使用

选择原则:
1. 默认用 const
2. 需要重新赋值用 let
3. 不要用 var(除非维护老代码)

11.4 BFC 三分钟速记

复制代码
BFC = 独立的渲染容器,内外隔离

触发方式(常用):
  display: flow-root → 最推荐,无副作用
  overflow: hidden   → 最常用,会裁剪溢出
  float: left/right  → 会脱离文档流
  position: absolute/fixed → 会脱离文档流

解决的问题:
  1. 高度塌陷(浮动子元素导致父元素高度为0)
  2. 外边距塌陷(父子元素margin合并)
  3. 阻止文字环绕浮动元素

记忆口诀:
  overflow hidden 最常用
  flow-root 最干净
  看到高度塌陷 = 给父元素加BFC
  看到margin穿透 = 给父元素加BFC

十二、学习路线建议

Day01 基础
CSS响应式布局
BFC块级格式上下文
JavaScript基础语法
媒体查询 ✅
Flexbox ✅
Grid布局 → Day进阶
清除浮动 ✅
解决外边距塌陷 ✅
变量与数据类型 ✅
运算符 → Day02
流程控制 → Day02
函数 → Day03
对象与数组 → Day04
DOM操作 → Day05
ES6+进阶 → Day06
✅ number/string/boolean
✅ null/undefined
✅ typeof运算符
✅ var/let/const
✅ Symbol/BigInt (了解)

学习建议:

  1. Day01 核心重点:掌握响应式布局三件套(viewport + 媒体查询 + Flexbox),JavaScript 基础语法和 6 种数据类型
  2. 理解而非死记 :理解为什么 typeof null === 'object',为什么有 NaN,而不是死背答案
  3. 动手实践:每个示例都要亲手敲一遍,在浏览器开发者工具中验证结果
  4. 善用 MDN :遇到不确定的 API,第一时间查 MDN Web Docs
  5. 建立错误集:遇到 Bug 记录到错误本,防止重复踩坑

相关推荐
光影少年1 小时前
React18 函数组件执行顺序、严格模式下重复执行问题
前端·javascript·react.js
之歆1 小时前
DAY_20JavaScript 条件语句与循环结构深度学习(一)
前端·javascript
lihaozecq1 小时前
从零实现一个 ReAct Agent Loop - 可中断、可流式、多模型支持
前端·agent·ai编程
冴羽yayujs1 小时前
GitHub 前端热榜项目 - 日榜(2026-05-10)
前端·github
CAE虚拟与现实1 小时前
前后端调试常用工具大全
前端·后端·vue·react·angular
iuu_star1 小时前
跑通最简单的Vue3+Python前后端分离项目
前端·vue.js·python
AZaLEan__1 小时前
CSS3:从 2D 变换到 3D 翻转
前端·3d·css3
剑神一笑1 小时前
Linux du 命令深度解析:从磁盘占用统计到目录空间分析
linux·运维·前端
weixin_446260851 小时前
AI驱动的前沿前端技术栈深度解析:从模型能力到UI封装的完整生命周期
前端·人工智能·ui