Mongoose查询结果修改异常:单条修改致所有文档字段变更
问题
我实现了如下异步函数:
const getXNumberOfDocuments = async function getXNumberOfDocuments( page, results_per_page ) { /*************************************************** */ results_per_page = parseInt(results_per_page); let x_number_of_documents = await Document.find() .populate([ { path: "user", populate: { path: "profile", select: ["profileImageURL"], }, }, ]) .limit(results_per_page) .skip(results_per_page * page) .lean(); /*************************************************** */ // I will stop this loop at i=0 by throwing an error // As you se below for (let i = 0; i < x_number_of_documents.length; i++) { console.log("🚀 ~ file: documentsServices.js ~ line 295 ~ i", i); // #BUG: // For some reason this changes // profileImageURL for i=0 and i=1 as well x_number_of_documents[i].user.profile.profileImageURL = "THIS SHOULD ONLY BE MODIFIED FOR i=0 BUT IT IS MODIFIED FOR ALL OF THEM!"; // await s3_config.getImageReadSignedUrl( // x_number_of_documents[i].user.profile.profileImageURL // ); /*************************************************** */ // I check all documents here // And I find that profileImageURL was modified in all of them // Even though I stopped the loop at i=0 for (let j = 0; j < x_number_of_documents.length; j++) { console.log( "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀 j,", j ); console.log( "🚀 ~ file: documentsServices.js ~ line 299 ~ x_number_of_documents[j].user.profile.profileImageURL", x_number_of_documents[j].user.profile.profileImageURL ); console.log( "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀 " ); } /*************************************************** */ throw new Error("STOPPING LOOP AT i=0"); /*************************************************** */ } return x_number_of_documents; };
该函数通过Mongoose的find方法结合populate、limit、skip及lean()获取分页文档数据,正常返回包含多个带user.profile.profileImageURL字段的对象。当我尝试仅修改结果数组中索引为0的元素的profileImageURL字段时,发现所有元素的该字段都被修改;但直接修改user字段时仅索引0的元素变更,修改user.profile字段时又出现所有元素变更的情况。相关测试输出及简化场景如下:
x_number_of_documents[0].user.profile.profileImageURL = "THIS SHOULD ONLY BE MODIFIED FOR i=0 BUT IT IS MODIFIED FOR ALL OF THEM!"; console.log( "🚀 ~ file: documentsServices.js ~ line 308 ~ x_number_of_documents[0].user.profile.profileImageURL", x_number_of_documents[0].user.profile.profileImageURL ); console.log( "🚀 ~ file: documentsServices.js ~ line 308 ~ x_number_of_documents[1].user.profile.profileImageURL", x_number_of_documents[1].user.profile.profileImageURL );
测试结果显示所有元素的profileImageURL均被修改,我怀疑这与嵌入式文档的修改逻辑有关,请问这是什么原因导致的?
原因分析
这是因为多个文档指向了同一个profile对象的引用。
具体细节:
- 当使用Mongoose的
populate关联数据时,如果多条Document记录关联的是同一个User,且该User绑定的是同一个Profile,Mongoose默认会复用同一个内存对象引用,不会为每条Document创建独立的profile副本。 - 即使调用了
lean()返回普通JavaScript对象,对象引用的特性依然存在——所有关联该profile的Document元素,它们的user.profile本质是内存中的同一个对象。 - 所以修改
x_number_of_documents[0].user.profile.profileImageURL时,实际是修改了这个共享对象的属性,所有引用它的元素自然都会同步变化。 - 直接修改
user字段时,是给索引0的元素重新赋值了新对象,不会影响其他元素的user引用;但修改user.profile时,同样是操作共享的profile对象,因此所有元素都会受影响。
解决思路:如果需要独立修改每个元素的profile属性,在获取数据后为每个user.profile做深拷贝(比如用JSON.parse(JSON.stringify(...))或深拷贝工具函数),切断对象引用关系即可。
内容的提问来源于stack exchange,提问作者AG_HIHI
相关产品推荐
相关产品推荐

