乐闻世界logo
搜索文章和话题

Mongoose 实例方法和静态方法有什么区别?

2月22日 20:12

Mongoose 提供了实例方法和静态方法两种方式来扩展模型的功能。理解这两种方法的区别和使用场景对于编写可维护的代码非常重要。

实例方法(Instance Methods)

实例方法是添加到文档实例上的方法,可以在单个文档上调用。这些方法可以访问 this 关键字来引用当前文档。

定义实例方法

javascript
const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, createdAt: { type: Date, default: Date.now } }); // 添加实例方法 userSchema.methods.getFullName = function() { return `${this.firstName} ${this.lastName}`; }; userSchema.methods.isNewUser = function() { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); return this.createdAt > oneDayAgo; }; userSchema.methods.comparePassword = function(candidatePassword) { // 使用 bcrypt 比较密码 return bcrypt.compare(candidatePassword, this.password); }; const User = mongoose.model('User', userSchema); // 使用实例方法 const user = await User.findById(userId); console.log(user.getFullName()); // "John Doe" console.log(user.isNewUser()); // true/false const isMatch = await user.comparePassword('password123');

实例方法的应用场景

  1. 文档特定操作:对单个文档执行操作
  2. 数据验证:验证文档数据
  3. 数据转换:转换文档数据格式
  4. 业务逻辑:封装业务逻辑
  5. 状态检查:检查文档状态
javascript
// 示例:订单实例方法 const orderSchema = new Schema({ items: [{ product: { type: Schema.Types.ObjectId, ref: 'Product' }, quantity: Number, price: Number }], status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'] }, createdAt: { type: Date, default: Date.now } }); orderSchema.methods.getTotalPrice = function() { return this.items.reduce((sum, item) => { return sum + (item.price * item.quantity); }, 0); }; orderSchema.methods.canBeCancelled = function() { return ['pending', 'paid'].includes(this.status); }; orderSchema.methods.markAsShipped = function() { if (this.status !== 'paid') { throw new Error('Order must be paid before shipping'); } this.status = 'shipped'; return this.save(); };

静态方法(Static Methods)

静态方法是添加到模型类上的方法,可以直接在模型上调用,不需要实例化文档。这些方法通常用于查询或批量操作。

定义静态方法

javascript
// 添加静态方法 userSchema.statics.findByEmail = function(email) { return this.findOne({ email }); }; userSchema.statics.getActiveUsers = function() { return this.find({ status: 'active' }); }; userSchema.statics.countByStatus = function(status) { return this.countDocuments({ status }); }; userSchema.statics.findAdultUsers = function() { return this.find({ age: { $gte: 18 } }); }; // 使用静态方法 const user = await User.findByEmail('john@example.com'); const activeUsers = await User.getActiveUsers(); const activeCount = await User.countByStatus('active'); const adultUsers = await User.findAdultUsers();

静态方法的应用场景

  1. 查询操作:封装常用查询
  2. 批量操作:执行批量更新或删除
  3. 统计操作:计算统计数据
  4. 业务规则:实现业务规则查询
  5. 复杂查询:封装复杂查询逻辑
javascript
// 示例:产品静态方法 const productSchema = new Schema({ name: String, price: Number, category: String, stock: Number, active: { type: Boolean, default: true } }); productSchema.statics.findByCategory = function(category) { return this.find({ category, active: true }); }; productSchema.statics.findInPriceRange = function(min, max) { return this.find({ price: { $gte: min, $lte: max }, active: true }); }; productSchema.statics.findLowStock = function(threshold = 10) { return this.find({ stock: { $lte: threshold }, active: true }); }; productSchema.statics.updateStock = function(productId, quantity) { return this.findByIdAndUpdate( productId, { $inc: { stock: quantity } }, { new: true } ); };

实例方法 vs 静态方法

区别对比

特性实例方法静态方法
调用方式document.method()Model.method()
访问 this可以访问文档实例不能访问文档实例
使用场景单文档操作查询和批量操作
定义位置schema.methodsschema.statics
返回值通常返回文档或修改后的值通常返回查询结果

选择指南

使用实例方法当:

  • 需要操作单个文档
  • 需要访问文档的属性
  • 方法与特定文档相关
  • 需要修改文档状态

使用静态方法当:

  • 需要查询多个文档
  • 需要执行批量操作
  • 方法与文档集合相关
  • 不需要访问特定文档

高级用法

异步方法

javascript
// 异步实例方法 userSchema.methods.sendWelcomeEmail = async function() { const emailService = require('./services/email'); await emailService.send({ to: this.email, subject: 'Welcome!', body: `Hello ${this.firstName}!` }); return this; }; // 异步静态方法 userSchema.statics.sendNewsletter = async function(subject, content) { const users = await this.find({ subscribed: true }); const emailService = require('./services/email'); for (const user of users) { await emailService.send({ to: user.email, subject, body: content }); } return users.length; };

链式调用

javascript
// 静态方法返回查询构建器 userSchema.statics.queryActive = function() { return this.find({ active: true }); }; // 使用链式调用 const users = await User.queryActive() .select('name email') .sort({ name: 1 }) .limit(10);

组合使用

javascript
// 静态方法查询,实例方法处理 const users = await User.findByEmail('john@example.com'); if (user) { await user.sendWelcomeEmail(); }

最佳实践

  1. 命名清晰:使用描述性的方法名
  2. 单一职责:每个方法只做一件事
  3. 错误处理:妥善处理错误情况
  4. 文档注释:为方法添加清晰的注释
  5. 类型安全:使用 TypeScript 或 JSDoc
  6. 测试覆盖:为自定义方法编写测试
  7. 避免重复:不要重复已有的 Mongoose 方法
  8. 性能考虑:注意方法对性能的影响
标签:Mongoose