乐闻世界logo
搜索文章和话题

服务端面试题手册

如何将项目从 npm 或 Yarn 迁移到 pnpm?需要注意什么?

将项目从 npm 或 Yarn 迁移到 pnpm 需要注意以下步骤和问题:迁移步骤:安装 pnpm# 使用 npm 安装npm install -g pnpm# 使用独立脚本安装curl -fsSL https://get.pnpm.io/install.sh | sh -# 使用 Homebrew (macOS)brew install pnpm清理旧依赖# 删除 node_modulesrm -rf node_modules# 删除旧的锁文件rm package-lock.json # npmrm yarn.lock # Yarn导入锁文件(可选)# pnpm 可以从 npm/yarn 锁文件导入pnpm import# 这会生成 pnpm-lock.yaml安装依赖pnpm install处理常见问题:幽灵依赖问题// ❌ 迁移前可以运行(幽灵依赖)const lodash = require('lodash'); // 未在 package.json 中声明// ✅ 迁移后需要显式声明pnpm add lodashpeer dependencies 问题# pnpm 对 peer dependencies 检查更严格# 可能会遇到 peer dependency 错误# 解决方案 1:安装缺失的 peer dependenciespnpm add react react-dom# 解决方案 2:使用 overrides# package.json{ "pnpm": { "overrides": { "react": "^18.0.0" } }}shamefully-hoist 配置# 如果项目依赖扁平化的 node_modules 结构# .npmrcshamefully-hoist=true# 这会创建类似 npm 的扁平化结构# 但会失去 pnpm 的严格依赖管理优势配置文件迁移:# .npmrc 配置shamefully-hoist=true # 扁平化模式strict-peer-dependencies=false # 不严格检查 peer dependenciesauto-install-peers=true # 自动安装 peer dependenciespackage.json 调整:{ "scripts": { "preinstall": "npx only-allow pnpm" // 强制使用 pnpm }, "engines": { "pnpm": ">=8.0.0" }}CI/CD 配置更新:# GitHub Actions- name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8- name: Install dependencies run: pnpm install --frozen-lockfilemonorepo 迁移:# 创建 pnpm-workspace.yamlpackages: - 'packages/*' - 'apps/*'// 更新包间依赖引用{ "dependencies": { "@my-org/utils": "workspace:*" // 使用 workspace 协议 }}迁移检查清单:# 1. 检查幽灵依赖pnpm ls --depth=0# 2. 检查 peer dependenciespnpm install --strict-peer-dependencies# 3. 运行测试pnpm test# 4. 构建项目pnpm build# 5. 检查脚本命令pnpm run <script>回滚方案:# 如果迁移失败,可以回滚rm pnpm-lock.yamlrm -rf node_modulesnpm install # 或 yarn install迁移后的优势:安装速度提升 2-3 倍磁盘空间节省 50-70%更严格的依赖管理更好的 monorepo 支持
阅读 0·3月5日 23:34

什么是幽灵依赖?pnpm 如何解决这个问题?

幽灵依赖(Phantom Dependencies)是指在项目中引用了 package.json 中未声明的依赖包。问题产生原因:npm 和 Yarn 使用扁平化的 node_modules 结构:# package.json{ "dependencies": { "express": "^4.18.0" # express 依赖 debug }}# npm/Yarn 的扁平化结构node_modules/├── express/├── debug/ # 被提升上来,虽然未直接声明└── ...幽灵依赖的危害:// 代码中可以直接使用const debug = require('debug'); // ✅ 能运行,但很危险!// 问题:// 1. package.json 中没有声明 debug// 2. express 更新后可能不再依赖 debug// 3. 删除 node_modules 重新安装可能失败// 4. 其他开发者克隆项目后可能运行失败pnpm 的解决方案:pnpm 使用严格的依赖结构,防止幽灵依赖:# pnpm 的结构node_modules/├── .pnpm/│ ├── express@4.18.2/│ │ └── node_modules/│ │ ├── express/│ │ └── debug/ # debug 只在这里可访问│ └── debug@4.3.4/└── express -> .pnpm/express@4.18.2/node_modules/express// pnpm 中尝试访问幽灵依赖const debug = require('debug');// ❌ Error: Cannot find module 'debug'// 必须显式声明// package.json{ "dependencies": { "express": "^4.18.0", "debug": "^4.3.4" // 显式声明 }}pnpm 的优势:依赖可见性明确:只能访问 package.json 中声明的依赖避免版本冲突:不同包可以依赖同一包的不同版本更安全:防止意外使用未声明的依赖更可靠:确保依赖关系完整记录对比总结:| 特性 | npm/Yarn | pnpm ||------|----------|------|| 依赖访问 | 可访问所有提升的包 | 只能访问声明的依赖 || 依赖隔离 | 弱,扁平化 | 强,严格隔离 || 幽灵依赖 | 容易产生 | 完全避免 |
阅读 0·3月5日 23:34

