Date/RegExp/Error/ArrayBuffer

16 阅读10分钟

一文搞懂JS四大特殊内置对象:Date/RegExp/Error/ArrayBuffer

DateRegExpErrorArrayBuffer是JavaScript中最常用的特殊内置对象,也是深拷贝、类型检测等场景中必须特殊处理的类型。很多前端开发者只会简单使用它们,却不了解其底层原理和常见坑点,导致频繁踩坑。

本文将从核心定义→底层原理→完整API→常见坑点→实际应用→深拷贝处理六个维度,带你彻底搞懂这四个对象。


一、Date:日期时间处理对象

1. 核心定义

Date是JavaScript中用于处理日期和时间的内置对象,它本质上是一个基于Unix时间戳的整数。

2. 底层原理

Date对象内部存储的是一个64位浮点数,表示自1970年1月1日00:00:00 UTC(协调世界时)以来的毫秒数

  • 正数表示1970年之后的时间
  • 负数表示1970年之前的时间
  • 最大值约为275760年,最小值约为-271821年

3. 完整常用API

(1)创建Date对象
// 1. 创建当前时间的Date对象
const now = new Date();

// 2. 从时间戳创建(最常用,跨环境一致)
const date1 = new Date(1715606400000); // 2024-05-14 00:00:00

// 3. 从日期字符串创建(不推荐,兼容性差)
const date2 = new Date('2024-05-14');
const date3 = new Date('2024-05-14T12:00:00');

// 4. 从年月日时分秒创建(月份从0开始!)
const date4 = new Date(2024, 4, 14, 12, 0, 0); // 2024年5月14日
(2)获取时间信息
const date = new Date();

// 本地时间
date.getFullYear(); // 完整年份(2024)
date.getMonth(); // 月份(0-11,0表示1月)
date.getDate(); // 日期(1-31)
date.getDay(); // 星期(0-6,0表示周日)
date.getHours(); // 小时(0-23)
date.getMinutes(); // 分钟(0-59)
date.getSeconds(); // 秒(0-59)
date.getMilliseconds(); // 毫秒(0-999)
date.getTime(); // 时间戳(最常用)

// UTC时间(与本地时区无关)
date.getUTCFullYear();
date.getUTCMonth();
// ... 其他UTC方法类似
(3)设置时间信息
const date = new Date();
date.setFullYear(2025);
date.setMonth(11); // 12月
date.setDate(25);
date.setHours(10);
date.setMinutes(30);
date.setTime(1715606400000); // 直接设置时间戳
(4)转换为字符串
const date = new Date();
date.toString(); // "Tue May 14 2024 12:00:00 GMT+0800 (中国标准时间)"
date.toLocaleString(); // "2024/5/14 12:00:00"
date.toLocaleDateString(); // "2024/5/14"
date.toLocaleTimeString(); // "12:00:00"
date.toISOString(); // "2024-05-14T04:00:00.000Z"(UTC时间,最常用)
date.getTime().toString(); // "1715606400000"(时间戳字符串)

4. 最常见的坑点

  1. 月份从0开始:这是最著名的坑,getMonth()返回0-11,0表示1月,11表示12月
  2. 年份是完整年份:不要用getYear()(已废弃),永远用getFullYear()
  3. 时区问题toLocaleString()等方法返回本地时间,toISOString()返回UTC时间
  4. 日期字符串解析不一致:不同浏览器对new Date('2024-05-14')的解析可能不同,永远优先使用时间戳

5. 实际应用场景

  • 时间格式化(推荐使用dayjsdate-fns,不要自己写)
  • 倒计时功能
  • 时间戳转换
  • 日期比较和计算

6. 深拷贝处理

// 正确的深拷贝方式
const original = new Date();
const clone = new Date(original.getTime());

二、RegExp:正则表达式对象

1. 核心定义

RegExp(Regular Expression)是用于匹配字符串中字符组合的模式,是处理字符串最强大的工具之一。

2. 底层原理

正则表达式的执行分为两个阶段:

  1. 编译阶段:将正则表达式字符串编译成内部的状态机
  2. 执行阶段:用编译好的状态机去匹配目标字符串

3. 完整常用API

(1)创建RegExp对象
// 1. 字面量方式(推荐,编译一次)
const regex1 = /abc/g;

// 2. 构造函数方式(动态生成正则)
const regex2 = new RegExp('abc', 'g');
const pattern = 'abc';
const regex3 = new RegExp(pattern, 'i');
(2)RegExp对象方法
const regex = /hello (\w+)/g;
const str = 'hello world, hello javascript';

// test():测试是否匹配,返回布尔值
regex.test(str); // true

