分享个很有意思的东西
今天在看新项目的代码的时候,发现一个很神奇的现象。
当接口进来的时候,被拦截器拦截了,此时拦截器给当前构造器中private readonly userJwtPayload: UserJwtPayload 进行了 Object.assign(this.userJwtPayload, payload);就是给它进行了赋值。之后回到控制器,在构造器中也有private readonly userJwtPayload: UserJwtPayload,此时,直接输出userJwtPayload就是有值的了。
这是为什么呢?
因为这两个类得到的 userJwtPayload 并不是两个独立的对象,而是指向内存中同一个对象的“钥匙”或“地址”(专业术语叫“引用”)。
所以,不是“一个类赋值,另一个类也自动被赋值”,而是“一个类通过钥匙进入房间修改了东西,另一个类晚点用同一把钥匙进入同一个房间,自然能看到被修改过的东西”**。
让我们用一个更详细的比喻和技术解释来拆解它。
1. 钥匙的比喻:对象 vs 对象的引用
想象一下,UserJwtPayload 这个对象实例是一个房子 🏠。
-
NestJS的依赖注入容器:是这栋房子的物业管理员 👨💼。
-
JwtInterceptor:是房子的保安 👮。
-
你的控制器 (Controller):是房子的主人 🧑💻。
现在,一个请求来了,整个流程是这样的:
物业管理员(DI容器) 为这次请求专门准备了一间全新的、空荡荡的房子(创建了一个 UserJwtPayload 对象实例)。
物业管理员复制了两把指向这间房子的钥匙 🔑。
他先把一把钥匙 🔑 交给了保安(JwtInterceptor)。保安在他的构造函数里拿到了这把钥匙,并把它存放在口袋里(this.userJwtPayload)。
保安用钥匙开门进入房子,检查了访客的身份(解析JWT),然后在房子的桌子上放上了一份访客资料档案(Object.assign(this.userJwtPayload, payload))。然后保安就锁门出来了。
接着,物业管理员把另一把一模一样的钥匙 🔑 交给了主人(你的控制器)。主人在他的构造函数里也拿到了这把钥匙,并把它也放在口袋里(this.userJwtPayload)。
当主人需要用户信息时,他用自己的钥匙开门,进入的还是之前那间房子。他一进门,就看到了桌子上那份被保安放上去的访客资料档案。
结论: 保安和主人并没有互相通信。他们只是通过物业管理员,拿到了通往同一个地方的钥匙。因为他们操作的是同一个房子,所以保安做的改动,主人自然能看到。
2. 技术层面的解释:值 vs 引用
在大多数编程语言中,变量可以存储两种东西:
-
值类型 (Value Types):像数字、布尔值。当你把一个数字变量赋给另一个变量时,你是复制了这个值。
let a = 10;
let b = a; // b 得到的是 10 这个值的副本
b = 20; // 修改 b 不会影响 a
console.log(a); // 输出 10 -
引用类型 (Reference Types):像对象、数组。当你把一个对象变量赋给另一个变量时,你复制的是指向该对象的内存地址(引用),而不是对象本身。
let objA = { name: 'Alice' };
let objB = objA; // objB 得到的是指向 { name: 'Alice' } 这个对象的内存地址的副本// 通过 objB 修改对象
objB.name = 'Bob';// 因为 objA 和 objB 指向同一个对象,所以通过 objA 也能看到修改
console.log(objA.name); // 输出 'Bob'
我的 JwtInterceptor 和 Controller 就是上面 objA 和 objB 的情况。
NestJS DI 容器 new UserJwtPayload(),在内存中创建了一个对象。
它把这个对象的引用(内存地址)传给了 JwtInterceptor 的构造函数。JwtInterceptor 内部的 this.userJwtPayload 保存了这个引用。
它又把同一个引用传给了 Controller 的构造函数。Controller 内部的 this.userJwtPayload 也保存了这个引用。
因此,JwtInterceptor 里的 this.userJwtPayload 和 Controller 里的 this.userJwtPayload 是两个不同的变量,但它们存储了相同的值——那个指向唯一 UserJwtPayload 实例的内存地址。
关于 readonly
你可能会想,readonly 不是只读吗,为什么还能改?
readonly 关键字限制的是变量本身不能被重新赋值。也就是说,你不能让 this.userJwtPayload 这个“口袋”去装一把新钥匙。
// 在 Interceptor 或 Controller 内部,这样做会报错
this.userJwtPayload = new UserJwtPayload(); // 错误!不能给 readonly 属性重新赋值
但是,readonly 不限制你通过这把钥匙去修改房子里的东西。Object.assign(this.userJwtPayload, payload) 正是在做这件事:它没有改变 this.userJwtPayload 指向的那个对象,而是修改了那个对象内部的属性。这是完全允许的。
评论前必须登录!
注册