5.6 经典应用场景
js
// 场景一:驼峰命名转换
function toCamelCase(str) {
return str.split('-').map(function(word, index) {
if (index === 0) return word;
return word.charAt(0).toUpperCase() + word.slice(1);
}).join('');
}
console.log(toCamelCase('get-element-by-id')); // getElementById
console.log(toCamelCase('background-color')); // backgroundColor
// 场景二:翻转字符串(利用数组 reverse 方法)
function reverseString(str) {
return str.split('').reverse().join('');
}
console.log(reverseString('Hello')); // olleH
// 场景三:格式化日期输出(padStart 补零)
function formatDate(date) {
var y = date.getFullYear();
var m = String(date.getMonth() + 1).padStart(2, '0');
var d = String(date.getDate()).padStart(2, '0');
var h = String(date.getHours()).padStart(2, '0');
var min = String(date.getMinutes()).padStart(2, '0');
var s = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
}
// 场景四:简单邮箱格式验证
function isValidEmail(email) {
return email.includes('@') &&
email.indexOf('@') > 0 &&
email.lastIndexOf('.') > email.indexOf('@') &&
email.lastIndexOf('.') < email.length - 1;
}
// 场景五:URL 参数解析
function parseQueryString(query) {
if (!query) return {};
return query.replace(/^\?/, '').split('&').reduce(function(acc, pair) {
var parts = pair.split('=');
acc[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1] || '');
return acc;
}, {});
}
console.log(parseQueryString('?name=Alice&age=25&city=Shanghai'));
// { name: 'Alice', age: '25', city: 'Shanghai' }
💡 代码解析
代码片段 含义 str.split('-').map(fn).join('')三步驼峰转换:① split('-')按连字符切割,②map对每个单词首字母大写(跳过第一个),③join('')无分隔符拼合word.charAt(0).toUpperCase() + word.slice(1)取首字母大写,拼接剩余部分。 slice(1)从索引1开始截取,即去掉首字母的其余部分String(n).padStart(2, '0')先将数字转字符串,再用 '0'填充到最少 2 位宽。数字9→'09',14已经两位不变s.split('').reverse().join('')split('')将字符串炸开为单字符数组,借用数组的reverse()翻转,join('')重新合并为字符串query.replace(/^\?/, '').split('&')先用正则去掉开头的 ?,再按&分割出各个参数对,decodeURIComponent解码 URL 编码的字符🏢 经典使用场景 & 业务价值
场景 核心方法 业务价值 前后端字段名映射 toCamelCase(下划线→驼峰)后端返回 user_name自动转为前端userName,消除命名风格差异日志时间戳格式化 formatDate(new Date())统一所有日志的时间格式 YYYY-MM-DD HH:mm:ss,便于排序和检索前端路由参数解析 parseQueryString(location.search)不依赖第三方库解析 URL 查询参数,减少打包体积 字符串回文检测 split('')+reverse()+join('')输入验证、算法面试题,理解数组与字符串转换的互操作 邮箱快速校验 includes('@')+lastIndexOf('.')前端提交前的轻量级格式校验,提升用户体验(复杂校验由后端完成)
5.7 完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>String 方法演示</title>
<style>
* { box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; max-width: 900px; margin: 0 auto; padding: 24px; background: #f0f4f8; }
.card { background: #fff; border-radius: 12px; padding: 20px; margin: 16px 0; box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
h2 { color: #7b1fa2; margin: 0 0 16px; }
input[type=text] { width: 100%; padding: 10px; border: 2px solid #ce93d8; border-radius: 8px; font-size: 15px; margin-bottom: 10px; }
.btn-group { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
button { background: #7b1fa2; color: #fff; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
button:hover { background: #6a1b9a; }
.result { background: #f3e5f5; padding: 12px 16px; border-radius: 8px; font-family: monospace; font-size: 14px; min-height: 36px; white-space: pre-wrap; color: #4a148c; }
</style>
</head>
<body>
<h1 style="color:#4a148c">String 内置构造函数全解析</h1>
<div class="card">
<h2>字符串操作实验室</h2>
<input type="text" id="strInput" value="Hello,开发者,欢迎学习 JavaScript!">
<div class="btn-group">
<button onclick="run('length')">长度</button>
<button onclick="run('upper')">大写</button>
<button onclick="run('lower')">小写</button>
<button onclick="run('trim')">去空格</button>
<button onclick="run('reverse')">翻转</button>
<button onclick="run('split')">分割(,)</button>
<button onclick="run('slice')">截取(0,5)</button>
<button onclick="run('includes')">includes查找</button>
<button onclick="run('padStart')">padStart补零</button>
</div>
<div class="result" id="strResult">输入字符串并点击操作按钮</div>
</div>
<div class="card">
<h2>驼峰命名转换器</h2>
<input type="text" id="camelInput" value="get-element-by-id">
<button onclick="camelConvert()">转为驼峰命名</button>
<div class="result" id="camelResult">点击转换</div>
</div>
<div class="card">
<h2>Unicode 字符查询</h2>
<input type="text" id="unicodeInput" value="A" maxlength="2">
<button onclick="getUnicode()">获取 Unicode 编码</button>
<div class="result" id="unicodeResult">点击查询</div>
</div>
<script>
function run(op) {
var s = document.getElementById('strInput').value;
var r = '';
switch(op) {
case 'length': r = '字符长度(UTF-16代码单元数): ' + s.length; break;
case 'upper': r = s.toUpperCase(); break;
case 'lower': r = s.toLowerCase(); break;
case 'trim': r = '"' + (' ' + s + ' ').trim() + '"(两端空格已去除)'; break;
case 'reverse': r = s.split('').reverse().join(''); break;
case 'split': r = JSON.stringify(s.split(',')); break;
case 'slice': r = '"' + s.slice(0, 5) + '"(slice(0,5))'; break;
case 'includes': r = 'includes("JavaScript") → ' + s.includes('JavaScript'); break;
case 'padStart': r = '"' + String(42).padStart(6, '0') + '"(42 补零到6位)'; break;
}
document.getElementById('strResult').textContent = r;
}
function camelConvert() {
var s = document.getElementById('camelInput').value;
var result = s.split('-').map(function(word, idx) {
return idx === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1);
}).join('');
document.getElementById('camelResult').textContent = s + ' → ' + result;
}
function getUnicode() {
var ch = document.getElementById('unicodeInput').value;
if (!ch) return;
var code = ch.codePointAt(0);
document.getElementById('unicodeResult').textContent =
'字符: "' + ch + '"\n' +
'charCodeAt(0) = ' + ch.charCodeAt(0) + '\n' +
'codePointAt(0) = ' + code + '\n' +
'十六进制: U+' + code.toString(16).toUpperCase().padStart(4,'0') + '\n' +
'fromCodePoint(' + code + ') = "' + String.fromCodePoint(code) + '"';
}
</script>
</body>
</html>

📌 String --- 知识特点总结
特点 描述 不可变性 字符串是值类型,所有方法都返回新字符串,原字符串不变;不能通过索引赋值修改 UTF-16 编码 length属性返回 UTF-16 代码单元数,emoji 等字符长度可能为 2,不等于视觉字符数自动包装 原始字符串调用方法时,引擎自动创建临时 String包装对象,调用后立即销毁slice最灵活三种截取方法中, slice支持负数索引且行为最一致,是首选indexOf返回 -1未找到时返回 -1,可用!== -1判断存在性;ES6+ 推荐includes()更语义化split+join组合字符串与数组的双向转换,是很多字符串处理(翻转、替换、格式化)的核心模式 padStart补零技巧日期格式化中 String(n).padStart(2, '0')是最简洁的补零方式模板字面量(ES6) 多行字符串和插值,彻底替代了字符串拼接,大幅提升可读性
6 内置对象 Math
6.1 理论基础:Math 不是构造函数
Math 是 JavaScript 中一个特殊的内置对象(Built-in Object) ,而非内置构造函数。它是一个普通对象(Object 的实例),所有属性和方法都直接挂载在 Math 对象上,无需也无法实例化(调用 new Math() 会报错)。
js
console.log(typeof Math); // "object"(不是 function)
console.log(Math instanceof Object); // true
console.log(Math.constructor); // Object(Math 是 Object 的实例)
// Math.__proto__ === Object.prototype
// 不能实例化
try { new Math(); } catch(e) { console.log(e.message); } // Math is not a constructor
💡 代码解析
代码片段 含义 typeof Math→"object"Math是普通对象实例(Object的实例),不是函数,因此typeof返回"object"Math instanceof Object→trueMath的原型链:Math → Object.prototype → null,instanceof Object为trueMath.constructor→Object说明 Math对象是由Object构造的,而非自定义类new Math()抛出TypeErrorMath对象不具有[[Construct]]内部方法,尝试new调用会抛出TypeError: Math is not a constructor
Math 对象的设计体现了**命名空间(Namespace)**模式------将相关的常量和函数组织在一个对象下,避免全局变量污染。
深层理论:伪随机数生成器(PRNG)原理
Math.random() 并非真正的随机数------计算机是确定性系统,无法凭空产生真正的随机数(除非借助硬件熵源)。它返回的是伪随机数(Pseudo-Random Number),由确定性算法基于初始"种子"计算。
① 随机数的分类
| 类型 | 原理 | 特点 | 适用场景 |
|---|---|---|---|
| TRNG(真随机数) | 物理随机过程(量子涨落、热噪声、放射性衰变) | 不可预测,不可重现 | 密钥生成、彩票 |
| PRNG(伪随机数) | 确定性数学算法(线性同余、MT、xorshift) | 速度极快,可重现(给定相同种子) | 游戏、模拟、动画 |
| CSPRNG(密码学安全 PRNG) | 基于密码学原语(如 AES-CTR、ChaCha20) | 输出不可预测,满足安全要求 | 密码学、安全令牌 |
② V8 的 xorshift128+ 算法
Chrome 49+ 起,V8 使用 xorshift128+ 算法实现 Math.random(),它是 xorshift 算法的改进版,具有极短的代码长度和良好的随机性质量:
xorshift128+ 状态:两个 64 位无符号整数 (s0, s1)
每次调用生成一个随机数:
t = s0
s0 = s1
t ^= t << 23 // 左移并异或
t ^= t >> 17 // 右移并异或
t ^= s1 ^ (s1 >> 26)
s1 = t
return s0 + t // 返回 (s0 + t) 的低 64 位
js
// xorshift128+ 的 JavaScript 模拟(仅用于理解原理)
function PRNG() {
var s = [Date.now() >>> 0, (Math.random() * 0xFFFFFFFF) >>> 0];
return function() {
var t = s[0];
s[0] = s[1];
t ^= t << 23;
t ^= t >>> 17;
t ^= s[1] ^ (s[1] >>> 26);
s[1] = t;
return (s[0] + t) >>> 0;
};
}
var rand = PRNG();
console.log(rand()); // 每次调用返回不同的 32 位整数
💡 代码解析
代码片段 含义 var s = [Date.now() >>> 0, ...]用当前时间戳作为初始种子(state0),再用另一个随机值作为 state1; >>> 0将值截断为 32 位无符号整数t ^= t << 23XOR-shift 核心操作:将 t左移 23 位后与自身异或,使低位和高位产生混沌关联,增强随机性t ^= t >>> 17右移异或:逆方向混合比特,消除前一步左移操作引入的模式 t ^= s[1] ^ (s[1] >>> 26)引入另一个状态 s[1]的影响,使两个 64 位状态字互相影响,输出的统计特性通过了 BigCrush 测试(s[0] + t) >>> 0对两个状态求和作为输出, >>> 0确保结果是 32 位无符号整数;真实 V8 实现中会除以2^32归一化到[0,1)
③ 为什么 Math.random() 不能用于密码学?
PRNG 有两个关键弱点:
- 状态可还原性:xorshift128+ 的内部状态只有 128 位,理论上观察足够多的输出后可以重建状态,从而预测未来输出
- 种子可预测性:浏览器初始化种子时使用的是当前时间等有限熵源,攻击者在特定条件下可能猜测种子
js
// ❌ 不安全:用 Math.random() 生成密码
var password = Math.random().toString(36).slice(2); // 可预测!
// ✅ 安全:用 Web Crypto API(基于操作系统的 CSPRNG)
var array = new Uint8Array(16);
crypto.getRandomValues(array); // 操作系统提供的密码学安全随机数
var secureToken = Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
💡 代码解析
代码片段 含义 Math.random().toString(36).slice(2)不安全toString(36)将小数转为 36 进制字符串,slice(2)去掉"0."前缀;但 PRNG 内部状态有限,输出可预测new Uint8Array(16)创建 16 字节(128位)的类型化数组,准备接收随机字节 crypto.getRandomValues(array)Web Crypto API,底层调用操作系统的 /dev/urandom或CryptGenRandom,获取真正密码学安全的随机字节b.toString(16).padStart(2, '0')将每个字节(0~255)转为两位十六进制字符串,不足两位补零,最终拼接成 32 个字符的十六进制令牌
④ 随机数的统计分布
Math.random() 返回 [0, 1) 上的均匀分布(Uniform Distribution)。但很多实际场景需要其他分布:
js
// 均匀分布 → 正态分布(Box-Muller 变换)
function randomGaussian(mean, stdDev) {
var u1 = Math.random(), u2 = Math.random();
var z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
return mean + z * stdDev;
}
// 应用:模拟身高、体重、测量误差等自然现象(中心极限定理)
// 指数分布(模拟等待时间)
function randomExponential(rate) {
return -Math.log(1 - Math.random()) / rate;
}
// 应用:模拟用户到达间隔、网络请求响应时间
💡 代码解析
代码片段 含义 Math.sqrt(-2 * Math.log(u1))Box-Muller 变换第一步:将均匀分布的 u1通过-2ln(u1)的平方根映射,生成正态分布的幅度分量Math.cos(2 * Math.PI * u2)第二步:用 u2生成均匀分布的相位角,与幅度分量相乘得到标准正态随机数z(均值0,标准差1)mean + z * stdDev将标准正态分布变换为指定均值和标准差的正态分布(线性变换) -Math.log(1 - Math.random()) / rate指数分布的逆变换采样(Inverse CDF):对均匀分布取对数变换, rate是期望到达率(λ),结果是平均等待时间1/λ
6.2 名词解释
| 术语 | 定义 |
|---|---|
Math.PI |
圆周率 π ≈ 3.141592653589793,精确到 15 位有效数字 |
Math.E |
自然常数 e ≈ 2.718281828459045,自然对数的底数 |
Math.SQRT2 |
√2 ≈ 1.4142135623730951 |
Math.LN2 |
ln(2) ≈ 0.6931471805599453 |
| 向下取整(floor) | 不大于给定数字的最大整数,沿负无穷方向取整 |
| 向上取整(ceil) | 不小于给定数字的最小整数,沿正无穷方向取整 |
| 截断取整(trunc) | ES6,截去小数部分,向零方向取整(与 floor 对负数行为不同) |
Math.random() |
返回 [0, 1) 范围内的伪随机浮点数,基于 PRNG(伪随机数生成器)算法 |
Math.hypot() |
返回各参数平方和的平方根(勾股定理),Math.hypot(3, 4) === 5 |
Math.log2() / Math.log10() |
ES6,以 2/10 为底的对数,比 Math.log(x)/Math.log(2) 更精确 |
Math.clz32() |
ES6,返回 32 位整数表示中前导零的个数,底层位运算优化用 |
Math.sign() |
ES6,返回数字符号:正数→1,负数→-1,零→0 |
6.3 方法速查表
| 方法 | 说明 | 示例 | 结果 |
|---|---|---|---|
Math.abs(x) |
绝对值 | Math.abs(-5) |
5 |
Math.pow(x,y) |
x 的 y 次方(等价 x**y) |
Math.pow(2,10) |
1024 |
Math.sqrt(x) |
平方根 | Math.sqrt(16) |
4 |
Math.cbrt(x) |
立方根(ES6) | Math.cbrt(27) |
3 |
Math.floor(x) |
向下取整 | Math.floor(-4.1) |
-5 |
Math.ceil(x) |
向上取整 | Math.ceil(-4.9) |
-4 |
Math.round(x) |
四舍五入 | Math.round(4.5) |
5 |
Math.trunc(x) |
截断小数(ES6) | Math.trunc(-4.9) |
-4 |
Math.max(...args) |
最大值 | Math.max(1,5,3) |
5 |
Math.min(...args) |
最小值 | Math.min(1,5,3) |
1 |
Math.random() |
伪随机浮点 [0,1) | Math.random() |
0.xxx |
Math.hypot(...args) |
各参数平方和的平方根 | Math.hypot(3,4) |
5 |
Math.sign(x) |
符号函数(ES6) | Math.sign(-3) |
-1 |
Math.log(x) |
自然对数 ln(x) | Math.log(Math.E) |
1 |
Math.log2(x) |
以 2 为底的对数(ES6) | Math.log2(8) |
3 |
Math.log10(x) |
以 10 为底的对数(ES6) | Math.log10(1000) |
3 |
Math.sin/cos/tan(x) |
三角函数(参数为弧度) | Math.sin(Math.PI/2) |
1 |
6.4 取随机数公式推导
js
// ════════ 公式一:0 到 n 的随机整数(含 0 含 n)════════
// Math.random() 取值 [0, 1)
// * (n+1) 得到 [0, n+1)
// floor 得到 [0, n] 的整数
function randomInt0toN(n) {
return Math.floor(Math.random() * (n + 1));
}
console.log(randomInt0toN(9)); // 0 ~ 9
// ════════ 公式二:m 到 n 的随机整数(含 m 含 n)════════
// 先在 [0, n-m] 范围内取随机数,再加偏移量 m
function randomIntMtoN(m, n) {
return Math.floor(Math.random() * (n - m + 1)) + m;
}
console.log(randomIntMtoN(5, 10)); // 5 ~ 10
// ════════ 从数组中随机取一个元素 ════════
function randomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// ════════ 生成随机十六进制颜色 ════════
function randomHexColor() {
return '#' + Math.floor(Math.random() * 0xFFFFFF)
.toString(16).padStart(6, '0');
}
// ════════ 随机洗牌(Fisher-Yates 算法)════════
function shuffle(arr) {
var result = arr.slice(); // 不修改原数组
for (var i = result.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = result[i];
result[i] = result[j];
result[j] = temp;
}
return result;
}
console.log(shuffle([1, 2, 3, 4, 5])); // 随机顺序
💡 代码解析
代码片段 含义 Math.floor(Math.random() * (n + 1))random()返回[0,1),乘以n+1得到[0, n+1),floor向下取整得到[0, n]的整数Math.floor(Math.random() * (n - m + 1)) + m先在 [0, n-m]范围取整数,再整体平移+m,得到[m, n]的随机整数Math.floor(Math.random() * 0xFFFFFF)0xFFFFFF= 16777215,在此范围随机取整,再用toString(16)转十六进制,padStart(6,'0')确保6位arr.slice()先拷贝Fisher-Yates 在原地洗牌,不拷贝会修改原数组;先浅拷贝确保原数组不变 for (i = length-1; i > 0; i--)从最后一个元素向前遍历,每次将当前位置与随机选取的更前位置交换,保证每个元素出现在任意位置的概率相等 🏢 经典使用场景 & 业务价值
场景 方法 业务价值 随机颜色主题 randomHexColor()数据可视化图表自动分配颜色,头像背景色随机生成 抽奖/随机推荐 randomItem(list)随机推荐商品、随机题库抽题、活动抽奖 问卷/考题乱序 shuffle(questions)防止考试作弊,每个人看到不同题目顺序(Fisher-Yates 保证均匀分布) 游戏地图生成 randomIntMtoN(m, n)随机生成地图障碍物位置、道具位置、NPC 坐标 AB 测试分组 Math.random() < 0.5 ? 'A' : 'B'随机将用户分入实验组/对照组,评估功能改动的效果 验证码干扰点 randomIntMtoN(0, width)生成坐标在验证码图片上随机添加干扰点,增加 OCR 识别难度
6.5 取整方法对比(负数行为差异)
| 方法 | 含义 | 4.7 |
-4.7 |
助记 |
|---|---|---|---|---|
Math.floor() |
地板 | 4 |
-5(向负∞) |
往下 |
Math.ceil() |
天花板 | 5 |
-4(向正∞) |
往上 |
Math.round() |
四舍五入 | 5 |
-5(≥0.5 向正∞) |
最近 |
Math.trunc() |
截断 | 4 |
-4(向 0) |
去掉小数 |
关键区别 :floor(-4.7) = -5(向更小的整数),而 trunc(-4.7) = -4(向零,只去掉小数部分)。
6.6 完整可运行示例(随机颜色生成器)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Math 对象演示</title>
<style>
* { box-sizing: border-box; }
body { font-family: sans-serif; max-width: 900px; margin: 0 auto; padding: 24px; background: #f5f5f5; }
.card { background: #fff; border-radius: 12px; padding: 20px; margin: 16px 0; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
h2 { color: #e65100; margin: 0 0 16px; }
.color-grid { display: flex; flex-wrap: wrap; gap: 12px; margin: 16px 0; }
.color-item { text-align: center; }
.color-box { width: 120px; height: 70px; border-radius: 8px; display: block; margin-bottom: 4px; }
.color-item span { font-size: 11px; font-family: monospace; color: #555; }
button { background: #e65100; color: #fff; border: none; padding: 10px 24px; border-radius: 8px; cursor: pointer; font-size: 14px; margin: 4px; }
button:hover { background: #bf360c; }
.formula { background: #fff3e0; padding: 12px; border-radius: 8px; font-family: monospace; font-size: 13px; margin: 12px 0; }
.math-result { background: #fce4ec; padding: 12px; border-radius: 8px; font-family: monospace; white-space: pre; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
</style>
</head>
<body>
<h1 style="color:#bf360c">Math 内置对象全解析</h1>
<div class="card">
<h2>随机颜色生成器</h2>
<div class="formula">
Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')
</div>
<button onclick="generateColors()">生成随机颜色</button>
<button onclick="generateGradient()">生成渐变色</button>
<div class="color-grid" id="colorGrid"></div>
</div>
<div class="card">
<h2>随机整数生成器</h2>
<div class="formula">
[0, n]:Math.floor(Math.random() * (n + 1))<br>
[m, n]:Math.floor(Math.random() * (n - m + 1)) + m
</div>
<label>m:<input type="number" id="minVal" value="1" style="width:70px;padding:4px"></label>
<label>n:<input type="number" id="maxVal" value="100" style="width:70px;padding:4px"></label>
<button onclick="generateRandom()">生成 10 个随机整数</button>
<div class="math-result" id="randomResult">点击生成</div>
</div>
<div class="card">
<h2>取整方法对比(输入任意小数)</h2>
<div class="grid2">
<div>
<input type="number" id="mathInput" value="-4.7" step="0.1" style="width:100%;padding:8px;border:1px solid #ffcc80;border-radius:6px;margin-bottom:8px">
<button onclick="runMath()" style="width:100%">计算各种取整</button>
</div>
<div class="math-result" id="mathResult">点击计算</div>
</div>
</div>
<script>
function generateColors() {
var grid = document.getElementById('colorGrid');
grid.innerHTML = '';
for (var i = 0; i < 8; i++) {
var color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
var item = document.createElement('div');
item.className = 'color-item';
item.innerHTML = '<div class="color-box" style="background:' + color + '"></div><span>' + color + '</span>';
grid.appendChild(item);
}
}
function generateGradient() {
var grid = document.getElementById('colorGrid');
grid.innerHTML = '';
for (var i = 0; i < 4; i++) {
var c1 = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
var c2 = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
var item = document.createElement('div');
item.className = 'color-item';
item.innerHTML = '<div class="color-box" style="background:linear-gradient(135deg,' + c1 + ',' + c2 + ')"></div><span>' + c1 + ' → ' + c2 + '</span>';
grid.appendChild(item);
}
}
function generateRandom() {
var m = parseInt(document.getElementById('minVal').value);
var n = parseInt(document.getElementById('maxVal').value);
var results = Array.from({length: 10}, function() {
return Math.floor(Math.random() * (n - m + 1)) + m;
});
document.getElementById('randomResult').textContent = '[' + m + ', ' + n + '] 的随机整数:\n' + results.join(' ');
}
function runMath() {
var x = parseFloat(document.getElementById('mathInput').value);
document.getElementById('mathResult').textContent = [
'x = ' + x,
'floor(' + x + ') = ' + Math.floor(x) + '(向 -∞)',
'ceil(' + x + ') = ' + Math.ceil(x) + '(向 +∞)',
'round(' + x + ') = ' + Math.round(x) + '(最近整数)',
'trunc(' + x + ') = ' + Math.trunc(x) + '(向 0)',
'abs(' + x + ') = ' + Math.abs(x),
'sign(' + x + ') = ' + Math.sign(x),
].join('\n');
}
generateColors();
</script>
</body>
</html>

📌 Math --- 知识特点总结
特点 描述 不是构造函数 Math是普通对象(命名空间),不能用new实例化,所有成员直接通过Math.xxx访问所有方法都是纯函数 Math 的方法不修改任何外部状态,相同输入永远得到相同输出(引用透明性) random()是伪随机基于 PRNG 算法,不适合加密安全场景;加密场景应使用 crypto.getRandomValues()取整方法的负数差异 floor和trunc对正数结果相同,对负数行为不同,务必区分随机整数公式 Math.floor(Math.random() * (n - m + 1)) + m是取[m, n]整数的标准公式,需要记忆三角函数参数单位 sin/cos/tan等的参数是弧度(radian) ,不是角度;角度转弧度:angle * Math.PI / 180max/min的数组用法对数组求最大值: Math.max(...arr)或Math.max.apply(null, arr)性能 Math 方法是引擎原生实现,通常比手写 JS 循环快得多
7 内置构造函数 Date
7.1 理论基础:时间的表示与时区
Unix 时间戳 起源于 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)这一历史约定。JavaScript 的时间戳以毫秒为单位(区别于 Unix 传统的秒),这提供了更高的时间精度。
时区复杂性:
- UTC(协调世界时):全球标准时间,不随地区/季节变化
- 本地时间:用户设备所在时区的时间
- 时区偏移:本地时间与 UTC 的差值(如 UTC+8 偏移 480 分钟)
js
// 获取当前时区偏移(分钟)
var offset = new Date().getTimezoneOffset();
console.log(offset); // 中国:-480(UTC+8 偏移 -480 分钟)
// 注意:偏移值是 UTC-本地,所以 UTC+8 得到的是 -480
💡 代码解析
代码片段 含义 new Date().getTimezoneOffset()返回 UTC 与本地时间的差值(分钟),公式为: UTC时间 - 本地时间(分钟)UTC+8 返回 -480东八区本地时间比 UTC 快 8 小时(480分钟),所以差值为 UTC - 本地 = -480注意符号方向 西方时区(UTC-5)返回正数 +300,东方时区(UTC+8)返回负数-480,与通常的 "UTC+8" 表示方向相反
Date 对象的内部表示 :Date 对象内部存储的是一个整数------UTC 时间的毫秒数(时间戳)。所有 get/set 方法都是对这个时间戳的格式化解读,区别在于是否应用本地时区偏移。
深层理论:ISO 8601 标准、闰秒与时区数据库
① ISO 8601 标准解析
ISO 8601 是国际标准化组织(ISO)于 1988 年发布的日期时间表示标准,JavaScript 的 Date.toISOString() 严格遵循此标准:
完整格式:YYYY-MM-DDTHH:mm:ss.sssZ
各字段含义:
YYYY --- 四位年份(公历)
MM --- 两位月份(01-12)
DD --- 两位日期(01-31)
T --- 日期与时间的分隔符(Temperature, 历史原因)
HH --- 两位小时(00-23,24小时制)
mm --- 两位分钟(00-59)
ss --- 两位秒数(00-60,60 保留用于闰秒)
.sss --- 三位毫秒(可选)
Z --- 时区偏移:Z 表示 UTC,+08:00 表示东八区,-05:00 表示西五区
js
var d = new Date('2023-10-15T14:30:05.123Z');
console.log(d.toISOString()); // "2023-10-15T14:30:05.123Z"(始终 UTC)
console.log(d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }));
// "2023/10/15 22:30:05"(转为东八区时间)
// 实际项目中,推荐始终以 ISO 8601 UTC 格式存储和传输时间
// 在展示层再转为用户本地时间
💡 代码解析
代码片段 含义 new Date('2023-10-15T14:30:05.123Z')Z后缀明确指定 UTC 时区,不受运行环境时区影响,跨平台行为一致.toISOString()始终输出 UTC 时间,格式严格遵循 ISO 8601,适合存储到数据库或通过 API 传输 toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })使用 Intl API 将 UTC 时间转换为东八区(中国标准时间)并按中文格式化; UTC 14:30→CST 22:30
② 时区数据库(tz database / IANA Time Zone Database)
计算机系统中的时区信息来自 IANA 时区数据库(又称 Olson 数据库),它维护了全球约 600+ 个时区的历史夏令时规则。
js
// Intl.DateTimeFormat 背后依赖 IANA 数据库
var formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York', // IANA 时区标识符
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
});
console.log(formatter.format(new Date()));
// 自动处理美国东部时间的夏令时(EDT/EST 之间切换)
💡 代码解析
代码片段 含义 new Intl.DateTimeFormat('en-US', {...})创建一个国际化日期格式化器, 'en-US'指定语言环境(影响数字分隔符、月份名称等格式),options对象控制各字段的显示格式timeZone: 'America/New_York'使用 IANA 时区标识符,而非固定偏移量(如 -05:00);IANA 标识符包含历史 DST 规则,能正确处理美东时间在 EST(UTC-5)和 EDT(UTC-4)之间的自动切换hour12: false使用 24 小时制,避免 AM/PM 歧义
时区处理的三大陷阱:
| 陷阱 | 例子 | 正确做法 |
|---|---|---|
| 夏令时(DST)跳变 | 美国 DST 切换时,2:00 AM 直接跳到 3:00 AM,2:30 AM 不存在 | 使用 IANA 标识符而非固定偏移量,让库处理 DST |
| 月末日期溢出 | new Date(2023, 0, 31); setMonth(1) → 2 月没有 31 日 |
先 setDate(1) 再 setMonth,或用专业日期库 |
| 不同浏览器的解析差异 | new Date('2023-10-15') 有些浏览器视为 UTC,有些视为本地时间 |
统一使用 new Date('2023-10-15T00:00:00Z') 明确指定时区 |
③ 闰秒(Leap Second)
地球自转速度略有变化,导致 UTC 时间与天文时间(UT1)存在微小偏差。国际地球自转服务(IERS)偶尔会在 UTC 时钟上插入一个"闰秒"(23:59:60),保持两者同步。
自 1972 年至今,已累计插入 27 个闰秒
最近一次:2016-12-31 23:59:60 UTC
对 JavaScript 的影响:
- ECMAScript 规范明确规定:Date 对象**忽略闰秒**
- 规范中的一天永远是 86400000 毫秒(不考虑闰秒)
- Unix 时间戳同样不考虑闰秒("闰秒涂抹"技术)
- 因此 JavaScript 的时间计算在闰秒发生的那一秒会与真实 UTC 有 1 秒误差
④ JavaScript 时间系统的设计局限
js
// Date 对象的已知问题(等待 Temporal API 修复):
// 1. 月份从 0 开始(API 设计不一致)
// 2. 没有不可变的日期类型(类似字符串那样)
// 3. DST 处理不一致
// 4. 解析行为依赖实现
// ES2025 候选 API:Temporal(更现代、更完整的日期时间 API)
// Temporal.PlainDate, Temporal.ZonedDateTime 等(目前需要 polyfill)
7.2 名词解释
| 术语 | 定义 |
|---|---|
| Unix 时间戳 | 从 1970-01-01 00:00:00 UTC 至今的毫秒数(JS)或秒数(传统 Unix) |
| UTC | 协调世界时(Universal Time Coordinated),国际标准时间基准 |
| 本地时间 | 根据用户设备时区设置显示的时间 |
getTime() |
获取 Date 对象对应的 Unix 时间戳(毫秒) |
Date.now() |
静态方法,当前时刻的时间戳,比 new Date().getTime() 更简洁高效 |
| 月份从 0 开始 | JavaScript 月份值 0~11 对应 1~12 月,这是 C 语言 tm_mon 的历史遗留设计 |
toISOString() |
返回符合 ISO 8601 标准的字符串(YYYY-MM-DDTHH:mm:ss.sssZ),始终是 UTC 时间 |
toLocaleString() |
根据系统本地化设置格式化日期时间 |
Intl.DateTimeFormat |
国际化 API,提供更灵活的日期格式化能力 |
7.3 实例化方式全解
js
// ① 当前时间(无参数)
var now = new Date();
// ② ISO 8601 字符串(最推荐,跨平台一致)
var d1 = new Date('2023-10-15T14:30:05.000Z'); // UTC 时间
var d2 = new Date('2023-10-15T14:30:05'); // 本地时间(行为因浏览器而异)
var d3 = new Date('2023-10-15'); // 只有日期(视为 UTC 午夜)
// ③ 数字参数(年, 月[0-11], 日, 时, 分, 秒, 毫秒)全部本地时间
var d4 = new Date(2023, 9, 1); // 2023年10月1日(月份 9 = 10月!)
var d5 = new Date(2023, 9, 1, 8, 0, 0); // 2023年10月1日 08:00:00
// ④ 时间戳(毫秒,UTC)
var d6 = new Date(0); // 1970-01-01T00:00:00.000Z
var d7 = new Date(Date.now()); // 等同于 new Date()
// ⑤ 复制 Date 对象
var d8 = new Date(now.getTime()); // 复制一个 Date 对象
💡 代码解析
代码片段 含义 new Date()无参数创建表示当前时刻的 Date 对象,内部时间戳等于 Date.now()new Date('2023-10-15T14:30:05.000Z')ISO 8601 格式,末尾 Z明确为 UTC,最推荐的字符串创建方式,跨浏览器行为一致new Date('2023-10-15T14:30:05')无 Z无时区后缀时行为因浏览器而异:现代浏览器视为本地时间,ES5 规范视为 UTC;应避免 new Date(2023, 9, 1)第二参数月份是 9(实际 10 月),月份从0开始是 JavaScript 的历史遗留设计(源自 Java 的Calendar类)new Date(0)时间戳为 0,即 Unix 纪元起点: 1970-01-01T00:00:00.000Znew Date(now.getTime())通过 getTime()获取时间戳再创建新 Date,实现日期对象的深拷贝(直接赋值只复制引用)
7.4 获取与设置方法
js
var d = new Date('2023-10-15T14:30:05.123');
// 获取(本地时间)
console.log(d.getFullYear()); // 2023
console.log(d.getMonth()); // 9(实际是 10 月,月份从 0 开始!)
console.log(d.getMonth() + 1); // 10(正确的月份显示)
console.log(d.getDate()); // 15(月中第几天,1-31)
console.log(d.getDay()); // 0~6(0=周日,1=周一...6=周六)
console.log(d.getHours()); // 14
console.log(d.getMinutes()); // 30
console.log(d.getSeconds()); // 5
console.log(d.getMilliseconds()); // 123
console.log(d.getTime()); // 时间戳(毫秒)
console.log(d.getTimezoneOffset()); // 时区偏移(分钟),UTC+8 返回 -480
// 获取(UTC 时间)
console.log(d.getUTCFullYear()); // UTC 年
console.log(d.getUTCHours()); // UTC 小时(比 UTC+8 少 8 小时)
// 设置(直接修改 Date 对象)
d.setFullYear(2024);
d.setMonth(0); // 1 月
d.setDate(1);
d.setHours(0, 0, 0, 0); // 时、分、秒、毫秒全设为 0(常用于日期比较)
// 静态方法
console.log(Date.now()); // 当前时间戳(毫秒)
console.log(Date.parse('2023-10-15T14:30:05.000Z')); // 将字符串解析为时间戳
💡 代码解析
代码片段 含义 d.getMonth()→9Date 内部存储 10 月,但 getMonth()返回9(月份从 0 计);显示时需要+1:d.getMonth() + 1d.getDay()→0~60表示周日,1~6依次为周一到周六(源自美国习惯,周日为一周第一天)d.getHours()与d.getUTCHours()getHours()返回本地时区的小时;getUTCHours()返回 UTC 小时;在 UTC+8 下两者相差 8 小时setHours(0, 0, 0, 0)四个参数依次设置:时、分、秒、毫秒,全设为 0 可将 Date 对象重置为当天 00:00:00.000,常用于日期(不含时间)比较 getTimezoneOffset()→-480返回本地时区偏移分钟数(UTC - 本地),UTC+8 区域返回 -480Date.now()静态方法,比 new Date().getTime()高效,不需要创建 Date 对象,直接返回当前时间戳
7.5 日期格式化与工具函数
js
// ════════ 标准格式化函数 ════════
function pad(n) { return String(n).padStart(2, '0'); }
function formatDateTime(d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) +
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
console.log(formatDateTime(new Date())); // "2023-10-15 14:30:05"
// ════════ 星期名称 ════════
function getWeekdayName(date) {
var days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return days[date.getDay()];
}
// ════════ 计算两日期之间的天数 ════════
function daysBetween(date1, date2) {
var MS_PER_DAY = 1000 * 60 * 60 * 24;
return Math.round(Math.abs(date2.getTime() - date1.getTime()) / MS_PER_DAY);
}
console.log(daysBetween(new Date('2023-01-01'), new Date('2023-12-31'))); // 364
// ════════ 判断是否为闰年 ════════
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
console.log(isLeapYear(2024)); // true
console.log(isLeapYear(1900)); // false(能被100整除但不能被400整除)
console.log(isLeapYear(2000)); // true(能被400整除)
// ════════ 获取本月第一天和最后一天 ════════
function getMonthRange(date) {
var first = new Date(date.getFullYear(), date.getMonth(), 1);
var last = new Date(date.getFullYear(), date.getMonth() + 1, 0); // 下月第 0 天 = 本月最后一天
return { first: first, last: last };
}
// ════════ 计时(性能测量)════════
var start = Date.now();
// ... 耗时操作 ...
var elapsed = Date.now() - start;
console.log('耗时:' + elapsed + 'ms');
// 更高精度:performance.now()(微秒级)
💡 代码解析
代码片段 含义 String(n).padStart(2, '0')最常用的日期补零技巧: 5→'05',14保持不变。padStart只在不足指定长度时才填充d.getDay()→ 索引查 weekdays 数组getDay()返回 0-6(0=周日),用这个值作为数组索引取对应的中文名称Math.abs(date2.getTime() - date1.getTime())时间戳相减得毫秒差,除以单日毫秒数 (1000*60*60*24)得天数差,Math.abs确保结果为正数year % 4 === 0 && year % 100 !== 0公历闰年规则一:能被4整除但不能被100整除(如 2024) ` new Date(year, month + 1, 0)构造"下月第0天",在 JS 中第0天会自动回退为上月最后一天,这是获取当月最后一天的经典技巧 Date.now() - start性能计时:记录开始时间戳,操作后再取当前时间戳,差值即为耗时毫秒数 🏢 经典使用场景 & 业务价值
场景 方法组合 业务价值 订单创建时间展示 formatDateTime(new Date(timestamp))将后端返回的时间戳格式化为用户可读格式 2023-10-15 14:30:05日历组件 getMonthRange+getDay确定月历排版获取当月的第一天是周几、共多少天,正确排列日历格子 活动倒计时 targetDate.getTime() - Date.now()计算距离活动开始/结束的剩余毫秒数,再分解为天/时/分/秒展示 用户注册天数 daysBetween(registerDate, new Date())展示"您已使用本产品 X 天",增加用户留存感 前端性能监控 Date.now()打点计时记录页面关键路径的耗时,发现性能瓶颈(更精确用 performance.now())日期范围校验 比较两个 Date 的 getTime()验证用户选择的开始日期不晚于结束日期,日期选择器的核心逻辑
7.6 完整可运行示例(数字时钟)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Date 构造函数演示 ------ 数字时钟</title>
<style>
* { box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #1a237e, #283593, #1565c0); color: #fff; margin: 0; }
.card { background: rgba(255,255,255,0.08); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.15); border-radius: 20px; padding: 40px 60px; margin: 20px; text-align: center; }
.date-display { font-size: 1.4rem; opacity: 0.85; margin-bottom: 16px; letter-spacing: 2px; }
.time-display { font-size: 5rem; font-weight: 700; letter-spacing: 8px; text-shadow: 0 0 30px rgba(100,181,246,0.8); font-variant-numeric: tabular-nums; }
.weekday { font-size: 1.2rem; opacity: 0.75; margin-top: 8px; }
.info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 32px; }
.info-item { background: rgba(255,255,255,0.1); border-radius: 12px; padding: 12px; }
.info-item .label { font-size: 0.75rem; opacity: 0.6; text-transform: uppercase; letter-spacing: 1px; }
.info-item .value { font-size: 1.5rem; font-weight: 600; margin-top: 4px; }
.timestamp { font-size: 0.85rem; opacity: 0.5; margin-top: 24px; font-family: monospace; }
</style>
</head>
<body>
<div class="card">
<div class="date-display" id="dateDisplay"></div>
<div class="time-display" id="timeDisplay"></div>
<div class="weekday" id="weekdayDisplay"></div>
<div class="info-grid">
<div class="info-item"><div class="label">年</div><div class="value" id="yearVal"></div></div>
<div class="info-item"><div class="label">月</div><div class="value" id="monthVal"></div></div>
<div class="info-item"><div class="label">日</div><div class="value" id="dayVal"></div></div>
</div>
<div class="timestamp" id="tsDisplay"></div>
</div>
<script>
var weekdays = ['周日','周一','周二','周三','周四','周五','周六'];
function pad(n) { return String(n).padStart(2, '0'); }
function updateClock() {
var now = new Date();
document.getElementById('dateDisplay').textContent = now.getFullYear() + ' 年 ' + pad(now.getMonth()+1) + ' 月 ' + pad(now.getDate()) + ' 日';
document.getElementById('timeDisplay').textContent = pad(now.getHours()) + ' : ' + pad(now.getMinutes()) + ' : ' + pad(now.getSeconds());
document.getElementById('weekdayDisplay').textContent = weekdays[now.getDay()];
document.getElementById('yearVal').textContent = now.getFullYear();
document.getElementById('monthVal').textContent = pad(now.getMonth()+1);
document.getElementById('dayVal').textContent = pad(now.getDate());
document.getElementById('tsDisplay').textContent = 'Timestamp: ' + Date.now() + ' ms';
}
updateClock();
setInterval(updateClock, 1000);
</script>
</body>
</html>

📌 Date --- 知识特点总结
特点 描述 月份从 0 开始 这是 JavaScript 最著名的"坑"之一,月份值 0~11 对应 1~12 月,获取/设置时务必 +1/-1内部存储时间戳 Date 对象本质是一个 UTC 毫秒数,所有方法都是对这个整数的格式化 本地时间 vs UTC getHours()返回本地时间的小时,getUTCHours()返回 UTC 时间;跨时区应用必须注意ISO 8601 字符串最可靠 构造 Date 时,ISO 8601 格式( YYYY-MM-DDTHH:mm:ss.sssZ)在各浏览器中行为最一致Date.now()更高效只需要时间戳时用 Date.now()而非new Date().getTime(),避免创建对象的开销padStart 格式化 String(n).padStart(2, '0')是格式化日期分量(月、日、时、分、秒)的标准技巧日期运算靠时间戳 日期差值计算:先 getTime()得到毫秒数,再做减法,最后除以相应毫秒数得到天/小时等推荐使用第三方库 复杂日期操作(时区转换、相对时间等)推荐 day.js(仅 2KB)或date-fns
8 数组 Array:修改器方法
8.1 理论基础:数组的内存模型
JavaScript 数组(Array)并非传统意义上的连续内存数组,而是一个特殊的对象,其键名为数字索引。
js
// 数组本质是对象
var arr = [1, 2, 3];
console.log(typeof arr); // "object"
console.log(arr instanceof Object); // true
// 可以给数组添加非数字属性(但不推荐)
arr.customProp = 'hello';
console.log(arr.customProp); // 'hello'
console.log(arr.length); // 3(非数字属性不计入 length)
💡 代码解析
代码片段 含义 typeof arr→"object"数组本质上是对象, typeof无法区分数组和普通对象;应使用Array.isArray()来判断数组arr instanceof Object→true数组的原型链: arr → Array.prototype → Object.prototype → null,经过Object.prototypearr.customProp = 'hello'有效数组是对象,可以像普通对象一样添加任意非数字属性;但这是反模式,会干扰数组迭代方法 arr.length仍为3length属性只追踪数字索引(0、1、2...),字符串键名属性不计入length
V8 引擎的数组优化:V8 对"密集数组"(SMI Array / PACKED Array)有特殊优化------当数组元素类型统一(如全是整数),V8 会使用高效的底层数组存储,接近 C 语言数组的性能。一旦数组出现空洞(稀疏数组)或混合类型,V8 会退化为字典模式,性能下降。
深层理论:V8 数组元素类型系统(Element Kinds)
V8 为数组维护一套**元素类型(Element Kinds)**系统,决定数组在内存中的存储方式和访问速度。这套系统是理解 JavaScript 数组性能的关键。
① 元素类型的层级(从最快到最慢)
PACKED_SMI_ELEMENTS ← 最快:只含小整数(Small Integer,31位)
PACKED_DOUBLE_ELEMENTS ← 快:含浮点数(含 SMI)
PACKED_ELEMENTS ← 中:含任意 JS 值(含 double)
HOLEY_SMI_ELEMENTS ← 稍慢:稀疏小整数数组(有"空洞")
HOLEY_DOUBLE_ELEMENTS ← 慢:稀疏浮点数组
HOLEY_ELEMENTS ← 最慢:稀疏混合类型数组
DICTIONARY_ELEMENTS ← 极慢:超大索引或超稀疏数组,退化为哈希表存储
重要原则:元素类型只能单向降级,不能自动升级!
js
// ① PACKED_SMI_ELEMENTS(最优状态)
var arr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
// ② 插入浮点数 → 降级到 PACKED_DOUBLE_ELEMENTS(不可逆!)
arr.push(4.5); // PACKED_DOUBLE_ELEMENTS
arr.pop(); // 移除浮点数,但类型不会回升
// arr 仍然是 PACKED_DOUBLE_ELEMENTS,即使现在全是整数
// ③ 插入字符串 → 降级到 PACKED_ELEMENTS
arr.push('hello'); // PACKED_ELEMENTS
// ④ 创建"空洞" → 降级到 HOLEY_*
arr[100] = 'far'; // HOLEY_ELEMENTS(索引 4-99 是空洞)
💡 代码解析
代码片段 含义 var arr = [1, 2, 3]→PACKED_SMI_ELEMENTS所有元素是连续整数,V8 用 C++ 整数数组存储,访问速度极快 arr.push(4.5)→ 降级到PACKED_DOUBLE_ELEMENTS插入浮点数触发类型扩展:V8 将整个数组从 SMI 数组转为 Double 数组(每个元素占用更多内存),此降级不可逆 arr.pop()后仍是PACKED_DOUBLE_ELEMENTS即使移除浮点数,V8 也不会回升类型(回升成本高且不安全),元素类型只会降级,从不升级 arr.push('hello')→PACKED_ELEMENTS混入字符串,V8 切换为通用对象指针数组,失去数值专用优化 arr[100] = 'far'→HOLEY_ELEMENTS跨越式赋值使索引 4~99 变为"空洞"(hole),V8 需要额外检查每个位置是否为空洞
② 稀疏数组(Sparse Array)的性能陷阱
js
// 稀疏数组:索引不连续,V8 用哈希表存储
var sparse = new Array(1000); // 创建 1000 个空位的稀疏数组(HOLEY)
sparse[0] = 1; // 仅第 0 位有值
// 密集数组:所有索引连续填充
var dense = new Array(1000).fill(0); // PACKED_SMI_ELEMENTS(高效)
// 或者:
var dense2 = Array.from({ length: 1000 }, () => 0); // 同样是密集数组
// 遍历稀疏数组的性能消耗是密集数组的数倍
// forEach、map 在稀疏数组上还需要检查每个位置是否有值
💡 代码解析
代码片段 含义 new Array(1000)创建有 1000 个空位的稀疏数组,索引 0~999 全为"空洞"(hole),V8 标记为 HOLEY_ELEMENTSnew Array(1000).fill(0)fill(0)将所有空洞填充为整数0,使数组变为密集的PACKED_SMI_ELEMENTSArray.from({ length: 1000 }, () => 0)Array.from每个槽位执行一次工厂函数,保证每个槽位都有真实值,结果是密集数组稀疏数组性能损耗 迭代稀疏数组时,V8 需要对每个索引执行 HasProperty检查(比直接读取慢 2~10 倍)
③ 实践建议:保持数组类型稳定
js
// ✅ 好的做法:预先确定类型,保持一致
var scores = [85, 92, 78, 96]; // 全整数,PACKED_SMI_ELEMENTS,最快
var prices = [9.99, 19.99, 4.50]; // 全浮点,PACKED_DOUBLE_ELEMENTS,快
// ❌ 不好的做法:混合类型
var mixed = [1, 'two', { three: 3 }, null]; // PACKED_ELEMENTS,最慢
// ✅ 预分配密集数组再赋值
var result = new Array(data.length); // 先分配空间
for (var i = 0; i < data.length; i++) {
result[i] = process(data[i]); // 顺序赋值,保持密集
}
// ❌ 避免用 delete 删除数组元素(会产生空洞)
var arr = [1, 2, 3, 4, 5];
delete arr[2]; // arr = [1, 2, empty, 4, 5],产生空洞,降级到 HOLEY
arr.splice(2, 1); // ✅ 正确删除方式,不产生空洞
💡 代码解析
代码片段 含义 var scores = [85, 92, 78, 96]全为整数字面量,V8 推断为 PACKED_SMI_ELEMENTS,在所有模式中性能最优var mixed = [1, 'two', {...}, null]混合类型必然是 PACKED_ELEMENTS(指针数组),每次访问需要类型检查和装箱操作new Array(data.length)+ 顺序赋值预分配空间但随即按顺序填充,V8 可能将其视为密集数组,避免多次扩容(比不断 push性能好)delete arr[2]产生空洞delete将索引 2 变为empty(hole),不改变length;数组变为HOLEY,后续所有迭代方法(map/filter)都需要跳过空洞splice(2, 1)正确删除splice删除元素并移动后续元素,保持数组连续(密集),不产生空洞
④ Array.sort() 的算法实现:TimSort
ECMAScript 2019 规范要求 sort() 必须是稳定排序(Stable Sort) 。V8 7.0+ 使用 TimSort 算法,这是 Python 的内置排序算法,同样被 Java 和 Android 使用。
TimSort 的设计思想:
TimSort = Merge Sort(归并排序)+ Insertion Sort(插入排序)的混合策略
核心概念:Run(自然有序段)
1. 扫描数组,找到已经有序的子序列(Run)
2. 如果 Run 长度太短(< minRun,通常 32~64),用插入排序扩展它
(插入排序在小数组上因缓存命中率高,实际速度胜过快排)
3. 将多个 Run 用 MergeSort 策略合并
时间复杂度:
最坏情况:O(n log n)
最好情况:O(n)(数组已经有序,直接找到一个 Run 就结束)
稳定排序:相等元素的原有顺序严格保留
为什么比纯快排好?
- 对真实数据中"部分有序"的情况有极佳的自适应性
- 稳定排序,快排不稳定(等值元素可能换位)
- 对已排序或近似有序的数组近乎 O(n)
js
// 稳定排序的意义
var students = [
{ name: 'Alice', score: 90 },
{ name: 'Bob', score: 85 },
{ name: 'Carol', score: 90 }, // 与 Alice 同分
];
students.sort((a, b) => b.score - a.score); // 按分数降序
// 稳定排序保证:Alice 和 Carol 同分时,Alice 仍然在 Carol 前面(保持原始相对顺序)
// 若用不稳定排序,Carol 可能出现在 Alice 前面
💡 代码解析
代码片段 含义 (a, b) => b.score - a.score比较函数返回负数 时 a在前,返回正数 时b在前;b.score - a.score实现降序(高分在前)Alice 在 Carol 前(稳定排序) Alice 和 Carol 同分(90), b.score - a.score = 0,稳定排序保留原始顺序,Alice(索引0)仍在 Carol(索引2)前面TimSort 自适应 若 students数组本来已按分数大致排好,TimSort 能以接近 O(n) 的速度完成排序(识别"Run"直接合并)ES2019 稳定性保证 在 ES2019 之前,规范不要求 sort稳定,Chrome 旧版对长度 ≤10 的数组用插入排序(稳定),长数组用快排(不稳定);现代引擎已统一为稳定排序
修改器方法(Mutator Methods) 是数组中会直接修改调用者(原数组)的方法,这与**访问器方法(Accessor Methods)**形成对比。
8.2 名词解释
| 术语 | 定义 |
|---|---|
| 修改器方法(Mutator Methods) | 调用后会直接改变原数组本身,如 push、pop、splice、sort、reverse |
push() |
在数组末尾追加一个或多个元素,返回新数组的长度 |
pop() |
删除并返回数组最后一个元素(栈操作:LIFO) |
unshift() |
在数组开头插入一个或多个元素,返回新数组的长度(性能比 push 低,需移动所有元素) |
shift() |
删除并返回数组第一个元素(性能比 pop 低,需移动所有元素) |
splice(start, count, ...items) |
万能方法:从 start 删除 count 个元素,并可插入新元素。返回被删除元素的数组 |
sort(compareFn) |
对数组元素排序,默认按 UTF-16 字符串升序(数字排序需要比较函数) |
reverse() |
翻转数组元素顺序(原地操作),返回翻转后的数组 |
fill(value, start, end) |
ES6,用 value 填充数组的 start 到 end 位置 |
| 稳定排序(Stable Sort) | ES2019 后规范要求 sort() 必须是稳定的,相等元素的原有相对顺序不变 |
toSorted() / toReversed() |
ES2023,非破坏性版本的 sort/reverse,返回新数组而不修改原数组 |
8.3 方法详解与对比
js
var arr = ['A', 'B', 'C', 'D', 'E'];
// ════════ push / pop ------ 栈结构(LIFO)════════
arr.push('F', 'G'); // 末尾追加,返回新长度 7
var last = arr.pop(); // 删除末尾,返回 'G'
// arr: ['A','B','C','D','E','F']
// ════════ unshift / shift ------ 队列结构(FIFO)配合 push ════════
arr.unshift('Z'); // 开头插入,返回新长度
var first = arr.shift(); // 删除开头,返回 'Z'
// ════════ splice ------ 最强大的修改器方法 ════════
var arr2 = [10, 20, 30, 40, 50];
// 仅删除:从索引 1 开始删除 2 个元素
var removed = arr2.splice(1, 2);
console.log(removed); // [20, 30]
console.log(arr2); // [10, 40, 50]
// 仅插入:从索引 1 插入,不删除(deleteCount = 0)
arr2.splice(1, 0, 'a', 'b');
console.log(arr2); // [10, 'a', 'b', 40, 50]
// 替换:删除并插入
arr2.splice(1, 2, 'X');
console.log(arr2); // [10, 'X', 40, 50]
// 从末尾删除(负数索引)
arr2.splice(-1, 1);
console.log(arr2); // [10, 'X', 40]
// ════════ sort ------ 排序 ════════
var nums = [10, 1, 5, 20, 3];
// ⚠️ 默认排序按 UTF-16 字符串!数字排序结果错误
nums.sort();
console.log(nums); // [1, 10, 20, 3, 5](字符串比较:'10' < '20' < '3')
// ✅ 正确:传比较函数
nums.sort(function(a, b) { return a - b; }); // 升序(a-b < 0 时 a 在前)
nums.sort(function(a, b) { return b - a; }); // 降序
// 按对象属性排序(稳定排序,ES2019+)
var items = [
{ name: 'Cherry', price: 30 },
{ name: 'Apple', price: 10 },
{ name: 'Banana', price: 20 },
];
items.sort(function(a, b) { return a.price - b.price; }); // 按价格升序
// ════════ reverse ════════
var letters = ['a', 'b', 'c', 'd'];
letters.reverse(); // ['d', 'c', 'b', 'a']
// ════════ ES2023 非破坏性方法 ════════
var original = [3, 1, 4, 1, 5];
var sorted = original.toSorted(function(a, b) { return a - b; }); // 新数组
var reversed = original.toReversed(); // 新数组
console.log(original); // [3, 1, 4, 1, 5](未修改!)
console.log(sorted); // [1, 1, 3, 4, 5]
💡 代码解析
代码片段 含义 arr.push('F', 'G')可以一次推入多个元素 ,全部追加到末尾,返回新数组的 lengtharr2.splice(1, 2)从索引 1 开始删除 2 个元素,被删除的元素作为数组返回。删除后原数组长度减少 2 arr2.splice(1, 0, 'a', 'b')deleteCount=0表示不删除,在索引 1 处纯插入'a'和'b',后面的元素自动后移[10,1,5].sort()→[1,10,5]默认按字符串 UTF-16 排序, '10' < '5'因为'1' < '5',这是排序最常见的 bug 来源sort((a,b) => a-b)比较函数返回负数则 a 在前(升序),返回正数则 b 在前; a-b是升序的简洁写法original.toSorted()ES2023,返回新数组 ,原数组 original保持不变,适合 React/Vue 等不可变数据流场景🏢 经典使用场景 & 业务价值
场景 方法 业务价值 消息队列(先进先出) push入队 +shift出队实现任务队列、消息推送队列,按顺序处理事件 浏览器历史记录栈 push入栈 +pop出栈实现路由前进/后退、撤销/重做操作, pop返回最近一条记录动态列表项的增删 splice(index, 1)删除用户删除表格某一行、待办列表中删除某个任务 数组中间插入元素 splice(index, 0, newItem)插队、在指定位置插入新评论/新数据 表格列表排序 sort((a,b) => a.field - b.field)点击表格列头按该列升/降序排列数据 轮播图乱序 shuffle(images)(内部用 splice)每次刷新页面呈现不同图片顺序,增加内容新鲜感
8.4 push/pop vs unshift/shift 性能差异
unshift / shift ------ O(n) 需移动所有元素
unshift('Z')
shift()
A, B, C
Z, A, B, C\] 所有元素右移 \[Z, A, B, C
A, B, C\] 所有元素左移 push / pop ------ O(1) 摊销复杂度 push('D') pop() \[A, B, C
A, B, C, D
A, B, C, D
A, B, C\] 返回 D > **性能建议** :在高频操作场景(如 10000+ 元素的数组),优先使用 `push/pop`(O(1)),而非 `unshift/shift`(O(n))。如果需要高效的队列操作,考虑使用链表或双端队列数据结构。 #### 8.5 sort 比较函数原理 \< 0(负数) = 0(零) \> 0(正数) sort(compareFn) 取两个元素 a, b 调用 compareFn(a, b) 返回值? a 排在 b 前面 保持相对位置(稳定排序 ES2019+) b 排在 a 前面 升序:return a - b 降序:return b - a 相等元素:原顺序不变 #### 8.6 完整可运行示例(待办列表) ```html
待办列表
演示:push · pop · splice · sort · reverse
商品筛选器
演示 filter · map · reduce · every · some 等数组迭代方法
连字符 → 驼峰命名转换器
字符串翻转(Array reverse 方法)
核心原理:split('') 将字符串分割为字符数组 → reverse()(数组修改器方法)→ join('') 拼回字符串
```  > **💡 代码解析** > > | 代码片段 | 含义 | > |---------------------------------------------------------|----------------------------------------------------------| > | `s.split('').reverse().join('')` | 字符串翻转三部曲:单字符数组化 → 数组反转 → 重新拼合。字符串本身无 `reverse` 方法,借用数组完成 | > | `s.split(' ').reverse().join(' ')` | 翻转的是**单词**:按空格分割,反转单词顺序,再用空格拼合;适合英文句子单词级翻转 | > | `.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]/g, '')` | 回文检测预处理:统一小写,去除标点/空格等非字母数字字符,`\u4e00-\u9fa5` 保留汉字范围 | > | `s === reversed` | 预处理后的字符串与翻转版本直接比较,相等则为回文;值类型 `===` 比较字符串内容 | > > **🏢 业务价值** :字符串翻转是算法面试高频题,核心模式(`split/reverse/join`)同样用于:① 实现撤销/重做(反转操作列表);② 翻转 UI 中的排序方向;③ 编码/解密简单字符串(配合其他手段)。回文检测用于:输入校验(身份证号部分场景)、文本分析。 #### 11.3 随机抽取(作业三) ```html随机抽取工具
日期时间格式化演示
核心技巧:padStart(2, '0') 补零 · getMonth()+1 修正月份