在 Cypress 中如何处理异步操作和 Promise?

在现代前端开发中,异步操作是常态,而 Cypress 作为一款流行的端到端测试框架,其核心设计基于 Promise 和异步处理机制。然而,许多测试工程师在编写 Cypress 测试时,常因异步操作的复杂性导致测试不稳定或失败。本文将深入探讨如何在 Cypress 中高效处理异步操作和 Promise,通过实践案例和专业分析,帮助开发者构建健壮、可靠的自动化测试流程。Cypress 的异步模型与浏览器原生 Promise 无缝集成,但需遵循特定模式以避免常见陷阱,例如未正确处理异步链式调用或忽略执行上下文问题。掌握这些技巧,能显著提升测试覆盖率和执行效率。核心概念为什么异步处理至关重要Cypress 测试运行在浏览器环境中,所有操作(如 DOM 交互或网络请求)本质上是异步的。当执行 cy.get() 或 cy.request() 时,Cypress 会返回一个 Promise,表示操作完成后的状态。错误处理不当会导致测试失败,例如未等待元素加载或 API 响应。理解 Cypress 的异步模型是关键:它基于浏览器事件循环,所有命令(commands)都是 Promise-based,允许链式调用。Promise 在 Cypress 中的角色Cypress 内置 Promise API 与 JavaScript 标准一致,但有细微差异。例如,cy 命令返回的 Promise 会自动附加 then 和 catch 方法,但需注意:Cypress 的 Promise 是 thenable,而非严格 Promise 实例。这意味着直接使用 Promise.resolve() 可能不兼容,应优先使用 Cypress 原生方法。处理异步操作的实战方法1. 使用 cy.then() 进行链式调用cy.then() 是处理异步操作的核心方法,允许在命令执行后添加自定义逻辑。它接收一个回调函数,该函数接收前一个命令的值(作为参数),并返回新值或新命令。示例:处理元素文本内容// 读取元素文本并验证const text = '欢迎使用';// 假设元素存在,但需等待加载const header = cy.get('h1');// 使用 cy.then() 链式处理header.then((el) => { const actualText = el.text(); expect(actualText).to.equal(text); return actualText;}).then((textValue) => { // 后续操作:例如发送文本到日志 console.log(`验证通过: ${textValue}`);});关键点:避免阻塞:cy.then() 不阻塞测试执行,而是异步处理。错误处理:在回调中使用 try/catch,或通过 catch() 处理异常。2. 处理 API 请求与网络操作Cypress 提供 cy.request() 处理 HTTP 请求,返回 Promise。需确保正确链式调用以验证响应。示例:验证 API 响应// 发送请求并验证状态码和数据const url = '/api/data';// 链式调用:先请求,再验证cy.request(url) .then((response) => { expect(response.status).to.equal(200); expect(response.body).to.have.lengthOf(5); return response.body; }) .then((data) => { // 处理数据:例如提取特定字段 const firstItem = data[0]; expect(firstItem.id).to.be.a('number'); });最佳实践:避免硬编码:使用 cy.request() 时,确保 URL 与测试环境匹配,避免测试脆弱。处理超时:添加 timeout 参数防止挂起:cy.request(url, { timeout: 5000 })。3. 使用 cy.wrap() 转换非 Promise 值当需要将同步值转换为 Promise 以嵌入异步流程时,cy.wrap() 有用。例如,处理 DOM 操作后返回原始值。示例:在异步操作中包装元素// 假设存在一个同步操作(如获取元素)const element = cy.get('button');// 使用 cy.wrap() 转换为 Promisecy.wrap(element).then((btn) => { // 执行异步操作:点击按钮 btn.click(); // 验证后续状态 cy.get('p').should('contain', '成功');});注意事项:cy.wrap() 仅用于包装同步值,不适用于复杂异步流。避免过度使用:优先用 cy.then() 处理链式逻辑。4. 集成 cy.wait() 处理动态内容在异步场景中,如元素加载或网络请求,cy.wait() 是处理延迟的利器。它等待指定条件满足(如元素出现或 API 响应),避免测试因未完成操作失败。示例:等待元素出现后执行操作// 等待元素加载后点击cy.get('button#submit', { timeout: 10000 }) .wait(2000) // 选项:等待固定时间 .then(() => { // 链式调用:点击后验证 cy.get('div#result').should('be.visible'); });高级技巧:结合 cy.request():cy.request('/api').wait(1000) 确保响应完成。错误处理:使用 catch() 捕获超时:.wait(5000).catch((err) => console.error(err));。避免常见陷阱与最佳实践常见问题与解决方案陷阱 1:未正确处理 Promise 链:在链式调用中,遗漏 then 或 catch 会导致测试中断。解决方案:始终使用 cy.then() 确保异步流程连贯。陷阱 2:测试阻塞:直接使用 Promise.resolve() 会阻塞执行。解决方案:优先用 Cypress 命令(如 cy.request())而非手动 Promise。陷阱 3:执行上下文错误:在 then 回调中使用 this 可能导致作用域问题。解决方案:使用箭头函数或明确绑定上下文。实践建议模块化测试:将异步逻辑拆分为小函数,提高可读性:function validateData(response) { return cy.wrap(response).then((data) => { expect(data).to.have.lengthOf(5); });}validateData(cy.request('/api/data'));使用 cy.intercept() 模拟异步:在测试中拦截网络请求,避免真实 API 依赖:cy.intercept('/api/data').as('apiCall');cy.get('button').click();cy.wait('@apiCall').then((interception) => { // 处理拦截响应});测试覆盖率:添加 it.only 专注异步逻辑:it.only('验证异步操作', () => { cy.request('/api').then((res) => { expect(res.body).to.exist; });});结论在 Cypress 中处理异步操作和 Promise 是自动化测试的核心技能。通过 cy.then() 链式调用、cy.request() API 处理和 cy.wrap() 转换值,开发者可以构建高效、可靠的测试脚本。关键在于理解 Cypress 的异步模型,避免常见陷阱,例如未处理的 Promise 链或执行上下文错误。建议始终优先使用 Cypress 原生方法而非手动 Promise,结合 cy.wait() 和 cy.intercept() 提升测试健壮性。实践证明,掌握这些技巧能显著减少测试失败率,提升开发效率。最后,持续参考 Cypress 官方文档 获取最新最佳实践。附加资源Cypress 异步操作指南Promise API 参考
阅读 0·2月25日 23:19

