Mongoose模型对象存入Session后方法丢失,此现象是否正常?
嘿,这个问题太常见了,我来给你掰扯清楚为啥会这样,以及怎么解决~
首先你得明白:Mongoose查询返回的puppy不是普通的JavaScript对象,它是Mongoose文档实例——自带了Mongoose封装的一堆方法(比如你定义的bark()、内置的save()),还有很多内部的属性、数据库连接关联信息,这些都挂在对象的原型链上。
但Session存储的核心逻辑是序列化(Serialize):把对象转成JSON字符串存起来,下次取的时候再反序列化(Deserialize)转成普通JS对象。而JSON序列化有个限制:它只会保留对象的可枚举自身属性,原型链上的方法、内部不可枚举的属性都会被直接丢弃。
所以当你把puppy存进Session再取出来时,它已经变成了一个没有任何Mongoose魔法的普通JS对象,自然调用bark()会报错。
给你看个直观的例子:
// 刚查出来的是Mongoose文档实例 const freshPuppy = await Puppies.findOne({_id: 123}); console.log(freshPuppy instanceof Puppies); // 输出true,是模型的实例,有bark方法 // 存Session再取出来后 req.session.puppy = freshPuppy; const storedPuppy = req.session.puppy; console.log(storedPuppy instanceof Puppies); // 输出false,就是个普通对象,没bark方法
1. 只存文档ID,下次请求重新查询
这是最稳妥、最推荐的方案。Session里只存_id这种轻量数据,既节省存储空间,又能保证下次拿到的是完整的Mongoose文档实例,所有方法都能用。
// 第一次请求:存ID就行 Puppies.findOne({_id: 123}).then(puppy => { req.session.puppyId = puppy._id; }); // 第二次请求:用ID重新查文档 app.get('/woof', async (req, res) => { const puppy = await Puppies.findById(req.session.puppyId); if (puppy) { puppy.bark(); // 完美运行!WOOF WOOF } });
2. 转成普通JS对象存入Session(适合不需要再调用模型方法的场景)
如果你只是想存文档的数据,后续不需要调用bark()这类模型方法,可以用Mongoose文档的toObject()或toJSON()方法把它转成普通JS对象再存。
Puppies.findOne({_id: 123}).then(puppy => { // 转成普通对象后存Session req.session.puppy = puppy.toObject(); });
⚠️ 注意:这种方式存的对象没有任何Mongoose方法,后续如果要调用bark()还是得重新查文档。
3. 自定义序列化逻辑(进阶玩法)
如果你的Session存储支持自定义序列化(比如Express Session的钩子),可以手动处理文档的序列化,把必要信息存起来,反序列化时再恢复成实例。不过这个方法比较复杂,一般没必要用,除非有特殊需求。
比如:
// 存的时候记录模型名和ID req.session.puppyData = { modelName: 'Puppies', id: puppy._id }; // 取的时候根据模型名和ID重新实例化 const { modelName, id } = req.session.puppyData; const PuppyModel = require(`./models/${modelName}`); const puppy = await PuppyModel.findById(id); puppy.bark(); // 正常运行
这种方法丢失的现象完全正常,本质是JSON序列化的限制导致Mongoose文档的原型方法被丢弃。最省心的做法就是只存ID,下次需要时重新查询文档,既能保证方法可用,又能避免Session存冗余数据的问题。
内容的提问来源于stack exchange,提问作者Seltsam

