浅拷贝用于备份拖拽元素的初始表面状态(如className、dataset、style.cssText等),避免拖拽中修改影响还原;因只复制第一层属性,适用于简单DOM状态快照,不适用于嵌套对象或动态属性。

在拖拽放置(Drag and Drop)中,浅拷贝常用于备份被拖拽元素的初始状态(比如 class、dataset、样式等可直接读取的属性),但要注意:它只复制对象第一层属性,不递归复制嵌套对象。适用于简单 DOM 元素状态快照,不适用于深层依赖或动态计算属性。
为什么用浅拷贝而不是直接引用?
拖拽过程中元素可能被临时修改(如添加 dragging 类、改变 opacity、禁用交互等)。若直接保存对原元素的引用,后续读取时拿到的是已被修改的状态,无法还原。浅拷贝能保留拖拽开始前那一刻的“表面状态”。
常用浅拷贝方式及适用场景
1. 使用 Object.assign() 备份元素属性
适合备份 element.className、element.dataset、element.style.cssText 等基础字段:
let initialData = {};
element.addEventListener('dragstart', (e) => {
initialData = Object.assign({}, {
className: element.className,
dataset: { ...element.dataset }, // 展开是浅拷贝 dataset 的键值
cssText: element.style.cssText,
disabled: element.disabled,
draggable: element.draggable
});
});
2. 使用展开语法 {...} 快速提取关键字段
更简洁,语义清晰,推荐用于已知结构的轻量备份:
element.addEventListener('dragstart', () => {
const backup = {
className: element.className,
dataset: { ...element.dataset },
style: element.style.cssText,
ariaHidden: element.getAttribute('aria-hidden')
};
// 存入 dataTransfer 或闭包变量中供 drop 时使用
e.dataTransfer.setData('text/plain', JSON.stringify(backup));
});
3. 注意不能浅拷贝的“伪属性”
以下属性看似可读,但浅拷贝后无实际还原价值,需单独处理:
-
element.children/element.childNodes:是实时集合,拷贝引用无效;如需结构备份,应序列化 HTML 字符串(element.outerHTML) -
element.getBoundingClientRect():返回对象每次调用都不同,不适合存为初始状态 -
element.classList:是 DOMTokenList,不能直接拷贝;应转成数组或字符串([...element.classList]或element.className)
在 drop 后如何安全还原初始状态?
还原时不要盲目覆盖所有字段,优先使用“差异还原”策略,避免误删新增属性:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 仅恢复明确备份过的字段(如 className、dataset、style.cssText)
- 还原
dataset前先清空:Object.keys(element.dataset).forEach(k => delete element.dataset[k]),再逐个赋值 - 还原
style.cssText会清除所有内联样式,如只需部分恢复,建议单独存style.opacity、style.visibility等关键项 - 若拖拽中修改了父级关系(如 appendTo 新容器),还原状态前确保元素已在原始位置(可通过
element.parentNode.insertBefore(element, refNode)回插)
一个实用的封装示例
将备份与还原逻辑收拢为可复用函数:
立即学习“Java免费学习笔记(深入)”;
function createDragState(element) {
return {
className: element.className,
dataset: { ...element.dataset },
style: element.style.cssText,
disabled: element.disabled,
ariaHidden: element.getAttribute('aria-hidden'),
restore() {
element.className = this.className;
Object.keys(element.dataset).forEach(k => delete element.dataset[k]);
Object.assign(element.dataset, this.dataset);
element.style.cssText = this.style;
element.disabled = this.disabled;
if (this.ariaHidden !== null) {
element.setAttribute('aria-hidden', this.ariaHidden);
} else {
element.removeAttribute('aria-hidden');
}
}
};
}
// 使用
element.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('application/json', JSON.stringify(createDragState(element)));
});
element.addEventListener('dragend', () => {
// 可选:自动还原(如果未被 drop 消费)
const state = createDragState(element);
state.restore();
});

