Cypress 中的断言(Assertions)有哪些类型和用法?

在现代前端测试中,Cypress 是一个广受欢迎的端到端测试框架,以其易用性和强大的测试能力著称。断言(Assertions)是 Cypress 的核心功能之一,用于验证测试中页面元素的状态、属性或行为是否符合预期。通过断言,测试工程师能够确保应用的 UI 逻辑正确性,从而提升测试的可靠性和可维护性。本文将深入探讨 Cypress 中的断言类型及其用法,结合实际代码示例,帮助开发者高效编写测试用例。Cypress 的断言机制基于 Chai 断言库,但提供了更简洁的 API,避免了传统测试框架的冗余语法。掌握断言类型是构建健壮测试套件的关键一步。Cypress 断言概述Cypress 的断言本质上是通过 cy 命令链式调用的验证方法,用于检查元素的属性、状态或值是否满足条件。断言分为两类:同步断言(在测试步骤中直接验证)和异步断言(通过 then() 或 should() 等方法实现)。Cypress 的断言设计原则是明确性和可读性,避免了测试代码的“魔法”行为。断言的核心作用是提供测试失败的详细反馈,便于快速定位问题。例如,当页面元素未按预期加载时,断言能立即报告错误,而不是继续执行测试。常见断言类型及用法Cypress 提供了丰富的断言类型,主要分为基础断言和高级断言。以下按功能分类详解,并附带代码示例。1. 存在断言(Existence Assertions)存在断言用于验证元素是否存在于 DOM 中。这是最基础的断言,适用于检查页面加载状态或元素初始化。cy.contains():搜索特定文本或子字符串,确保元素存在。cy.get().should('exist'):显式检查元素是否存在。用法示例:// 检查页面是否包含 'Welcome' 文本(文本断言的变体)- cy.contains('Welcome').should('exist');// 检查按钮元素是否存在- cy.get('#login-btn').should('exist');最佳实践:避免使用 cy.contains() 时过度依赖文本,因为它可能因 UI 变化而失效。优先使用 cy.get() 与 should('exist') 结合,提高测试稳定性。2. 值断言(Value Assertions)值断言用于验证元素的值属性,如输入框的值、变量的数值等。它通过 should() 方法链式调用,确保值匹配预期。eq:检查数值是否相等。contain:检查值是否包含特定字符串。include:检查数组或对象是否包含指定元素。用法示例:// 检查用户名输入框的值是否等于 'test'- cy.get('#username').should('have.value', 'test');// 检查状态文本是否包含 'active'- cy.get('#status').should('have.text', 'active');// 检查数组长度是否为 3- cy.get('#list').should('have.length', 3);关键点:have.value 用于输入框,而 have.text 用于文本内容。避免在值断言中使用 cy.get().then(),因为 should() 已内置异步处理。3. 属性断言(Attribute Assertions)属性断言验证元素的属性值,如 href、class 或 data-* 属性。Cypress 提供 have.attr 方法实现,支持精确匹配和模糊匹配。have.attr:检查属性是否存在或值匹配。have.css:用于 CSS 样式,如 width 或 color。用法示例:// 检查链接的 href 属性是否正确- cy.get('a').should('have.attr', 'href', '/home');// 检查元素的 CSS 类是否包含 'active'- cy.get('.item').should('have.css', 'color', 'red');高级技巧:使用 have.attr 时,如果属性值是动态的,可结合 include 或 match 操作符,例如 cy.get('a').should('have.attr', 'href', /\/home/);。4. 状态断言(State Assertions)状态断言用于验证元素的交互状态,如可见性、可点击性或加载状态。Cypress 的 should() 方法提供丰富的状态检查器。be.visible:检查元素是否在视口内可见。be.disabled:验证元素是否禁用。be.checked:检查复选框或单选按钮是否选中。用法示例:// 确保按钮在页面加载后可见- cy.get('#submit-btn').should('be.visible');// 验证登录按钮是否禁用(示例:表单未填)- cy.get('#login-btn').should('be.disabled');// 检查复选框状态- cy.get('#agree').should('be.checked');注意事项:状态断言必须在元素渲染后调用,避免因渲染延迟导致测试失败。结合 cy.wait() 可确保元素已就绪。5. 高级断言:自定义和链式验证Cypress 允许通过 cy.wrap() 和 cy.then() 实现更复杂的断言逻辑。例如,验证 API 响应或自定义数据结构。cy.request() 与断言结合:发送请求后验证响应数据。cy.wrap() 用于对象断言:将对象封装为 Cypress 元素进行验证。用法示例:// 验证 API 响应数据- cy.request('/api/data').then((response) => { expect(response.body).to.have.property('status', 'success');});// 自定义断言:检查对象属性- cy.wrap({ name: 'test', age: 30 }).should('have.property', 'age', 30);实践建议:对于复杂场景,优先使用 Chai 断言库的扩展方法,如 expect(),但需注意 Cypress 会自动处理异步操作。实践建议与最佳实践选择断言类型:根据测试目标选择。例如,验证页面加载状态用存在断言;验证表单值用值断言。避免过度使用 should(),可能导致测试冗长。提高测试健壮性:结合 cy.wait() 和 cy.get() 确保元素已加载。例如:cy.get('#username').wait(1000).should('have.value', 'test');错误处理:断言失败时,Cypress 会暂停执行并显示详细错误信息。在测试中添加 cy.log() 记录调试信息。避免常见陷阱:不要使用 cy.contains() 仅验证文本,因为文本可能动态变化。避免在断言中使用全局变量,使用 cy.get() 确保上下文明确。性能优化:对于大量元素,优先使用 cy.get() 与 should() 而非 cy.contains(),以减少 DOM 搜索开销。结论Cypress 的断言机制为前端测试提供了强大且灵活的验证工具。通过掌握存在断言、值断言、属性断言、状态断言等类型,开发者能构建高效、可靠的测试用例,确保应用质量。本文详细分析了每种断言的用法和最佳实践,强调了在实际项目中选择合适断言的重要性。建议在测试开发中逐步实践这些技术,并结合 Cypress 文档(Cypress 官方文档)深入探索。断言不仅是验证工具,更是提升测试可维护性的关键——合理使用断言能显著减少测试维护成本,让测试工作更加高效和愉悦。 提示:Cypress 的断言设计遵循“测试驱动开发”原则,鼓励在编写测试时优先考虑断言逻辑。对于大型项目,推荐使用 cy.task() 和 cy.intercept() 与断言结合,实现更全面的测试覆盖。​
阅读 0·2月25日 23:17

