什么是对象的属性拷贝

2485字

对于初学者来说,“对象属性拷贝”这个概念一开始可能会有点抽象,特别是和 DTO (Data Transfer Object) 一起出现时。

别担心,这个概念本身并不复杂。它其实就是解决一个很常见的编程问题:如何方便、安全地把数据从一个“篮子”(对象A)装到另一个“篮子”(对象B)里。


📝 关于「对象属性拷贝」的学习笔记

你好!很高兴你开始学习 DTO 相关的知识。在深入 DTO 之前,我们必须先掌握一个基础且非常实用的技能:对象属性拷贝 (Object Property Copying)

📌 1. 为什么需要这东西?从一个故事说起

想象一下,你正在开发一个网站。后端程序从数据库里查询出了一个用户(User)的完整信息。这个 User 对象可能长这样:

JavaScript

1
2
3
4
5
6
7
8
9
// 这是一个代表数据库里用户信息的对象 (我们称之为“实体” Entity)
let userFromDB = {
  id: 123,
  username: "zhangsan",
  password: "a_very_long_and_secret_password_hash", // 密码的哈希值,非常敏感!
  email: "zhangsan@example.com",
  createTime: "2023-10-27T10:00:00Z",
  role: "admin"
};

现在,前端页面需要显示用户的基本信息(ID、用户名、邮箱)。我们是不是应该直接把 userFromDB 这个对象整个发给前端呢?

绝对不行! 🙅‍♂️

如果我们直接发送,就会把 password(密码)和 role(角色)这种敏感信息也泄露给前端了。这是非常危险的。

所以,我们需要一个“中间人”,一个专门用来在网络上传输数据的“小包裹”。这个小包裹就是 DTO (Data Transfer Object)。它只包含前端需要的数据:

JavaScript

1
2
3
4
5
6
// 这是一个专门用于传输的用户信息对象 (DTO)
let userForFrontEnd = {
  id: 0,        // 准备接收数据
  username: "", // 准备接收数据
  email: ""   // 准备接收数据
};

那么问题来了:如何把 userFromDB 里的 id, username, email 的值,填到 userForFrontEnd 这个新对象里呢?

这就是“对象属性拷贝”大显身手的时刻!

核心思想:对象属性拷贝,就是将一个源对象 (Source Object) 的属性值,复制到另一个目标对象 (Target Object) 的同名属性中。


💡 2. 怎么拷贝?从手动到自动

2.1 最笨拙的方法:手动拷贝

最直观的方法,就是一个一个地赋值。

JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 源对象
let userFromDB = { id: 123, username: "zhangsan", password: "...", email: "zhangsan@example.com" };

// 目标对象
let userForFrontEnd = {}; // 创建一个空对象

// 手动一个一个地拷贝
userForFrontEnd.id = userFromDB.id;             // 把 123 拷贝过去
userForFrontEnd.username = userFromDB.username; // 把 "zhangsan" 拷贝过去
userForFrontEnd.email = userFromDB.email;       // 把 "zhangsan@example.com" 拷贝过去

// 现在,userForFrontEnd 就是 { id: 123, username: "zhangsan", email: "zhangsan@example.com" }
// 它很“干净”,不包含任何敏感信息,可以安全地发给前端了。

优点

  • 非常清晰,一目了然。

  • 可以精确控制拷贝哪些属性,忽略不需要的属性(比如 password)。

缺点

  • 如果对象有50个属性,手动写50行代码会非常繁琐且容易出错。

2.2 聪明一点的方法:使用工具库

几乎所有的编程语言都提供了简化这个过程的工具。在 Java 中有 BeanUtils.copyProperties(),在 JavaScript 中有 Object.assign() 或扩展运算符 ...。它们可以自动拷贝所有同名的属性。

以 JavaScript 的 Object.assign() 为例:

JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let source = { a: 1, b: 2 };
let target = { b: 3, c: 4 };

// Object.assign(目标, 源1, 源2, ...)
// 它会把所有源对象的属性拷贝到目标对象,如果属性名相同,后面的会覆盖前面的。
Object.assign(target, source);

// 结果是什么?
// 1. source 的 a:1 被拷贝到 target,target 变成 { b: 3, c: 4, a: 1 }
// 2. source 的 b:2 被拷贝到 target,覆盖了原来的 b:3,target 最终变成 { c: 4, a: 1, b: 2 }
console.log(target); // 输出 {c: 4, a: 1, b: 2}