// exec():执行匹配,返回匹配结果数组或null
let result;
while ((result = regex.exec(str)) !== null) {
  console.log(result[0]); // "hello world", "hello javascript"
  console.log(result[1]); // "world", "javascript"
  console.log(regex.lastIndex); // 11, 28
}
(3)字符串的正则方法
const str = 'hello world, hello javascript';
const regex = /hello (\w+)/g;

// match():返回所有匹配结果数组
str.match(regex); // ["hello world", "hello javascript"]

// matchAll():返回所有匹配结果的迭代器(ES2020)
for (const match of str.matchAll(regex)) {
  console.log(match[1]); // "world", "javascript"
}

// replace():替换匹配的字符串
str.replace(regex, 'hi $1'); // "hi world, hi javascript"

// search():返回第一个匹配的索引,没有匹配返回-1
str.search(/world/); // 6

// split():用正则分割字符串
str.split(/[, ]+/); // ["hello", "world", "hello", "javascript"]

4. 最常见的坑点

  1. 贪婪匹配:默认是贪婪匹配,会尽可能多的匹配字符
    const str = '<div>hello</div><div>world</div>';
    str.match(/<div>(.*)<\/div>/); // ["<div>hello</div><div>world</div>", "hello</div><div>world"]
    str.match(/<div>(.*?)<\/div>/); // 非贪婪匹配,["<div>hello</div>", "hello"]
    
  2. lastIndex属性:带g修饰符的正则,lastIndex会记录上次匹配的位置,下次匹配从该位置开始
  3. 特殊字符转义. * + ? ^ $ { } [ ] ( ) | \ /这些字符需要转义
  4. 性能问题:过于复杂的正则可能导致回溯爆炸,甚至浏览器崩溃

5. 实际应用场景

  • 表单验证(手机号、邮箱、身份证等)
  • 字符串替换和提取
  • 语法高亮
  • 路由匹配

6. 深拷贝处理

// 正确的深拷贝方式
const original = /abc/gi;
const clone = new RegExp(original.source, original.flags);

三、Error:错误处理对象

1. 核心定义

Error是JavaScript中表示运行时错误的基类,所有内置错误类型都继承自它。它用于在程序运行出错时,传递错误的详细信息。

2. 内置错误类型

JavaScript提供了7种内置错误类型:

错误类型说明
SyntaxError语法错误,代码不符合JavaScript语法规范
ReferenceError引用错误,引用了不存在的变量
TypeError类型错误,变量的类型不符合预期
RangeError范围错误,数值超出了有效范围
URIErrorURI错误,encodeURI()decodeURI()参数无效
EvalErroreval()函数执行错误(已基本废弃)
AggregateError聚合错误,多个错误打包成一个(ES2021)

3. 完整常用API

(1)创建Error对象
// 1. 创建普通错误
const err = new Error('这是一个错误信息');

// 2. 创建特定类型的错误
const typeErr = new TypeError('类型错误');
const refErr = new ReferenceError('引用错误');

// 3. 自定义错误类型(ES6)
class MyError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'MyError';
    this.code = code;
    // 修复原型链
    Object.setPrototypeOf(this, MyError.prototype);
  }
}
(2)Error对象属性
const err = new Error('这是一个错误信息');
err.name; // "Error"(错误类型名称)
err.message; // "这是一个错误信息"(错误描述)
err.stack; // 错误堆栈信息(非标准,但所有浏览器都支持)
err.cause; // 错误原因(ES2022新增)
(3)错误处理语法
// try/catch/finally
try {
  // 可能出错的代码
  throw new Error('出错了');
} catch (err) {
  // 处理错误
  console.error(err.message);
} finally {
  // 无论是否出错,都会执行
  console.log('执行完毕');
}

// 异步错误处理(Promise)
fetch('/api/data')
  .then(res => res.json())
  .catch(err => {
    console.error('请求失败:', err);
  });

// 异步错误处理(async/await)
async function fetchData() {
  try {
    const res = await fetch('/api/data');
    const data = await res.json();
    return data;
  } catch (err) {
    console.error('请求失败:', err);
  }
}

4. 最常见的坑点

  1. 不要捕获所有错误:不要写空的catch块,也不要捕获Error基类而不做任何处理
  2. 异步错误无法被同步try/catch捕获
    // ❌ 错误:无法捕获setTimeout中的错误
    try {
      setTimeout(() => {
        throw new Error('异步错误');
      }, 1000);
    } catch (err) {
      console.error(err); // 永远不会执行
    }
    
  3. 不要忽略错误:至少要打印错误信息,方便调试
  4. Error对象的stack属性是非标准的:不同浏览器的堆栈格式可能不同

5. 实际应用场景

  • 程序错误处理和调试
  • 自定义错误类型,区分不同的错误场景
  • 异步请求错误处理
  • 错误监控和上报