如何防止 JSON 注入攻击?有哪些常见的安全问题需要注意?

JSON 安全防护与常见问题JSON 注入攻击的原理JSON 注入攻击是指攻击者通过在输入数据中插入恶意 JSON 代码,导致应用程序解析错误或执行恶意操作的安全漏洞。常见的 JSON 安全问题JSON 注入:攻击者通过构造特殊的 JSON 字符串,破坏 JSON 结构或执行恶意代码。反序列化漏洞:不安全的 JSON 反序列化可能导致远程代码执行(RCE)攻击。跨站请求伪造(CSRF):利用 JSON 格式的请求进行 CSRF 攻击。信息泄露:JSON 响应中包含敏感信息,如密码、令牌等。拒绝服务攻击:构造超大或嵌套极深的 JSON 数据,导致服务器解析时内存耗尽。防止 JSON 注入的措施1. 输入验证与净化严格验证输入数据:检查所有用户输入,确保符合预期格式。使用参数化查询:避免直接拼接 JSON 字符串。转义特殊字符:对 JSON 中的特殊字符进行适当转义。2. 安全的反序列化使用安全的反序列化库:选择经过安全审计的 JSON 解析库。限制反序列化类型:只允许反序列化到预期的类型。禁用危险功能:如禁用反序列化中的类型自动识别。3. 传输安全使用 HTTPS:确保 JSON 数据在传输过程中的加密。添加适当的 HTTP 头部:如 Content-Type: application/json。实现 CORS 策略:限制跨域请求。4. 服务器端防护设置合理的解析限制:限制 JSON 数据大小和嵌套深度。实现请求速率限制:防止暴力破解和 DoS 攻击。监控异常请求:及时发现和阻止可疑的 JSON 数据。5. 数据处理安全过滤敏感信息:确保 JSON 响应中不包含敏感数据。使用最小权限原则:限制 JSON 处理代码的权限。定期更新依赖库:修复已知的安全漏洞。最佳实践使用成熟的 JSON 库:避免自行实现 JSON 解析。遵循安全编码规范:如 OWASP 安全编码指南。定期进行安全审计:检查 JSON 处理代码中的安全漏洞。实施安全测试:包括渗透测试和代码审查。通过以上措施,可以有效防止 JSON 相关的安全问题,保护应用程序和用户数据的安全。
阅读 0·2月25日 23:17

