news 2026/9/8 4:20:31

JavaScript 删除对象属性全指南:从 delete 到性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 删除对象属性全指南:从 delete 到性能优化

前几天在交流群里看到有人问:JS 里怎么删除一个对象的属性?底下一片回答:delete。这个回答对不对?对,但远远不够。如果这段代码写在热循环里,或者你试图删掉一个不可配置的属性,直接写 delete 很容易踩坑。我想把“删除元素属性”这件事从头到尾讲透——从 delete 的基本用法,到它背后的引擎原理,到性能损耗到底从哪里来,再到工作中真正值得推荐的几种删除思路,最后结合 DOM 元素属性的场景聊一聊。这篇东西适合刚入门前端的朋友,也适合写了一两年 JS 但没深究过属性机制的开发者。

1. 删除元素属性:先从最熟悉的方案说起

1.1 delete 操作符的基本用法

大多数人对 delete 的第一印象是“用来删对象属性”。确实,这是它最典型的应用场景。

const user = { name: '张三', age: 28, email: 'zhangsan@example.com' }; delete user.email; console.log(user); // { name: '张三', age: 28 }

这里 delete 干了几件事:把 user 对象上的 email 属性彻底移除,属性不见了,对象的内存里也不再持有对这个值的引用。如果你删的是一个对象类型的属性值,那这个值如果没有其他引用,会被垃圾回收机制回收掉。这一点很重要,delete 不只是把属性标记为 undefined,它是真真切切地从对象上“拔掉”了这个键。

delete 可以链式访问:

const obj = { a: { b: { c: 1 } } }; delete obj.a.b.c; console.log(obj); // { a: { b: {} } }

这里删除的是内层对象 obj.a.b 上的 c 属性,外层 obj.a 和 obj.a.b 都还在。这一点很多人会搞混,以为 delete 会递归清理整个链条,其实不会。它只删除你明确指定的那一个属性,不会动其他层级。

delete 也可以操作数组元素:

const arr = [1, 2, 3, 4]; delete arr[1]; console.log(arr); // [1, empty, 3, 4] console.log(arr.length); // 4

注意,delete 删除数组元素后,数组长度不会变,被删的位置会留下一个空槽位。这不是 undefined,是真正的 empty。遍历数组时 forEach 会跳过空槽,但 for 循环不会,这个差异经常导致隐藏 bug,后面我会专门展开。

1.2 为什么说 delete 不一定是最优解

很多人写代码时形成了条件反射:删属性,用 delete。在大多数业务场景下这么写没有问题,但如果深入一层,delete 有几个不太友好的地方。

第一,性能问题。V8 引擎为了快速访问对象属性,会把对象的结构做成隐藏类(Hidden Class)。每次 delete 一个属性,都会破坏对象原有的结构,导致引擎不得不重新创建隐藏类,后续属性访问的优化全部失效。短期看无所谓,如果在循环里高频删属性,性能损耗会非常明显。

第二,返回值容易误导。delete 操作符返回布尔值,表示删除是否成功。

const obj = { a: 1 }; console.log(delete obj.a); // true,删除成功 console.log(delete obj.b); // true,属性不存在也返回 true

你没看错,删除一个不存在的属性,它反而返回 true。很多人以为 delete 返回 false 就代表属性不存在,这个理解是错的。delete 返回值只代表“属性是否能被删除”或“是否已经不存在了”,不代表“删除前属性是否真实存在”。

第三,严格模式下的限制。如果在代码里开启了 "use strict",delete 一个不可配置的属性,或者删除一个未声明的变量,都会直接抛 TypeError 或 SyntaxError。这在某些项目里会让人措手不及。

第四,不可删除的属性。用 Object.defineProperty 定义时把 configurable 设为 false 的属性,delete 删不掉。还有对象继承自原型链上的属性,delete 也无法删除——它只会返回 true,但属性仍然存在。这里涉及属性描述符的机制,我在第 2 部分细讲。

1.3 面向对象与 DOM 元素的属性删除

说完普通对象,再来看前端另一个高频场景:DOM 元素的属性。这里的“删除属性”和普通对象不完全一样,因为 DOM 元素同时存在两种“属性”概念——HTML attribute 和 DOM property。

HTML attribute 是指写在标签里的那些东西:

<div id="app">const el = document.getElementById('app'); el.removeAttribute('title'); el.removeAttribute('data-id');

如果想要一次删多个,可以循环处理:

['title', 'data-id'].forEach(attr => el.removeAttribute(attr));

也可以用 setAttribute 把值置空,但那样属性键还在,只是值变成空字符串,并不是真正删除。

而 DOM property 是指元素对象上面挂的属性,比如 el.id、el.title。这些属性严格说属于 DOM 对象的 JavaScript 属性,普通对象上 delete 的规则同样适用,但一般不推荐对 DOM property 使用 delete,因为浏览器内部对 DOM 对象有额外的封装逻辑,直接 delete 可能会导致后续行为不一致。稳妥做法是用 removeAttribute 处理 attribute,或者把 property 赋值成默认值。

从这一层对比可以看到,JS 里“删除元素属性”至少包含两类场景:普通对象/数组的属性删除,以及 DOM 元素的属性删除。二者手段不同、原理不同,需要分开理解。

2. 删除属性背后的 JS 引擎原理

2.1 属性存储与隐藏类机制

要理解 delete 为什么慢,得先明白 V8 是怎么存属性的。很多人以为对象就是一张简单的哈希表,键映射值。理论上没错,但现代 JS 引擎为了加速访问,做的事情远比这复杂。

V8 会把对象属性分成两类:对象内属性(in-object properties)和属性存储区属性。当创建一个对象并给它添加属性时,V8 会记录属性的添加顺序和类型,形成一个“隐藏类”,引擎内部叫 Map(注意这不是 ES6 的 Map,是 Hidden Map 的概念,V8 里叫 HiddenClass)。同一段代码创建出来的对象,如果属性结构一致,它们会共享同一个隐藏类,这样属性访问就变成固定偏移量的内存读取,比哈希查找快得多。

function createUser(name, age) { return { name: name, age: age }; } const u1 = createUser('张三', 28); const u2 = createUser('李四', 32);

u1 和 u2 有相同的属性结构,V8 会给它们分配同一个隐藏类,访问 u1.name 时,引擎直接根据隐藏类里的偏移量取内存,不需要做字符串哈希匹配。

但如果你在某个时刻 delete 了其中一个对象的属性,比如:

delete u1.age;

u1 的结构变了,它不再能和 u2 共享隐藏类,V8 必须为 u1 重新创建隐藏类,而这个对象后续的属性访问也要退化为更慢的字典模式。更糟的是,如果这个对象被频繁 delete 属性再添加属性,隐藏类切换会反复发生,性能损耗是肉眼可见的。

2.2 delete 之所以影响性能的根因

再往深一层说,delete 操作在 V8 里不只影响单个对象。当一个对象的属性被 delete 后,V8 对该对象的所有优化前提都失效了:inline cache 失效、隐藏类迁移、属性访问变成查字典。所以在实际编码时,如果你有一个对象需要频繁地“删了再加”,引擎会把它判定为“形态不稳定”的对象,后续的所有操作都按最慢路径处理。

我做过一个简单的基准测试(Node.js 环境下,对一个大对象循环删除属性再遍历),对比三种操作:

// 场景1:delete 删除 const obj1 = {}; for (let i = 0; i < 1000; i++) { const key = 'key_' + i; obj1[key] = i; } console.time('delete'); Object.keys(obj1).forEach(key => delete obj1[key]); console.timeEnd('delete'); // 场景2:置为 undefined const obj2 = {}; for (let i = 0; i < 1000; i++) { const key = 'key_' + i; obj2[key] = i; } console.time('undefined'); Object.keys(obj2).forEach(key => obj2[key] = undefined); console.timeEnd('undefined');

实测下来 delete 的耗时大约是直接赋值的 5 到 10 倍。这个比例在不同数据规模下会有波动,但趋势很稳定:delete 越频繁,性能劣势越明显。如果对象里面还有大量嵌套结构,删嵌套属性的开销会更高,因为每次 delete 都要触达最后一层属性并修改其隐藏类。

这不是说 delete 就不能用。业务里偶尔删一两个属性,性能差异根本感知不到。怕的是在循环体、高频事件回调、渲染热路径里使用 delete。理解引擎原理的意义,就是让你在写关键代码时能做出对的选择。

2.3 不可配置属性与原型链上的坑

关于 delete 删不掉的属性,官方术语叫“不可配置属性”。所有属性都有一个属性描述符(Property Descriptor),里面包含几个配置项:writable、enumerable、configurable。configurable 只有在为 true 时,属性才允许被 delete 删除。

