freeCodeCamp 基础数据结构实战:用 JavaScript delete 关键字移除对象属性
本篇文章围绕 freeCodeCamp 开源课程 Basic Data Structures(基础数据结构)模块中的经典关卡 "Use the delete Keyword to Remove Object Properties"(关卡源文件)展开,系统讲解如何使用 delete 运算符从 JavaScript 对象中移除键值对,并结合该仓库的源码结构、测试断言与配置 schema,深入辨析 delete 与其它"删除"写法的本质差异。读完本文,你将理解对象作为键值存储的核心特性,掌握 delete 的语法、返回值与适用边界,并能独立完成该关卡及同模块后续挑战。
一、关卡定位:Basic Data Structures 模块中的关键一环
在 Basic Data Structures 模块结构文件 中,本关卡(id: 587d7b7c367417b2b2512b1b,dashedName: use-the-delete-keyword-to-remove-object-properties)是整个模块第 16 个挑战。它在课程脉络上承接前面三道对象操作关卡,又为后续关卡做铺垫:
- 前序关卡 Add Key-Value Pairs to JavaScript Objects:演示如何用点号与方括号向对象添加键值对;
- 前序关卡 Access Property Names with Bracket Notation:讲解用变量动态访问属性名;
- 本关卡:演示如何用
delete移除对象自身的键值对; - 后续关卡 Check if an Object has a Property:讲解
hasOwnProperty()与in运算符检测属性是否存在(其测试用例中正是用delete users.Alan来构造"属性缺失"的场景)。
从代码结构看,这一系列关卡构成一条完整的学习链:增 → 改 → 查 → 删 → 再验证,本关卡正是链条中"删"的环节。
二、为什么要用 delete:回顾对象(Object)的本质
关卡描述中明确指出:对象本质上是 key-value store(键值存储),具备两大优势——为数据提供灵活、直观的组织方式,同时提供非常快的查找时间(very fast lookup time)。
理解这一背景对正确使用 delete 很重要:
- 对象是引用类型:变量保存的是对象的引用,而非值本身;
- 键值对动态可变:属性可以随时添加、修改和删除,无需重新定义整个对象;
- 查找依赖键名哈希:这也是描述中强调"快速查找"的原因——对象访问本质上是基于键名的哈希查找,与数组基于下标顺序遍历不同。
关卡源码给了本节练习使用的 foods 对象(源文件 --seed-- 部分):
let foods = {
apples: 25,
oranges: 32,
plums: 28,
bananas: 13,
grapes: 35,
strawberries: 27
};
// Only change code below this line
// Only change code above this line
console.log(foods);
该对象的初始定义(含 let 声明)在前序关卡 Add Key-Value Pairs to JavaScript Objects 的 --solutions-- 中曾经出现过:那里用 foods['bananas'] = 13; 等写法完成了"增",而本节要做的正是反向操作——"删"。
三、delete 运算符基础语法与示例
delete 是 JavaScript 的一元运算符,用于删除对象的自有属性(own property)。关卡给出的核心示例非常简洁:
delete foods.apples;
执行后,foods 对象中将不再存在 apples 这个键。它既支持点号写法,也支持方括号写法(当属性名含特殊字符或来自变量时必须用方括号):
delete foods['apples']; // 方括号 + 字符串字面量,等价于 delete foods.apples;
const key = 'apples';
delete foods[key]; // 属性名来自变量,只能使用方括号
delete 的返回值
delete 表达式会返回一个布尔值,可以用它来判断删除是否成功:
- 删除自有可配置属性成功时返回
true; - 试图删除不存在的属性也会返回
true(JS 引擎将其视为"删除成功",因为目标属性原本就不存在); - 删除不可配置属性(如
var声明的全局变量、内置对象的某些属性)时,在非严格模式下返回false,在严格模式下会抛出TypeError。
一个"删不干净"的经典陷阱
delete 只能删除对象自身的属性,不能删除继承自原型链的属性。如果某属性来自构造函数的 prototype 或内置原型,delete 对该实例是无效的,属性会依然可见。这也解释了为什么后续关卡 Check if an Object has a Property 的官方解法特意选用 hasOwnProperty()(自有属性检查)来精确判断"实例自身是否拥有该键"。
从源码层面看,关卡测试中大量使用 !foods.hasOwnProperty('oranges') 这类断言(见下节),说明本关卡的教学目标是让学习者理解:真正"移除"一个键应使用 delete,其效果可以用 hasOwnProperty() 和 Object.keys() 进行验证。
四、实战任务与自动化测试断言解析
关卡 --instructions-- 要求:
Use the delete keyword to remove the
oranges,plums, andstrawberrieskeys from thefoodsobject.
在 freeCodeCamp 课程体系中,每一道关卡都配有自动化测试(--hints--),对应仓库中的 schema 校验与构建逻辑。本关卡的测试分两组,拆解如下。
断言一:删除效果的可观测验证
assert(
!foods.hasOwnProperty('oranges') &&
!foods.hasOwnProperty('plums') &&
!foods.hasOwnProperty('strawberries') &&
Object.keys(foods).length === 3
);
这条断言同时验证两件事:
foods不再自有oranges、plums、strawberries三个键(用hasOwnProperty排除原型链干扰,精确判断是自有属性删除而非被继承遮蔽);- 剩余键数量恰好为 3(
Object.keys(foods).length === 3),即只剩下apples、grapes、bananas。
注意:如果只是把属性值设为 undefined(例如 foods.oranges = undefined),属性键仍然存在,hasOwnProperty('oranges') 依旧返回 true,Object.keys(foods).length 仍会是 6,无法通过该断言。这从侧面说明 delete 与"赋值为 undefined"在语义上的本质区别。
断言二:确认使用的是 delete 写法
assert(
__helpers.removeJSComments(code).search(/oranges:/) !== -1 &&
__helpers.removeJSComments(code).search(/plums:/) !== -1 &&
__helpers.removeJSComments(code).search(/strawberries:/) !== -1
);
__helpers.removeJSComments(code) 是课程测试运行环境提供的辅助函数,用于剔除代码注释后再做正则匹配。该断言用于确认:学习者没有改动 foods 对象的初始定义(即 oranges:、plums:、strawberries: 键仍存在于对象字面量中),改动只应发生在对象字面量之外的 delete 语句上。这也呼应了"只修改指定区域"的工程实践——初始数据保持不变,删除操作通过独立语句完成。
五、参考答案与代码运行结果
关卡 --solutions-- 中给出的官方参考实现如下:
let foods = {
apples: 25,
oranges: 32,
plums: 28,
bananas: 13,
grapes: 35,
strawberries: 27
};
delete foods.oranges;
delete foods.plums;
delete foods.strawberries;
console.log(foods);
运行后控制台输出:
{ apples: 25, bananas: 13, grapes: 35 }
可以看到三个目标键被移除,其余键值对完好保留。相比重建一个对象(手动剔除不想保留的键),delete 的语义是原地修改,代码意图一目了然;而在需要频繁删除属性的动态场景(如缓存清理、表单状态重置)中,这种原地操作也避免了不必要的对象复制。
六、delete 的边界条件与实战注意事项
基于 JavaScript 语言语义与本关卡的测试逻辑,使用 delete 时需要记住以下边界条件:
1. 对变量声明无效
用 var、let、const 声明的变量本身(包括全局变量)通常是不可配置的,delete 不能把它们从作用域中移除,非严格模式下静默返回 false。delete 的操作对象是"对象的属性",而不是"变量"。
2. 对数组会产生"空洞"
虽然本关卡主题是对象,但需要留意:对数组使用 delete(如 delete arr[1])只会把该下标位置置为"空槽(hole)",数组的 length 不会缩短。若想真正移除数组元素,应使用 splice() 等数组专用方法——这正是 Basic Data Structures 模块前序关卡 Remove Items Using splice() 的内容,两者恰好形成对照。
3. 与置空(set to undefined/null)的区别
| 操作 | 键是否还在 | hasOwnProperty | Object.keys 计数 | 适用场景 |
|---|---|---|---|---|
delete obj.key |
否(键被移除) | false |
减少 | 真正删除结构成员、清理缓存 |
obj.key = undefined |
是(键保留,值为 undefined) | true |
不变 | 临时清值、避免频繁增删键 |
4. 非严格模式 vs 严格模式
对不可配置属性执行 delete 时:非严格模式返回 false 不报错;严格模式下直接抛出 TypeError。建议在模块化代码中以严格模式运行,让这类错误尽早暴露。
5. 性能语义提示
关卡描述强调对象提供"非常快的查找时间"。作为键值存储的对象删除键值对是常数级语义操作,但如果需要在热路径中反复增删同一批属性,也可以考虑"重新赋值新对象 + 解构排除"等不可变风格写法,二者各有利弊,应根据真实场景(可变共享引用 vs 数据不可变)权衡。
七、从课程文件到题库:仓库里如何组织这道题
这道题只是仓库课程数据中的一个节点,理解它在整个体系中的存放方式,有助于你阅读并复用整套课程数据:
- 关卡正文:存放在 curriculum/challenges/english/blocks/basic-data-structures/587d7b7b367417b2b2512b1b.md,文件名即关卡唯一
id。每个关卡文件都包含 frontmatter(YAML 头:id、title、challengeType、forumTopicId、dashedName)与--description--、--instructions--、--hints--、--seed--(含--seed-contents--)、--solutions--等结构化区块。 - 模块结构:curriculum/structure/blocks/basic-data-structures.json 定义了该 block 的
challengeOrder(本关卡排在第 16 位)、helpCategory: "JavaScript"以及blockLayout: "legacy-challenge-list"布局方式。 - 类型体系:本关卡 frontmatter 中
challengeType: 1对应 packages/shared/src/config/challenge-types.ts 中的const js = 1;,即经典的"JavaScript 单文件挑战"类型,配备可编辑的--seed-contents--与浏览器内测试。 - Schema 约束:curriculum/schema/challenge-schema.js 使用 Joi 校验所有关卡元数据,例如
challengeType: Joi.number().min(0).max(33).required()、description对不同类型关卡的必填/允许为空规则,以及helpCategory的取值白名单(其中包含'JavaScript')等,保证本文件这类课程数据在进入构建与测试管线前格式合法。
如果你在本机克隆了本仓库,即可在 curriculum/challenges/english/blocks/basic-data-structures/ 目录下打开该关卡文件,替换 --seed-contents-- 区域内的实现后,通过课程配套的挑战测试来校验自己的答案是否通过全部 hint。
八、小结:从"会删"到"删得明白"
本关卡篇幅虽短,却承载了三个必须透彻理解的知识点:
- 语法层面:
delete object.property(或delete object['property'])原地移除对象的自有键值对; - 语义层面:
delete针对属性而非变量,对数组产生空洞而非缩短长度,删除不可配置属性在严格模式会抛错; - 验证层面:删除是否生效,要用
hasOwnProperty()与Object.keys()来检验——这也正是本关卡 hint 断言的设计逻辑,并为下一关 Check if an Object has a Property 埋下伏笔。
掌握 delete,就等于在"增、改、查、删"的对象操作闭环中补上了最后一块拼图;配合 Iterate Through the Keys of an Object with a for...in Statement 与 Generate an Array of All Object Keys with Object.keys() 等后续关卡,你将能完整掌握对象这一 JavaScript 核心数据结构的增删改查与遍历技巧。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00