JSON 与其他数据格式(如 XML、YAML、CSV)相比有哪些优缺点?

JSON 与其他数据格式的比较JSON vs XMLJSON 优点更简洁:语法更简单,数据体积更小解析速度快:解析器实现更简单,性能更好易于阅读:人类可读性强,结构清晰与 JavaScript 集成:直接可以用作 JavaScript 对象XML 优点更强大的元数据支持:支持属性、命名空间更严格的验证:Schema 验证更成熟更适合文档标记:保留文档结构和格式更广泛的工具支持:传统系统支持更好JSON vs YAMLJSON 优点更简单的语法:规则更少,学习成本低更广泛的支持:几乎所有编程语言都内置支持更适合机器处理:解析器更简单高效无歧义:语法严格,避免解析错误YAML 优点更简洁的语法:支持缩进、省略引号更丰富的数据类型:支持日期、时间、二进制等支持注释:提高配置文件可读性更适合人类编写:配置文件更友好JSON vs CSVJSON 优点支持复杂结构:嵌套对象和数组类型信息:保留数据类型自描述性:数据结构包含在数据中更灵活:字段数量和结构可以变化CSV 优点更紧凑:数据密度更高更适合表格数据:二维数据表示更简洁加载速度快:处理大量数据时性能更好更易于编辑:可以用电子表格软件编辑适用场景选择JSON:Web API、前后端数据交换、配置文件XML:企业级应用、需要严格验证的场景、遗留系统集成YAML:配置文件、数据序列化(需要人类可读性)CSV:数据导入导出、批量数据处理、数据分析
阅读 0·2月25日 23:17