用这个方法来解决我们最初的问题:

这种工具非常适合将一个简单的字典(比如前端发来的请求体)的属性拷贝到一个对象上。但是,从 Entity 拷贝到 DTO 时,由于我们想忽略某些属性,直接使用 Object.assign 仍然会把 password 等敏感属性拷贝过去。

因此,更专业的做法是使用专门的 Mapper (映射器) 库,例如 Java 的 MapStruct、ModelMapper,或者 .NET 的 AutoMapper。这些库可以让你配置映射规则,比如:“把 User 拷贝到 UserDTO 时,请忽略 password 字段”。


⚠️ 3. 重要的区别:浅拷贝 vs. 深拷贝

这是学习属性拷贝时必须理解的一个关键点,也是面试中经常被问到的问题。

3.1 浅拷贝 (Shallow Copy)

想象一下,你的对象里有一个属性,它的值本身也是一个对象。

JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let user = {
  name: "lisi",
  address: {        // address 是一个嵌套的对象
    city: "Beijing",
    street: "Wangfujing"
  }
};

let copiedUser = {};
Object.assign(copiedUser, user); // 这就是一种浅拷贝

浅拷贝的特点:

  • 它只拷贝对象的第一层属性。

  • 如果某个属性的值是一个基础类型(如数字、字符串),它会拷贝这个值。

  • 如果某个属性的值是一个引用类型(如另一个对象、数组),它只会拷贝这个引用的“地址”,而不是创建一个新的对象。

后果是什么?

usercopiedUseraddress 属性指向的是同一个内存地址里的同一个 { city: "Beijing", ... } 对象。

如果你修改 copiedUser 的地址,user 的地址也会跟着变!

JavaScript

1
2
3
copiedUser.address.city = "Shanghai"; // 修改拷贝后对象的城市

console.log(user.address.city); // 输出 "Shanghai"! 😱 **原始对象被意外修改了!**

适用场景:当你的对象结构很简单,没有嵌套的对象或数组时,浅拷贝速度快,效率高,完全够用。我们之前 UserUserDTO 的转换,就属于这种情况。

3.2 深拷贝 (Deep Copy)

深拷贝会彻底地、递归地复制一个对象的所有层级。

JavaScript

1
2
// (伪代码,不同语言实现方式不同)
let deepCopiedUser = deepCopy(user);

深拷贝的特点:

  • 它会创建一个全新的、一模一样但完全独立的对象。

  • 如果遇到嵌套的对象,它会为这个嵌套对象也创建一个全新的副本。

后果是什么?

userdeepCopiedUser 内部的所有部分都是独立的,互不影响。

如果你再次修改地址:

JavaScript

1
2
3
deepCopiedUser.address.city = "Shenzhen"; // 修改深拷贝后对象的城市

console.log(user.address.city); // 输出 "Beijing"。 ✅ **原始对象安然无恙!**

适用场景:当你需要一个对象的完整、独立的副本,并且不希望任何对副本的修改影响到原始对象时,就必须使用深拷贝。这在复杂的状态管理(如前端框架 Vuex/Redux)或需要历史记录功能时非常重要。


✅ 总结与回顾

  1. 是什么:对象属性拷贝就是把一个对象(源)的属性值,复制到另一个对象(目标)上。

  2. 为什么用:最常见的场景是为了构建 DTO。从包含敏感信息的完整对象(Entity)中,只挑选出安全、必需的属性,拷贝到一个新的 DTO 对象中,用于网络传输或视图展示。

  3. 怎么做

    • 手动拷贝:简单、可控,但繁琐。

    • 工具/库:如 Object.assign,可以自动拷贝同名属性。更专业的有 Mapper 库,可以配置复杂的映射规则。

  4. 关键区别

    • 浅拷贝 (Shallow Copy):只拷贝第一层,速度快。嵌套的对象共享同一个引用,修改一个会影响另一个。

    • 深拷贝 (Deep Copy):递归拷贝所有层,生成完全独立的新对象,互不干扰,但性能开销更大。

记住,它不是一个孤立的技术,而是编程中为了实现数据隔离、安全和封装而广泛使用的一种基本模式。

如对内容有异议,请联系关邮箱2285786274@qq.com修改