6. 深拷贝处理

// 正确的深拷贝方式
const original = new Error('这是一个错误');
const clone = new Error(original.message);
clone.name = original.name;
clone.stack = original.stack;
if (original.cause) {
  clone.cause = original.cause;
}

四、ArrayBuffer:二进制数据缓冲区

1. 核心定义

ArrayBuffer是JavaScript中用于表示通用的、固定长度的原始二进制数据缓冲区的对象。它是所有二进制数据操作的基础。

2. 底层原理

ArrayBuffer会在内存中分配一块连续的、固定大小的字节空间,用于存储二进制数据。

  • 你不能直接读写ArrayBuffer的内容
  • 必须通过视图对象TypedArrayDataView)来操作缓冲区中的数据
  • 视图对象不存储数据,只是提供了不同的方式来解读同一块内存

3. 完整常用API

(1)创建ArrayBuffer
// 创建一个8字节的缓冲区,初始值都是0
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength); // 8
(2)ArrayBuffer方法
// 截取缓冲区的一部分,返回新的ArrayBuffer
const buffer = new ArrayBuffer(8);
const slice = buffer.slice(2, 6); // 截取第2到第5个字节
console.log(slice.byteLength); // 4

// 转移缓冲区的所有权(ES2023),原缓冲区变为空
const transferred = buffer.transfer();
console.log(buffer.byteLength); // 0
console.log(transferred.byteLength); // 8
(3)通过TypedArray视图操作数据

TypedArray是一组特定类型的数组视图,用于操作相同类型的二进制数据:

const buffer = new ArrayBuffer(8);

// 创建一个Uint8Array视图,操作buffer中的数据
const uint8 = new Uint8Array(buffer);
uint8[0] = 0x12;
uint8[1] = 0x34;
uint8[2] = 0x56;
uint8[3] = 0x78;

console.log(uint8); // Uint8Array(8) [18, 52, 86, 120, 0, 0, 0, 0]

// 创建一个Uint32Array视图,同样操作同一个buffer
const uint32 = new Uint32Array(buffer);
console.log(uint32[0]); // 2018915346(0x78563412,小端序)

常见的TypedArray类型:

类型大小(字节)说明
Uint8Array18位无符号整数
Int8Array18位有符号整数
Uint16Array216位无符号整数
Int16Array216位有符号整数
Uint32Array432位无符号整数
Int32Array432位有符号整数
Float32Array432位浮点数
Float64Array864位浮点数
(4)通过DataView视图操作数据

DataView是一个更灵活的视图,可以操作不同类型、不同字节序的数据:

const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);

// 写入不同类型的数据
view.setUint8(0, 0x12);
view.setUint16(1, 0x3456, false); // 大端序(默认)
view.setUint32(3, 0x789abcde, true); // 小端序

// 读取数据
console.log(view.getUint8(0)); // 18
console.log(view.getUint16(1)); // 13398
console.log(view.getUint32(3, true)); // 2023406872

4. 最常见的坑点

  1. 字节序问题:TypedArray使用系统的字节序(通常是小端序),DataView可以指定字节序
  2. 视图的偏移量和长度:创建视图时可以指定偏移量和长度,超出范围会报错
  3. ArrayBuffer是不可变的:你不能改变ArrayBuffer的大小,只能创建新的
  4. 不要直接操作ArrayBuffer:必须通过视图来读写数据

5. 实际应用场景

  • 处理文件(File API)
  • 网络请求(fetch获取二进制数据)
  • WebGL图形编程
  • WebSocket二进制通信
  • 加密和解密

6. 深拷贝处理

// 正确的深拷贝方式
const original = new ArrayBuffer(8);
const clone = original.slice();

五、总结:四大对象深拷贝处理汇总

这四个对象都是深拷贝中必须特殊处理的类型,不能用普通的递归拷贝:

对象类型正确的深拷贝方式
Datenew Date(original.getTime())
RegExpnew RegExp(original.source, original.flags)
Errornew Error(original.message) + 复制namestackcause
ArrayBufferoriginal.slice()

六、面试高频考点总结

  1. Date对象的月份是从0开始还是从1开始?
  2. 如何解决Date对象的时区问题?
  3. 正则表达式的贪婪匹配和非贪婪匹配有什么区别?
  4. lastIndex属性的作用是什么?
  5. JavaScript有哪些内置错误类型?
  6. 为什么异步错误无法被同步的try/catch捕获?
  7. ArrayBufferTypedArray的区别是什么?
  8. TypedArrayDataView的区别是什么?
  9. 为什么这四个对象不能用普通的递归方式深拷贝?

如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,有任何问题可以在评论区留言讨论!