什么是 JSON Schema?它的作用是什么?

JSON Schema 概念与作用JSON Schema 是一种描述 JSON 数据结构的规范,它允许你定义 JSON 数据的预期结构、类型和约束。JSON Schema 的核心作用数据验证:验证 JSON 数据是否符合预期的结构和格式,确保数据质量。文档生成:自动生成 JSON 数据结构的文档,提高 API 可维护性。代码生成:基于 Schema 自动生成数据模型和验证代码,减少手动编码错误。交互界面:根据 Schema 自动生成表单和用户界面,简化前端开发。数据约束:定义数据的取值范围、格式要求等约束条件。JSON Schema 的基本结构一个简单的 JSON Schema 示例:{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Person", "type": "object", "properties": { "name": { "type": "string", "description": "Person's full name" }, "age": { "type": "integer", "minimum": 0 }, "email": { "type": "string", "format": "email" } }, "required": ["name", "age"]}JSON Schema 的主要特性类型定义:支持 string、number、integer、boolean、array、object、null 等类型嵌套结构:可以定义复杂的嵌套数据结构约束条件:支持 minimum、maximum、minLength、maxLength、pattern 等约束枚举值:可以定义允许的取值范围引用:支持通过 $ref 引用其他 Schema 定义,实现复用条件逻辑:支持 if/then/else 等条件验证规则实际应用场景API 开发:定义请求和响应的数据结构,确保 API 接口的数据一致性配置文件验证:验证应用配置文件的正确性数据交换:在不同系统间传输数据时,确保数据格式符合预期数据库迁移:验证导入/导出数据的结构正确性
阅读 0·2月25日 23:17

如何在 JavaScript 中解析和序列化 JSON 数据?

JavaScript 中的 JSON 解析与序列化在 JavaScript 中,处理 JSON 数据是非常常见的操作,主要涉及两个核心方法:1. JSON 解析(Parse)JSON.parse() 方法用于将 JSON 字符串转换为 JavaScript 对象。基本用法const jsonString = '{"name": "John", "age": 30, "city": "New York"}';const obj = JSON.parse(jsonString);console.log(obj.name); // 输出: John高级用法:使用 reviver 函数JSON.parse() 可以接受第二个参数作为 reviver 函数,用于在解析过程中转换结果:const jsonString = '{"name": "John", "birthDate": "2000-01-01"}';const obj = JSON.parse(jsonString, (key, value) => { if (key === 'birthDate') { return new Date(value); } return value;});console.log(obj.birthDate instanceof Date); // 输出: true2. JSON 序列化(Stringify)JSON.stringify() 方法用于将 JavaScript 对象转换为 JSON 字符串。基本用法const obj = {name: "John", age: 30, city: "New York"};const jsonString = JSON.stringify(obj);console.log(jsonString); // 输出: {"name":"John","age":30,"city":"New York"}高级用法2.1 使用 replacer 参数可以指定要包含的属性,或者对值进行转换:const obj = {name: "John", age: 30, city: "New York", salary: 50000};// 只包含指定属性const jsonString1 = JSON.stringify(obj, ['name', 'age']);// 使用函数转换值const jsonString2 = JSON.stringify(obj, (key, value) => { if (key === 'salary') { return value > 0 ? ' confidential ' : value; } return value;});2.2 使用 space 参数用于添加缩进和空白,使输出更易读:const obj = {name: "John", age: 30, city: "New York"};const jsonString = JSON.stringify(obj, null, 2);// 输出格式化的 JSON 字符串,缩进 2 个空格注意事项循环引用:如果对象中存在循环引用,JSON.stringify() 会抛出错误。undefined 和函数:这些值在序列化时会被忽略(对象中)或转换为 null(数组中)。Date 对象:会被转换为 ISO 格式的字符串。正则表达式:会被转换为空对象 {}。Symbol:作为对象键时会被忽略,作为值时会被转换为 undefined。
阅读 0·2月25日 23:17