在 MongoDB 的 Node.js 驱动 Mongoose 中,虚拟属性是一种模式属性,在对文档进行查询或者保存时会自动进行计算或者转换,但它不会在 MongoDB 中存储。这种特性在很多场景下非常有用,它可以给抽象属性和逻辑增加一个新的维度。
基本概念
在 Mongoose 中,虚拟属性的定义非常简单,我们可以通过在 Schema 中调用 virtual()
方法来声明一个虚拟属性。下面是一个示例:
// javascriptcn.com 代码示例 var personSchema = new Schema({ firstName: String, lastName: String }); personSchema.virtual('fullName').get(function() { return this.firstName + ' ' + this.lastName; }); var Person = mongoose.model('Person', personSchema); var john = new Person({ firstName: 'John', lastName: 'Doe' }); console.log(john.fullName); // John Doe
可以看出,虚拟属性并不存储在 MongoDB 中,而是在运行时(取值时)计算得到。虚拟属性有两种类型:getter
和 setter
,它们分别对应着一个属性值的读取和设置。
虚拟属性类型
Getter
Getter 是一个没有 setter 的虚拟属性类型。当我们在代码中访问该属性时,Mongoose 会执行 get
函数来获取该值。我们可以使用 get
函数返回计算过的值,也可以利用它进行一些复杂的运算、逻辑判断等处理。
下面的例子展示如何计算一个用户文档的年龄:
// javascriptcn.com 代码示例 var userSchema = new Schema({ birthDate: Date }); userSchema.virtual('age').get(function() { var ageDifMs = Date.now() - this.birthDate.getTime(); var ageDate = new Date(ageDifMs); return Math.abs(ageDate.getUTCFullYear() - 1970); }); var User = mongoose.model('User', userSchema); var bob = new User({ birthDate: new Date(1987, 6, 2) }); console.log(bob.age); // 34
Getter 虚拟属性有一个非常重要的特性,它们可以通过虚拟属性计算,这对于需要频繁执行复杂计算的情况非常有用。而且 Getter 属性的计算可以依赖其他属性,方便我们进行维护和修改。
Setter
Setter 是一个没有 getter 的虚拟属性类型。当我们在代码中设置该属性时,Mongoose 会执行 set
函数,并返回一个新的属性值。Setter 有一个非常重要的作用,它可以用来将外部数据格式化为符合 Schema 规范的格式。
下面的例子展示了一个字符串去除空格的 setter:
// javascriptcn.com 代码示例 var notificationSchema = new Schema({ channel: { type: String, enum: ['email', 'sms', 'push'] }, message: String }); notificationSchema.virtual('message').set(function(msg) { if (typeof msg !== 'string') return ''; return msg.trim(); }); var Notification = mongoose.model('Notification', notificationSchema); var sms = new Notification({ channel: 'sms', message: ' hello, world! ' }); console.log(sms.message); // 'hello, world!'
这个 setter 可以用来格式化一个消息,也可以用来进行格式校验或转换。Setter 属性还有一个重要的应用场景,就是用来处理外部 API 或者用户输入的数据,保证数据都符合指定格式。
虚拟属性选项
ref
在 Mongoose 中,虚拟属性可以通过 ref 选项关联到其他文档,从而实现文档间关联。ref 选项用来指定被关联文档的 model,支持 string 形式和 objectid 形式。
下面的例子展示了如何在 Mongoose 中关联两个文档:
// javascriptcn.com 代码示例 var orderSchema = new Schema({ customer: { type: mongoose.ObjectId, ref: 'Customer' }, product: { type: mongoose.ObjectId, ref: 'Product' }, price: Number }); var customerSchema = new Schema({ name: String, email: String, phone: String }); customerSchema.virtual('orderList', { ref: 'Order', localField: '_id', foreignField: 'customer' }); var Order = mongoose.model('Order', orderSchema); var Product = mongoose.model('Product', productSchema); var Customer = mongoose.model('Customer', customerSchema); var john = new Customer({ name: 'John', email: 'john@example.com', phone: '123456' }).save(); var apple = new Product({ name: 'Apple', price: 10 }).save(); var appleOrder = new Order({ customer: john._id, product: apple._id, price: apple.price }).save(); Customer.findOne({ name: 'John' }).populate('orderList').exec(function(err, customer) { console.log(customer.orderList[0].product); // Apple });
这个例子展示了如何通过虚拟属性 ref 将 Customer 和 Order 关联起来。我们使用 localField
和 foreignField
选项分别指定了关联的本地字段和外部字段。这样,我们就可以在查询 Customer 时直接获取它的订单列表。
localField 和 foreignField
在 ref 选项中,我们使用了两个额外的选项:localField
和 foreignField
。它们描述了两个文档间关联的字段。
localField
:用于指定本 model(即虚拟属性所属的 model)中用于与外部 model 进行关联的字段名称。默认值为 '_id'。foreignField
:用于指定与外部 model 关联的字段名称(即外部 model 中要关联的字段)。默认值为 '_id'。
在关联两个文档时,我们经常需要在查询时使用 populate()
方法,这个方法可以将虚拟属性关联的文档一并查询出来,从而避免了多次查询带来的性能损失。
总结
虚拟属性是 Mongoose 中非常有用的功能,它可以用来处理文档间的关联、逻辑计算和数据格式转换等问题。在使用虚拟属性时,需要特别注意一些选项(如 ref
、localField
和 foreignField
),以及虚拟属性类型(如 getter 和 setter)的区别。同时,还需要充分理解虚拟属性的特性和使用场景,以便更好的利用它们。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/653e69377d4982a6eb7e7b9c