Mongoose 是 Node.js 中最流行的 MongoDB 驱动器之一。它是一个优秀的对象文档模型(ODM),为我们提供了快速而方便的数据库访问。然而,在 Mongoose 使用的过程中,有些坑是我们不可避免的,接下来我们就来详细了解一下 Mongoose 使用过程中需要注意的坑。
坑一:Schema 字段的默认值
当我们在定义 Mongoose Schema 的时候,如果字段有默认值,那么每次创建这个模型的新实例时,就会将默认值添加到新实例中。示例代码如下:
// javascriptcn.com 代码示例 const UserSchema = new mongoose.Schema({ name: { type: String, default: 'Unknown' }, age: { type: Number, default: 18 }, }); const User = mongoose.model('User', UserSchema); const user = new User(); console.log(user.name); // "Unknown" console.log(user.age); // 18
然而,我们需要注意的是,如果字段的默认值是一个对象或数组,那么这个对象或数组在每个新实例中都将是同一个对象或数组。比如下面这个示例:
// javascriptcn.com 代码示例 const PostSchema = new mongoose.Schema({ title: String, content: String, tags: { type: [String], default: [] }, }); const Post = mongoose.model('Post', PostSchema); const post1 = new Post(); const post2 = new Post(); console.log(post1.tags === post2.tags); // true
在上面的示例中,tags
字段的默认值是一个空数组,然而,当我们创建两个 Post
实例时,它们的 tags
字段实际上引用了同一个数组,改变其中一个 tags
数组的值,两个实例的 tags
值都会发生改变,这是一个十分危险的坑,因此,在设置默认值时需要多加注意。如果默认值是一个对象或数组的话,建议使用函数的方式设定默认值。
// javascriptcn.com 代码示例 const PostSchema = new mongoose.Schema({ title: String, content: String, tags: { type: [String], default: function() { return []; }, }, });
使用函数的方式设定默认值,每次创建实例的时候都会调用函数生成新的数组,避免了多个实例共享同一个对象的问题。
坑二:Document 的默认方法
在 Mongoose 中,每个文档实例(也就是 Mongoose 中的 Document)都有一些默认方法,例如save
、remove
、validate
等等。当我们在定义 Schema 的时候,如果自定义了某个字段的一个方法同名,可能会引发一些不可预知的错误。比如下面这个示例:
// javascriptcn.com 代码示例 const UserSchema = new mongoose.Schema({ name: String, age: Number, }); UserSchema.methods.validate = function() { console.log('Custom validate method.'); }; const User = mongoose.model('User', UserSchema); const user = new User({ name: 'John', age: 18, }); user.validate(); // "Custom validate method." user.save(); // TypeError: user.save is not a function
在上面的示例中,我们为 UserSchema
中的 validate
字段自定义了一个 validate
方法。然而,在创建 User
实例时,这个实例继承了 Document
中的 validate
方法,在调用自定义的 validate
方法后,user
实例将拥有自定义方法的属性而丢失了原有的 save
等方法。因此,在自定义方法名前,需要先了解Document
中是否包含同名方法。
坑三:Document 数组的 push 方法不触发中间件
在 Mongoose 中,Document 数组的 push 方法并不会触发 pre
和 post
中间件,这可能会导致我们的一些业务逻辑在使用 push
方法后没有传递到中间件中。比如下面这个示例:
// javascriptcn.com 代码示例 const UserSchema = new mongoose.Schema({ name: String, followers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], }); UserSchema.pre('save', function(next) { console.log('before save'); next(); }); UserSchema.post('save', function(document) { console.log('after save'); }); const User = mongoose.model('User', UserSchema); const follower = new User({ name: 'follower' }); const leader = new User({ name: 'leader', followers: [follower] }); console.log(leader.followers); // [follower] leader.followers.push(follower); leader.save();
在上面的示例中,我们为UserSchema
注册了pre
和post
两个中间件。因为在 Mongoose 中,数组的 push 方法并不会触发中间件,所以在上面的代码中,如果我们调用leader.followers.push(follower)
方法,pre
和post
两个中间件都不会被调用,实际上只有保存到数据库的操作会触发中间件。
如果需要在 push 数组时也调用中间件,我们可以使用 $push
替代数组的 push。
leader.followers = leader.followers.concat([follower]); leader.markModified('followers'); leader.save();
在上面的示例中,我们先使用concat
方法将 follower 添加到 leader followers 数组中,再使用markModified
方法告诉 Mongoose 来检测 followers 数组的更改,接着再保存 leader。这样做的话,pre
和post
中间件会被正常调用。
坑四:Document 数组深度限制
当我们在 Mongoose 操作 Document 数组时,有些操作只能递归地进行一定的深度限制,否则将会造成栈溢出错误。默认情况下,Mongoose 对 Document 数组递归的深度限制为 20 层深度。如果我们需要修改这个限制,可以按照下面这个方式来修改:
mongoose.set('maxDepth', 100);
在上面的示例中,我们将递归的深度限制增加到了 100 层深度。
如果我们在对 Document 数组进行操作时,遇到了栈溢出的错误提示,就要重点注意这个问题。
总结
这篇文章只是介绍了 Mongoose 使用中的一些坑,当然,还有很多没有被提到的知识点。在 Mongoose 的使用过程中,我们还需要注意到一些其他的问题,例如 Mongoose 的连接引用计数、Mongoose 的多模型操作等等。只有通过不断地实践和深入学习,才能在使用 Mongoose 的过程中避免遇到坑,提高我们的开发效率。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65349c707d4982a6eb97c1a0