Mongoose 提供了实例方法和静态方法两种方式来扩展模型的功能。理解这两种方法的区别和使用场景对于编写可维护的代码非常重要。
实例方法(Instance Methods)
实例方法是添加到文档实例上的方法,可以在单个文档上调用。这些方法可以访问 this 关键字来引用当前文档。
定义实例方法
javascriptconst 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');
实例方法的应用场景
- 文档特定操作:对单个文档执行操作
- 数据验证:验证文档数据
- 数据转换:转换文档数据格式
- 业务逻辑:封装业务逻辑
- 状态检查:检查文档状态
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();
静态方法的应用场景
- 查询操作:封装常用查询
- 批量操作:执行批量更新或删除
- 统计操作:计算统计数据
- 业务规则:实现业务规则查询
- 复杂查询:封装复杂查询逻辑
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.methods | schema.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(); }
最佳实践
- 命名清晰:使用描述性的方法名
- 单一职责:每个方法只做一件事
- 错误处理:妥善处理错误情况
- 文档注释:为方法添加清晰的注释
- 类型安全:使用 TypeScript 或 JSDoc
- 测试覆盖:为自定义方法编写测试
- 避免重复:不要重复已有的 Mongoose 方法
- 性能考虑:注意方法对性能的影响