1. 对象属性操作:从“增删改查”到“知其所以然”
在日常的JavaScript开发中,处理对象(Object)就像呼吸一样自然。无论是处理API返回的JSON数据,还是构建复杂的应用状态,对象都是我们最核心的数据结构。然而,越是基础的操作,越容易藏着“坑”。就拿删除对象属性和判断属性是否存在来说,看似简单的delete操作符和in操作符,背后涉及到的原型链、属性描述符、性能考量等细节,足以让一个不小心就写出隐藏Bug的代码。
这篇文章,我们不谈高深莫测的设计模式,就聚焦在这两个最基础、最高频的操作上:删除对象属性的三种方法和判断对象中是否有某一属性的四种方法。我会结合我过去在大型前端项目、Node.js服务以及性能优化实践中遇到的实际案例,不仅告诉你“怎么做”,更会深入剖析“为什么这么做”以及“在什么场景下该选择哪种方法”。你会发现,即便是obj.hasOwnProperty(‘key’)这样一行代码,选择的不同,也可能对代码的健壮性和性能产生微妙的影响。
无论你是刚入门的前端新人,还是想巩固基础的资深开发者,相信这篇从实战中提炼的总结都能给你带来新的启发。我们直接进入正题。
2. 删除对象属性的三种方法:不仅仅是delete
当我们想从一个对象中移除某个属性时,直觉反应就是使用delete操作符。这没错,但delete并非唯一选择,也并非在所有场景下都是最佳选择。理解每种方法的原理和副作用,是写出可靠代码的第一步。
2.1 方法一:delete操作符——最直接,但需知其所以然
delete操作符是JavaScript语言内置的,用于删除对象的属性。它的语法非常简单:delete object.property或delete object[‘property’]。
let user = { name: ‘Alice’, age: 30, id: ‘U001’ }; console.log(user); // { name: ‘Alice’, age: 30, id: ‘U001’ } // 使用delete删除属性 delete user.age; console.log(user); // { name: ‘Alice’, id: ‘U001’ } // 使用方括号语法删除属性 delete user[‘id’]; console.log(user); // { name: ‘Alice’ }看起来很简单,对吧?但这里有几个关键的细节和“坑”你需要了然于胸:
delete的返回值:delete操作会返回一个布尔值。如果删除成功(或要删除的属性不存在),返回true;如果属性是一个**不可配置(non-configurable)**的属性,则删除失败并返回false。在严格模式下,尝试删除不可配置的属性会抛出TypeError。‘use strict‘; let obj = {}; Object.defineProperty(obj, ‘immutable‘, { value: 42, configurable: false // 设置为不可配置 }); try { delete obj.immutable; // 这里会抛出 TypeError: Cannot delete property ‘immutable‘ of #<Object> } catch (e) { console.error(e); }delete不影响原型链:delete操作只删除对象自身的属性,不会去触碰原型链上的属性。如果你尝试删除一个从原型上继承来的属性,它不会起作用(但会返回true,因为“属性不存在”于自身也被视为成功)。function Person() {} Person.prototype.species = ‘Human‘; let alice = new Person(); alice.name = ‘Alice‘; console.log(alice.species); // ‘Human‘ (来自原型) console.log(delete alice.species); // true (删除成功?) console.log(alice.species); // ‘Human‘ (依然存在!因为删除的是自身不存在的属性,原型上的没动) console.log(delete alice.name); // true console.log(alice.name); // undefined性能考量与优化:这是一个非常重要的实战经验。JavaScript引擎(如V8)会对对象进行内部优化,将其划分为不同的“形状”或“隐藏类”。频繁使用
delete操作符,特别是删除不同的属性,可能会破坏这些优化,导致对象切换隐藏类,从而引发性能下降。对于性能敏感的场景(如游戏主循环、高频触发的函数),需要谨慎使用。实操心得:在处理配置对象或临时数据对象时,使用
delete没问题。但在处理需要被高频访问和修改的核心业务对象(如React/Vue组件的状态、游戏实体对象)时,我倾向于避免使用delete。一个常见的替代模式是将不需要的属性值设置为undefined或null,而不是删除它。这样保持了对象结构的稳定,有利于引擎优化。
2.2 方法二:将属性值设置为undefined或null——一种“软删除”
严格来说,这并不算从对象中“删除”了属性,因为属性键仍然存在于对象中。但这是一种非常常见且有用的模式,我称之为“软删除”或“逻辑删除”。
let session = { userId: ‘123‘, token: ‘abcxyz‘, expires: ‘2023-12-31‘ }; // 用户登出,但我们不清除整个session对象,只是标记token无效 session.token = undefined; // 或者 session.token = null; console.log(session); // { userId: ‘123‘, token: undefined, expires: ‘2023-12-31‘ } console.log(‘token‘ in session); // true!属性仍然存在 console.log(session.token); // undefined为什么以及何时使用这种方法?
保持对象结构:如上所述,有利于JavaScript引擎的优化。对象形状固定,V8等引擎可以为其生成更高效的代码。
明确的“空值”语义:
undefined通常表示“未定义”或“缺失”,而null常被用来表示“有意设置为空”。根据团队规范选择一种,可以使代码意图更清晰。序列化友好:当你使用
JSON.stringify()时,值为undefined的属性会被完全忽略,不会出现在JSON字符串中。而值为null的属性会被保留为null。这是一个关键区别!let obj = { a: 1, b: undefined, c: null }; console.log(JSON.stringify(obj)); // “{“a“:1,“c“:null}“如果你希望某个属性在传输或存储时不被包含,设置为
undefined是一个巧妙的技巧。而null则明确表示“这里有这个字段,但它的值是空”。与
delete的关键区别:特性 delete obj.propobj.prop = undefined属性键 从对象中移除 仍存在于对象中 in操作符falsetrueObject.keys()不包含 包含 for…in循环不会遍历到 会遍历到(除非属性不可枚举) 性能影响 可能破坏隐藏类优化 保持隐藏类稳定 JSON.stringify属性消失 属性消失(值为 undefined时)注意事项:如果你使用
obj.prop = undefined,后续用in操作符或Object.hasOwn()检查属性是否存在时,会得到true。这可能会干扰一些逻辑判断。因此,选择这种方法意味着你的“存在性”检查逻辑需要调整为检查值是否为undefined,而不是检查属性是否存在。
2.3 方法三:对象解构与剩余运算符——函数式与不可变思想的体现
这是ES6引入的现代语法,它提供了一种声明式、无副作用的方式来创建一个不包含特定属性的新对象。注意,它不修改原对象,而是返回一个新对象。
const original = { a: 1, b: 2, c: 3, d: 4 }; // 我们想“删除”属性 b 和 c const { b, c, …newObj } = original; console.log(original); // { a: 1, b: 2, c: 3, d: 4 } (原对象未变!) console.log(newObj); // { a: 1, d: 4 } console.log(b); // 2 (被单独解构出来了) console.log(c); // 3这种方法的核心优势和应用场景:
- 不可变性(Immutability):这是React、Redux、Vuex等现代前端框架推崇的核心原则。直接修改状态(Mutation)是许多Bug的根源。通过解构创建新状态,可以避免意外的副作用,使状态变化可预测、可追踪。
- 代码清晰:意图非常明确——“我要创建一个除了某某属性之外的新对象”。
- 一次“删除”多个属性:非常方便,无需对每个属性调用
delete。
但它也有局限性:
- 性能开销:对于非常大的对象,创建完整副本会有内存和性能开销。但在绝大多数业务场景下,这种开销可以忽略不计,其带来的可维护性提升是值得的。
- 动态属性名:如果需要“删除”的属性名是动态的,写起来会稍微麻烦一些,通常需要结合计算属性名和循环。
const original = { a: 1, b: 2, c: 3 }; const keysToRemove = [‘a‘, ‘c‘]; // 一种实现方式 const newObj = Object.keys(original).reduce((acc, key) => { if (!keysToRemove.includes(key)) { acc[key] = original[key]; } return acc; }, {}); console.log(newObj); // { b: 2 }
常见问题排查:使用解构“删除”属性后,发现原对象好像也变了?请立刻检查你是否不小心使用了同一个对象的引用。记住,解构赋值
const {b, …rest} = obj中,rest是一个浅拷贝的新对象。如果属性值是对象或数组,它们仍然是共享的引用。const original = { data: { x: 1 }, list: [1, 2] }; const { list, …shallowCopy } = original; shallowCopy.data.x = 999; // 修改浅拷贝对象中的引用类型值 console.log(original.data.x); // 999!原对象被影响了如果需要深层次的不可变更新,可能需要借助
lodash.cloneDeep、immer等库。
3. 判断属性存在的四种方法:深入原型链与枚举性
检查一个属性是否存在于对象中,是条件逻辑的基石。JavaScript提供了多种方法,它们的区别细微但至关重要,主要围绕两个核心问题:是否检查原型链?以及是否区分不可枚举属性?
3.1 方法一:in操作符——检查整个原型链
in操作符是最直接的,它检查指定属性是否在对象或其原型链上。
let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; // 设置原型 console.log(‘jumps‘ in rabbit); // true (自身属性) console.log(‘eats‘ in rabbit); // true (继承自原型) console.log(‘toString‘ in rabbit); // true (继承自Object.prototype) console.log(‘nonexistent‘ in rabbit); // false何时使用in?当你需要判断一个对象能否访问到某个属性时,无论它来自自身还是继承。例如,在插件或混入系统中,检查某个功能方法是否可用。
注意事项:
in操作符对任何属性都有效,包括那些值为undefined的属性。这正好与obj.prop === undefined这种判断方式形成对比,后者无法区分属性不存在和属性值为undefined。let obj = { a: undefined }; console.log(‘a‘ in obj); // true console.log(obj.a === undefined); // true console.log(‘b‘ in obj); // false console.log(obj.b === undefined); // true (无法区分!)
3.2 方法二:Object.hasOwn()——现代且安全的“自身属性”检查
Object.hasOwn()是ES2022引入的新方法,旨在替代旧的obj.hasOwnProperty()。它接收两个参数:对象和属性键,返回一个布尔值,表示该属性是否为对象的自身属性(非继承)。
let animal = { eats: true }; let rabbit = { jumps: true, __proto__: animal }; console.log(Object.hasOwn(rabbit, ‘jumps‘)); // true console.log(Object.hasOwn(rabbit, ‘eats‘)); // false (是继承的)为什么推荐Object.hasOwn()overhasOwnProperty?
- 安全性:这是最重要的原因。
obj.hasOwnProperty(prop)是一个方法调用。如果对象本身没有hasOwnProperty方法(例如,通过Object.create(null)创建的纯字典对象),或者这个属性被意外覆盖了,调用就会失败。let dict = Object.create(null); dict.key = ‘value‘; // console.log(dict.hasOwnProperty(‘key‘)); // TypeError: dict.hasOwnProperty is not a function console.log(Object.hasOwn(dict, ‘key‘)); // true (完美工作) let obj = { hasOwnProperty: ‘oops!‘ }; // console.log(obj.hasOwnProperty(‘key‘)); // TypeError: obj.hasOwnProperty is not a function (字符串不可调用) console.log(Object.hasOwn(obj, ‘key‘)); // false (安全) - 静态方法:作为
Object的静态方法,它不依赖于目标对象的原型链,行为更加可靠和一致。 - 可读性:语义清晰,是现代代码的首选。
3.3 方法三:obj.hasOwnProperty()——经典但需谨慎的自身属性检查
在Object.hasOwn()普及之前,这是标准方法。它的功能与Object.hasOwn()相同,只检查自身属性。
let obj = { a: 1 }; console.log(obj.hasOwnProperty(‘a‘)); // true console.log(obj.hasOwnProperty(‘toString‘)); // false (继承的)使用时的“坑”与变通:如上节所述,直接调用obj.hasOwnProperty()有风险。安全的调用方式是使用Object.prototype上的方法,并通过call来调用:
let dict = Object.create(null); dict.key = ‘value‘; console.log(Object.prototype.hasOwnProperty.call(dict, ‘key‘)); // true但这显然不如Object.hasOwn(dict, ‘key‘)简洁明了。因此,在新项目中,应优先使用Object.hasOwn()。
3.4 方法四:obj.property !== undefined与obj[property]——基于值的粗略判断
这不是一个正式的方法,而是一种常见的模式,通过检查属性值是否为undefined来间接推断。
let obj = { a: 1, b: undefined }; console.log(obj.a !== undefined); // true console.log(obj.b !== undefined); // false (属性存在,但值为undefined) console.log(obj.c !== undefined); // false (属性不存在) // 或者更简短的写法(但注意0, ““, false等假值) console.log(obj.a); // 1 (truthy) console.log(obj.b); // undefined (falsy) console.log(obj.c); // undefined (falsy) 无法区分b和c!为什么不推荐作为“存在性”检查?这种方法有严重缺陷:
- 无法区分“属性不存在”和“属性值为
undefined”。如上例中的b和c。 - 如果属性值是其他假值(如
0,““,false,null),也会被误判为“不存在”。let config = { retryCount: 0, enableLog: false }; if (config.retryCount) { // 0是假值,条件不成立 console.log(‘Will retry‘); } if (config.enableLog) { // false是假值,条件不成立 console.log(‘Logging enabled‘); } // 即使属性存在,这里的逻辑也可能出错。
正确的使用场景:这种方法只适用于你明确知道该属性如果存在,其值绝不可能是undefined(或其他需要警惕的假值)的情况。更多时候,它被用于访问属性值,而非检查存在性。
实操心得:在我参与的代码审查中,看到用
if (obj.key)或if (obj.key !== undefined)来判断属性是否存在,通常都会提出修改意见。除非有非常特殊的理由,否则请坚持使用in或Object.hasOwn()来检查存在性,用单独的逻辑来处理值的真假判断。这能让你的代码意图更清晰,避免隐含的Bug。
4. 方法对比与综合应用场景分析
现在我们已经掌握了全部七种方法(删除3种,判断4种)。是时候把它们放在一起,根据不同的应用场景,做出最合适的选择了。
4.1 删除方法的选择矩阵
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 需要彻底移除属性,且不关心对象形状优化 | delete obj.prop | 最符合语义,永久移除。 |
| 性能关键路径,需保持对象“隐藏类”稳定 | obj.prop = undefined | 避免delete导致的性能抖动。 |
| 需要JSON序列化时自动过滤掉该字段 | obj.prop = undefined | JSON.stringify会忽略undefined值。 |
| 函数式编程,不可变数据更新 | 解构const {prop, …rest} = obj | 创建新对象,无副作用,符合React/Redux模式。 |
| 需要同时“删除”多个已知属性 | 解构 | 语法简洁,一目了然。 |
| 属性是“不可配置”的 | 无法删除 | 考虑用undefined覆盖,或重新设计数据结构。 |
4.2 判断方法的选择矩阵
| 你需要回答的问题 | 推荐方法 | 示例 | 注意 |
|---|---|---|---|
| 属性是否可访问(包括继承的)? | prop in obj | 检查‘toString‘ in obj | 包含原型链。 |
| 属性是否是对象自身的(不包括继承的)? | Object.hasOwn(obj, prop) | 遍历对象自身键时使用 | ES2022+,最安全。 |
| (旧环境)属性是否是对象自身的? | Object.prototype.hasOwnProperty.call(obj, prop) | 兼容性写法 | 比直接调用安全。 |
| 属性值是否有效(非undefined/null/空)? | 条件判断(如obj.prop != null) | if (obj.name) { … } | 这不是存在性检查,是值检查。 |
4.3 实战场景串联示例
假设我们有一个用户配置对象,需要实现一个updateUserSettings函数,它接收旧配置和一个更新补丁,返回一个新配置。要求:删除补丁中值为null的字段;跳过不存在的字段;更新其他字段。
function updateUserSettings(oldSettings, patch) { // 1. 首先浅拷贝原对象,作为更新的起点(不可变思想) let newSettings = { …oldSettings }; for (let key in patch) { // 2. 判断:补丁中的key是否在旧配置中存在?(这里我们只关心自身属性) if (!Object.hasOwn(oldSettings, key)) { console.warn(`忽略不存在的配置项: ${key}`); continue; } // 3. 判断:补丁中的值是否为null(表示要删除)? if (patch[key] === null) { // 4. 删除操作:为了保持对象结构稳定,我们选择设置为undefined // 同时,因为我们的配置后续可能被序列化存储,undefined会被JSON.stringify忽略,符合“删除”的语义。 newSettings[key] = undefined; // 注意:此时 key 仍然在 newSettings 中,但值为 undefined。 // 如果后续用 `in` 判断会为true,用 `Object.hasOwn` 判断也会为true。 } else { // 5. 更新操作 newSettings[key] = patch[key]; } } // 6. 可选:在返回前,如果我们想真正移除值为undefined的键,可以过滤一下。 // 但这会改变对象形状,根据性能需求决定。 // return Object.fromEntries(Object.entries(newSettings).filter(([_, v]) => v !== undefined)); return newSettings; } // 测试 const defaultSettings = { theme: ‘light‘, notifications: true, itemsPerPage: 20 }; const patch = { theme: ‘dark‘, notifications: null, itemsPerPage: 50, unknownKey: ‘test‘ }; const updated = updateUserSettings(defaultSettings, patch); console.log(updated); // 输出: { theme: ‘dark‘, notifications: undefined, itemsPerPage: 50 } // 控制台警告: 忽略不存在的配置项: unknownKey在这个例子中,我们综合运用了:
Object.hasOwn()进行安全的自身属性检查。obj[key] = undefined作为符合场景的“软删除”。- 解构
{ …oldSettings }作为不可变更新的起点。 - 明确区分了“属性存在性检查”和“属性值判断”(
patch[key] === null)。
5. 进阶话题与性能深度探讨
对于大多数应用,掌握上述方法已经足够。但如果你在处理超大规模数据、编写底层库或追求极致的性能,以下几个进阶话题值得深入了解。
5.1delete操作符与JavaScript引擎优化
现代JavaScript引擎(V8, SpiderMonkey, JavaScriptCore)使用“隐藏类”或“形状”系统来优化对象属性访问。当对象的结构稳定时,引擎可以生成非常高效的机器码。
delete如何破坏优化:当你删除一个属性时,对象可能会转换到一个新的隐藏类。如果代码中大量、动态地添加和删除属性,会导致引擎频繁地创建和切换隐藏类,优化被破坏,性能退回较慢的字典模式。- 性能测试对比:
// 测试用例1:频繁添加/删除不同属性 console.time(‘delete‘); let obj1 = {}; for (let i = 0; i < 100000; i++) { obj1[‘key‘ + i] = i; delete obj1[‘key‘ + i]; } console.timeEnd(‘delete‘); // 可能较慢 // 测试用例2:使用undefined覆盖 console.time(‘undefined‘); let obj2 = {}; for (let i = 0; i < 100000; i++) { obj2[‘key‘ + i] = i; obj2[‘key‘ + i] = undefined; } console.timeEnd(‘undefined‘); // 通常更快且稳定实测提醒:性能差异因引擎和场景而异。对于字面量初始化的、结构固定的对象,偶尔的
delete影响不大。但对于在热函数中动态变化的对象,差异会很明显。最佳实践是:对于需要高频操作的核心对象,预先定义好结构,避免使用delete。
5.2 不可枚举属性与in/hasOwn的关系
属性除了有值,还有描述其行为的“属性描述符”,其中enumerable(可枚举性)是关键之一。Object.defineProperty可以定义不可枚举属性。
let obj = {}; Object.defineProperty(obj, ‘hiddenSecret‘, { value: ‘I am here‘, enumerable: false // 不可枚举 }); obj.visibleProp = ‘You can see me‘; console.log(‘hiddenSecret‘ in obj); // true console.log(Object.hasOwn(obj, ‘hiddenSecret‘)); // true console.log(‘visibleProp‘ in obj); // true console.log(Object.hasOwn(obj, ‘visibleProp‘)); // true // 区别体现在遍历上: for (let key in obj) { console.log(key); // 只输出 ‘visibleProp‘ } console.log(Object.keys(obj)); // [‘visibleProp‘] console.log(Object.getOwnPropertyNames(obj)); // [‘hiddenSecret‘, ‘visibleProp‘] (包含所有自身属性名)关键点:in和Object.hasOwn()/hasOwnProperty()不关心属性是否可枚举。它们只检查属性是否存在。区分可枚举性主要影响for…in循环和Object.keys()的行为。像toString这样的内置方法通常就是不可枚举的。
5.3 使用Reflect.deleteProperty()进行删除
Reflect是ES6引入的全局对象,提供了一套用于拦截JavaScript操作的方法。Reflect.deleteProperty(target, propertyKey)在功能上等同于delete target[propertyKey],但它是一个函数。
let obj = { a: 1, b: 2 }; console.log(Reflect.deleteProperty(obj, ‘a‘)); // true console.log(obj); // { b: 2 } // 与delete操作符对比 console.log(delete obj.b); // true (操作符) console.log(Reflect.deleteProperty(obj, ‘c‘)); // true (属性不存在也返回true)使用Reflect.deleteProperty()的理由:
- 函数式风格:它总是返回一个布尔值(表示成功与否),而
delete操作符在删除不存在的属性时也返回true,但在严格模式下对不可配置属性会抛出错误。Reflect的方法行为更一致、可预测。 - 与
Proxy配合:Reflect的方法常与Proxy的捕获器(trap)一一对应,用于在代理中实现默认行为。let target = { foo: ‘bar‘ }; let handler = { deleteProperty(trapTarget, key) { if (key.startsWith(‘_‘)) { console.log(`禁止删除私有属性 ${key}`); return false; } // 调用默认的删除行为 return Reflect.deleteProperty(trapTarget, key); } }; let proxy = new Proxy(target, handler); delete proxy.foo; // 正常删除 delete proxy._secret; // 控制台输出:禁止删除私有属性 _secret, 返回false
对于日常开发,delete操作符更简洁。但在编写元编程(如Proxy)或需要统一函数式接口的库时,Reflect.deleteProperty()是更好的选择。
6. 常见陷阱与最佳实践总结
最后,我们来盘点一下在实际开发中最容易踩的坑,并总结成可以“抄作业”的最佳实践清单。
6.1 陷阱一:混淆“属性存在性”与“属性值真假”
这是最常见的逻辑错误。
// 错误示范 function getUserName(user) { if (user.name) { // 如果user.name是空字符串““,这里会判断为false! return user.name; } return ‘Guest‘; } // 正确做法:明确你的意图 function getUserName(user) { // 意图1:如果name属性存在(无论值是什么) if (‘name‘ in user) { return user.name; } // 意图2:如果name属性存在且不是假值(null, undefined, ““, 0, false, NaN) if (user.name) { return user.name; } // 意图3:如果name属性存在且不是null或undefined if (user.name != null) { // != 会排除null和undefined return user.name; } return ‘Guest‘; }规则:使用in或Object.hasOwn()检查存在性。使用条件判断(if (obj.prop)、if (obj.prop != null))检查值的有效性。两者目的不同,不要混用。
6.2 陷阱二:在遍历中修改对象
在for…in循环中直接使用delete删除当前正在遍历的对象属性,会导致不可预期的行为(某些属性可能被跳过)。
let obj = { a: 1, b: 2, c: 3 }; for (let key in obj) { if (key === ‘b‘) { delete obj[key]; // 危险操作! } console.log(key); // 输出可能不是 a, b, c }安全做法:先收集要删除的键,循环结束后再统一删除。
let obj = { a: 1, b: 2, c: 3 }; let keysToDelete = []; for (let key in obj) { if (key === ‘b‘) { keysToDelete.push(key); } } keysToDelete.forEach(key => delete obj[key]);或者,更现代的方法是使用Object.keys()获取键数组后再遍历,因为它是原对象快照。
let obj = { a: 1, b: 2, c: 3 }; Object.keys(obj).forEach(key => { if (key === ‘b‘) { delete obj[key]; // 此时遍历的是固定的键数组,相对安全 } });6.3 陷阱三:忽略属性描述符的影响
试图删除一个configurable: false的属性,在非严格模式下会静默失败(返回false),在严格模式下会直接报错。这在操作内置对象、Object.defineProperty定义的对象或某些库返回的冻结对象时容易遇到。
‘use strict‘; let obj = {}; Object.defineProperty(obj, ‘immutable‘, { value: 42, configurable: false }); delete obj.immutable; // TypeError: Cannot delete property ‘immutable‘ of #<Object>应对策略:在删除重要属性前,如果不确定其可配置性,可以使用Object.getOwnPropertyDescriptor()先查看描述符,或者用try…catch包裹删除操作。
6.4 最佳实践清单
判断属性是否存在:
- 首选
Object.hasOwn(obj, prop)(ES2022+)。 - 兼容环境用
Object.prototype.hasOwnProperty.call(obj, prop)。 - 需要检查原型链时用
prop in obj。 - 避免用
obj.prop !== undefined作为存在性检查。
- 首选
删除对象属性:
- 默认选择:语义明确的
delete obj.prop。 - 性能敏感/结构稳定:考虑
obj.prop = undefined。 - 函数式/不可变更新:使用解构
const {prop, …rest} = obj。 - 元编程/库开发:考虑
Reflect.deleteProperty()。
- 默认选择:语义明确的
遍历与操作:
- 避免在
for…in循环内直接增删对象属性。 - 使用
Object.keys()、Object.entries()获取快照后再操作更安全。
- 避免在
代码可读性:
- 将“删除属性”和“判断存在性”的代码意图写清楚,必要时添加注释。
- 对于设置为
undefined的“软删除”,在团队内明确其语义(是“逻辑删除”还是“待定”)。
安全性:
- 对待来自外部的对象(如API响应、用户输入),操作属性前做好防御性判断,避免因对象是
null、undefined或没有原型而导致程序崩溃。
- 对待来自外部的对象(如API响应、用户输入),操作属性前做好防御性判断,避免因对象是