const obj = {}; Object.defineProperty(obj, 'fixed', { value: 42, configurable: false // 不可配置 }); console.log(delete obj.fixed); // false,删不掉 console.log(obj.fixed); // 42,依然存在

普通字面量创建的对象,属性 configurable 默认是 true,所以 delete 基本都能成功。但如果你用 defineProperty 定义属性时设置了 configurable: false,那这个属性就成了“钉子户”,delete 怎么删都删不掉,严格模式下还会直接抛错。

原型链上的属性也一样。delete 只作用于对象自身的属性,不沿原型链向上寻找。

function Person() {} Person.prototype.name = '张三'; const p = new Person(); delete p.name; console.log(p.name); // 还是 '张三',因为 name 在原型上,delete 没有动它

想要从原型上删除,得直接操作原型对象:

delete Person.prototype.name; console.log(p.name); // undefined

但这种操作通常不推荐,因为会污染所有基于该原型创建的实例。实际业务中你更可能想要的是“属性遮蔽”——在实例上定义一个同名属性,而不是真的去删原型属性。理解 delete 的边界,能帮你避免在这种细节上吃大亏。

3. 更高效、更安全的删除方案

3.1 置空属性:用 null / undefined 占位

既然 delete 慢、破坏结构,那最简单的替代方案就是不删,而是把属性值置为 null 或 undefined。

const user = { name: '张三', age: 28, email: 'zhangsan@example.com' }; user.email = undefined; // 或者 user.email = null;

这样做最大的好处是对象结构不变,隐藏类不失效,后续访问性能不受影响。很多状态管理库(比如 Redux、Vuex)在处理“删除字段”时,并不真的 delete,而是把字段值置为 null 或 undefined,因为这样可以保持数据结构稳定,方便比较和调试。

但这里要区分一个取舍:置为 undefined 后,这个属性仍然存在,Object.keys、for...in、JSON.stringify 的默认行为都会受到影响。如果用 Object.keys 遍历对象,你仍然会看到这个键;如果用 JSON.stringify 序列化,值为 undefined 的属性会被直接跳过,值为 null 的属性会保留为 null。这两者行为不一样,需要根据业务需求选择。

const obj1 = { a: 1, b: undefined }; const obj2 = { a: 1, b: null }; JSON.stringify(obj1); // '{"a":1}' JSON.stringify(obj2); // '{"a":1,"b":null}'

所以如果你想“删除”属性后不希望它出现在 JSON 里,置为 undefined 的效果类似 delete(都从 JSON 里消失);如果你希望结构完整、字段保留但值为空,用 null 更合适。这个细节在前后端联调时很容易引发问题,建议提前约定好。

3.2 解构忽略:函数式剔除的灵活运用

ES6 的解构赋值提供了一种非常优雅的“剔除属性”方式——通过剩余属性(rest)语法,把要删除的字段先解构出来,剩下的就是你要的对象。

const user = { name: '张三', age: 28, email: 'zhangsan@example.com' }; const { email, ...rest } = user; console.log(rest); // { name: '张三', age: 28 }

这里的变量 email 在解构中被“忽略”了,rest 变量接收了剩余属性,形成一个新对象。原对象 user 没有被修改,这符合不可变数据的理念,也意味着没有隐藏类被破坏的问题。在新对象上,属性访问依然是高效的,因为 rest 生成的是全新对象,与新隐藏类天然匹配。

如果要删除多个属性,也很灵活:

const { email, age, ...rest } = user; // 同时剔除 email 和 age

这种方法适合函数式风格比较浓的代码库。比如在 React 项目里经常有这样的场景:组件的 props 里包含一些不想传到 DOM 上的自定义属性,可以直接解构剔除。

function MyComponent({ onClick, className, style, ...otherProps }) { return ( <div onClick={onClick} className={className} style={style} {...otherProps}> 内容 </div> ); }

otherProps 里就是所有没有显式解构出来的属性,从而避免了把多余属性传给 DOM 元素。这也是 Vue 里 $attrs 概念的实质,本质都是利用“剩余属性”做过滤。

但要注意,解构忽略只对一层有效。如果对象嵌套很深,你需要逐层解构:

const data = { user: { name: '张三', age: 28 }, meta: { page: 1 } }; const { user: { name, ...userRest }, meta } = data; // userRest = { age: 28 }

还有一点,解构出来的新对象是浅拷贝,嵌套对象的引用仍然共享。如果嵌套的子对象需要深拷贝,单纯解构是做不到的,得配合结构化克隆或其他方案。

3.3 白名单 / 黑名单过滤:数据清洗的正规军

解构适合“已知要删哪几个字段”的静态场景。如果字段是动态的,或者你根本不知道对象里有哪些字段,更实用的做法是用白名单或黑名单配合 reduce、filter 等数组方法进行过滤。

白名单思路:只保留你需要的关键字段。

const allowedKeys = ['name', 'age']; function pick(obj, keys) { return keys.reduce((acc, key) => { if (obj[key] !== undefined) { acc[key] = obj[key]; } return acc; }, {}); } const user = { name: '张三', age: 28, email: 'zhangsan@example.com', password: '123456' }; const safeUser = pick(user, allowedKeys); // { name: '张三', age: 28 }

黑名单思路:排除不需要的字段。

function omit(obj, keys) { return Object.keys(obj) .filter(key => !keys.includes(key)) .reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {}); } const safeUser = omit(user, ['password']); // { name: '张三', age: 28, email: 'zhangsan@example.com' }

这两种方式都是生成新对象,不改变原对象,也不触碰 delete,所以性能稳定。而且它们天然适合批量处理,比如接口返回的数组:

const users = [{ id: 1, name: '张三', password: 'x' }, { id: 2, name: '李四', password: 'y' }]; const safeUsers = users.map(user => omit(user, ['password']));

在很多后端返回数据直接渲染到前端页面的场景,我会首先用白名单/黑名单把不需要的字段过滤掉。这既避免了把敏感字段泄露给前端,又让组件里面拿到的数据更干净,不需要到处操心“这个字段到底要不要传”。实际经验告诉我,这比在组件里一个一个 delete 靠谱得多。

4. 实战场景:什么时候该删,什么时候不该删

4.1 接口数据脱敏

我最常遇到删除属性需求的场景,就是接口数据脱敏。后端接口经常返回一整个用户对象,里面带着密码哈希、内部手机号、第三方凭证之类不该暴露给前端的字段。早年的做法是后端过滤,但现在很多接口是 BFF 或直出的,前端必须自己处理。

这种场景下,delete 勉强能用,但更推荐用白名单。原因很简单:白名单是“默认拒绝”,字段没列出来就进不来,圈定范围后可维护性最好。万一后端悄悄多返回了一个新字段,白名单天然帮你挡在外面,黑名单则可能会漏。

const PUBLIC_USER_FIELDS = ['id', 'name', 'avatar', 'bio']; function toPublicUser(user) { return pick(user, PUBLIC_USER_FIELDS); }

如果强调性能,这个 pick 函数可以用展开运算符优化成更紧凑的写法:

function toPublicUser(user) { const { password, phone, token, ...safeUser } = user; return safeUser; }

但要注意,这种解构写法写死了要剔除的字段,如果字段新增,代码也要跟着更新。基于列表的 pick/omit 更适合策略集中管理。

4.2 表单数据提交前过滤

另一个高频场景是表单数据提交。现在很多表单库都支持双向绑定或者表单状态管理,提交时拿到的数据对象里常常混着不该提交的字段,比如用户没有填写的空值、中间状态字段、UI 相关的临时字段。

如果只是想要“去掉所有空字符串字段”,可以用黑名单加正则:

function cleanEmptyFields(data) { return Object.entries(data) .filter(([key, value]) => value !== '' && value !== null && value !== undefined) .reduce((acc, [key, value]) => { acc[key] = value; return acc; }, {}); }

这种做法不会改变原表单对象,也不影响表单库的受控状态,提交到后端的就是一个清爽的数据对象。如果后端对字段有严格要求,比如某些字段不能传空字符串,否则校验失败,这个方案能直接救急。

4.3 缓存更新与状态管理

在状态管理或者缓存场景,删除属性这个需求也很常见。比如实现一个简单的 LRU 缓存,容量满了要淘汰最久未使用的 key。这种场景下,delete 其实是必要的——因为容量限制要求 key 真正消失,而不是留着占位,否则 Object.keys().length 统计会不准。

class SimpleCache { constructor(limit = 10) { this.limit = limit; this.cache = Object.create(null); } get(key) { if (this.cache[key] !== undefined) { return this.cache[key]; } return undefined; } set(key, value) { if (this.get(key) !== undefined) { this.cache[key] = value; } else { const keys = Object.keys(this.cache); if (keys.length >= this.limit) { delete this.cache[keys[0]]; } this.cache[key] = value; } } }

删除 key 时确实需要 delete,因为用 undefined 占位仍然会让 keys.length 保持不减,缓存永远无法淘汰旧数据。但注意,这里我用 Object.create(null) 创建对象,它的原型是 null,没有继承 Object.prototype 上的属性,相比普通对象更适合做纯键值存储。这个细节很多文章都提过,但真正意识到它意义的人不多。

在 React/Vue 这类框架的状态管理里,如果你用不可变数据,删除一个字段通常不是 delete,而是解构生成新对象:

// React setState 中剔除某个字段 setState(prev => { const { unwanted, ...rest } = prev; return rest; });

这样改动的对象是全新的,React 能准确识别变化并触发渲染,如果用 delete 修改原对象,在不可变数据规范下反而会引发问题。

4.4 DOM 元素属性操作实战

DOM 场景下的删除属性,前面已经提过 removeAttribute,这里再补一个综合案例。假设你要实现一个“tooltip 属性清理”的批量操作:

function clearUnsafeAttributes(root) { const elements = root.querySelectorAll('*'); const forbidden = ['onclick', 'onerror', 'href', 'src']; elements.forEach(el => { forbidden.forEach(attr => { el.removeAttribute(attr); }); }); }

在富文本渲染、用户生成内容展示的前端场景中,这种清理很有用。但要注意,removeAttribute 更准确的名字是“移除 HTML 特性”,它不会清理元素对象上的某些 property,比如 el.value。如果你要清空表单控件的值,应该用赋值的方式:

input.value = '';

而如果想要移除整个 DOM 节点,那是 removeChild 或 remove 方法,跟属性删除就是另一回事了:

el.remove(); // 直接移除元素

把这个边界理清楚,写出来的代码才不容易混淆概念。

5. 常见问题与避坑实录

5.1 问题速查表

我整理了工作中常见的几个“删除元素属性”问题,直接在表格里对照着看。

问题现象原因推荐方案
delete 返回 true 但属性还在原型链上的属性删不掉delete 只处理自身属性用 Object.prototype.hasOwnProperty 检查后再决定是否 delete;真需要删就操作原型对象
严格模式下 delete 报错TypeError: Cannot delete property属性 configurable 为 false检查 defineProperty 配置;如果是内置 API 返回的对象,别硬删,换白名单
删除数组元素后 forEach 跳过它遍历行为怪异delete 数组元素会产生 empty 槽需要紧凑数组用 splice;需要删除最后一个用 pop;需要保留长度用 filter 生成新数组
删除属性后 JSON.stringify 结果不符合预期undefined 被忽略、null 保留序列化机制不同根据下游需求决定用 undefined 还是 null
delete 对象属性后性能变差高频调用有明显卡顿V8 隐藏类失效、退化为字典模式热路径用置空/解构/白名单替代

这里特别想强调数组场景。很多人会把数组当对象用,随手 delete arr[index],结果 arr.length 没变,遍历却踩坑。实际开发中,如果要删除数组里的某个元素,优先考虑 splice、filter、pop、shift 这些数组专用方法。需要不要最后一个都行,filter 生成新数组最直观:

const numbers = [1, 2, 3, 4, 5]; const noThree = numbers.filter(n => n !== 3); // [1, 2, 4, 5]

5.2 关于 delete 的两个高频面试题

有一道很经典的面试题:delete 能删除一个变量吗?

答案是不能,只要这个变量是用 var、let、const 声明的。

var a = 1; delete a; console.log(a); // 1,a 还在

var 声明的全局变量会挂在 window 上,但它的 configurable 是 false,所以 delete 删不掉。而如果你在浏览器里直接给 window 添加属性:

window.b = 2; delete window.b; console.log(b); // 这里会报错,因为 b 已经不存在了

window.b 这种通过赋值创建的全局属性,configurable 是 true,能删掉。var a = 1 和 window.b = 2 看起来结果很像,但背后的属性描述符完全不同。这也解释了为什么“全局变量能删/不能删”经常被拿来考察属性描述符和声明机制的底层理解。

另一个高频问题:delete 一个不存在的属性返回什么?返回 true。严格模式下也返回 true,不会抛错。这个行为容易跟 in 操作符混淆,很多人以为返回 false 才代表属性不存在,其实 delete 的返回值只反映“属性是否已消失或不可存在”,并不代表操作前属性是否存在。

const obj = { a: 1 }; console.log('a' in obj); // true console.log('b' in obj); // false console.log(delete obj.a); // true console.log(delete obj.b); // true,还是 true console.log('b' in obj); // false,属性确实没有

想要判断属性是否存在时,应该用 in 或 Object.prototype.hasOwnProperty,而不是看 delete 的返回值。

5.3 我的一个真实调试经历

最后分享一个我自己踩过的坑。之前做过一个表格组件的性能优化,表格会缓存一份“原始数据”和一份“展示数据”,用户操作后需要把某些字段从展示数据里剔除。最初实现是直接在循环里 delete 字段,数据量小的时候完全没感觉。后来表格扩展到一次渲染几千行,每次操作后都要循环调用 delete,页面明显卡顿,Chrome 的 Performance 面板一抓,发现 DeleteObjectProperty 占了快一半的脚本时间。

后来把循环里的 delete 换成了解构生成新对象的方式,只改了几行代码,性能问题当场消失。这件事给我的经验是:单次 delete 没什么,但循环和高频里一定要警惕。如果你发现某个对象要反复更新、删字段,就不要再把它当成一个固定结构来维护了,直接用新对象替换或者用 Map 这种更合适的数据结构。

顺便说一下,Map 在需要高频增删键值对的时候,其实是比普通对象更合适的选择。Map 的 delete 是专门为键值对操作设计的,底层实现上不会像普通对象那样受隐藏类影响,语义也明确:

const cache = new Map(); cache.set('name', '张三'); cache.delete('name'); console.log(cache.size); // 0

所以在决定怎么删属性之前,先问自己一个问题:这个数据到底该不该用普通对象来存?如果是不断变化的键值集合,Map 可能从一开始就是更正确的选择。

写在最后的一点经验

我对删除属性的态度,经历了三个阶段。最早是无脑 delete,后来被性能问题捶打后开始尽量避免,再到后来明白:没有银弹,只有场景。对于频繁增删的集合,用 Map;对于业务对象偶尔删一两个属性,delete 一点问题没有,代码也更直白;对于前后端数据交互,白名单过滤最稳;对于不可变数据规范项目,解构生成新对象是标准答案。

最后再分享一个小技巧:在真的决定用 delete 之前,先想清楚你要的是“属性消失”还是“值不可用”。有时候你想要的只是值不被读取、不被序列化,那 undefined 足够;有时候你要求对象结构绝对精简,那才需要 delete。把这个差异想明白,很多代码都可以写得更稳、更快,也更好维护。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 4:18:51

Docker实战:镜像容器与Dockerfile打包部署全攻略

最近在帮团队做项目环境交付时&#xff0c;反复踩到同一个坑&#xff1a;本地开发环境一切正常&#xff0c;换到测试服务器或新同事电脑上&#xff0c;不是缺依赖&#xff0c;就是版本对不上&#xff0c;环境配置能折腾大半天。后来把整套项目环境用 Docker 打包后&#xff0c;…

作者头像 李华
网站建设 2026/9/8 4:17:09

Arm-astc-encoder源码级解析:ASTC纹理压缩原理与移动端优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 4:16:32

卡通风格角色技能系统开发指南:从框架设计到实战部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 4:11:26

Modbus RTU调试实战:RS485物理层与参数配置排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 4:10:42

浏览器端 JavaScript 在线录音与 MP3 导出实现指南

简介&#xff1a;面向Web前端开发者的一套在线录音方案代码&#xff0c;解决在浏览器中实时获取麦克风音频并导出MP3的核心需求&#xff0c;适用于在线教育、语音留言、录音笔记等场景。压缩包共6个文件、约58KB&#xff0c;包含3个JavaScript文件负责录音控制、实时处理与MP3编…

作者头像 李华
网站建设 2026/9/8 4:10:05

拓扑生成范式:提升GPU利用率与破解模型黑箱的关键钥匙

上个月有个做训练集群的朋友跟我抱怨&#xff0c;说好不容易从供应商那边排到了几十张加速卡&#xff0c;结果一跑起来GPU利用率只有三成多&#xff0c;WBM曲线看一整天都是锯齿&#xff0c;光看一眼就焦虑。我问了他一句&#xff1a;你考虑过你的计算拓扑是平坦的还是有层次的…

作者头像 李华