Mongoose中document和object的區別

這個問題實際上是mongoose很是常見的問題,常常有不少之前沒遇到這個問題的人都會被這個問題弄得懷疑人生。咱們先介紹一些問題的背景。先看下面一段代碼:javascript

router.get('/', function(req, res, next) {
  // res.render('index', { title: 'Express' });
  const model = mongoose.model('realestate');
  const queryCretia = {};
  model.find(queryCretia, (err, docs) => {
    res.render('index', {
      title: 'express',
      docs: docs
    })
  })
});
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <!-- <%= docs %> -->
    <ul>
      <% docs.forEach(function(doc){ %>
      <li><%= doc.type %></li>
      <% }) %>
    </ul>
  </body>
</html>

在第一段代碼中,經過model.find咱們應該可以獲取到根據queryCriteria獲取的結果,結果應該是一個對象數組,相似於這樣:css

[{
    "_id" : ObjectId("59bdeadb2a5c612514ee7970"),
    "title" : "好樓層,中等裝修,滿5年,上門實拍",
    "type" : "2室1廳",
    "square" : "75.42平",
    "direction" : "朝南",
    "floor" : "中區/6層",
    "unitPrice" : 47732,
    "totalPrice" : 360,
    "location" : null,
    "specialExplain" : "滿五",
    "url" : "http://sh.lianjia.com//ershoufang/sh4528035.html",
    "station" : "江楊北路",
    "line" : "3號線",
    "updateTime" : "2017-09-17 11:24:11"
}
{
    "_id" : ObjectId("59bdeadb2a5c612514ee7971"),
    "title" : "南北戶型,廚衛全明,高區採光好,裝修精美",
    "type" : "2室2廳",
    "square" : "90.92平",
    "direction" : "朝南北",
    "floor" : "高區/6層",
    "unitPrice" : 46194,
    "totalPrice" : 420,
    "location" : null,
    "specialExplain" : "滿五",
    "url" : "http://sh.lianjia.com//ershoufang/sh4546221.html",
    "station" : "江楊北路",
    "line" : "3號線",
    "updateTime" : "2017-09-17 11:24:11"
}]

預期index.ejs應該渲染的頁面是一個ul渲染的結果,相似於html

  • 2室1廳
  • 2室2廳

固然,理想很豐滿,現實很骨感。我就是死活渲染不出來doc.type。照理說應該是不可能的,在index.ejsdoc就是一個對象,我爲何不能獲取doctype屬性呢?這不合理,太不合理了!java

老實說,這個問題我之間也是遇到過,當初我是想修改這個doc的屬性,可是死活沒有辦法修改,當初也是花了好久找到緣由。此次我就把這個問題好好地研究一下。express

先說結果,以及解決方法把。我比較喜歡劇透。願意是由於再次返回的doc是屬於Document的實例,而不是一個普通的對象。也就是說它和普通的對象是不同的,它沒有普通對象的一些方法,普通對象也沒有它身上的一些方法。解決方案有幾種,不過究其根本都是將這種document轉化爲普通的對象:json

方法1:

利用toObject方法api

docs.forEach(doc => {
    return doc.toObject();
})

方法2:

利用JSON方法,這是我想到的一個方法,具體深層緣由在這就不展開了:數組

docs = JSON.stringify(docs);
docs = JSON.parse(docs);

方法3:

利用lean方法:app

model.find().lean().exec((err, docs) => {
....
})

上述的三種方法應該都能成功將find獲取的結果轉化爲普通的對象。dom

可是我還想知道到底document和一個普通的對象到底有什麼區別,區別在哪裏呢?

咱們假設find獲取的結果是docs,轉化爲普通對象的結果是docs1。如今咱們就看一看這兩者的區別。理論上docsdocs1都應該是數組,而它們中元素都應該是一個對象,咱們先來看看是否是這樣呢?

console.log(Object.prototype.toString.call(docs));
consoele.log(Object.prototype.toString.call(docs[0]));

console.log(Object.prototype.toString.call(docs1));
console.log(Object.prototype.toString.call(docs1[0]))

咱們經過上述方法能夠獲取docs以及docs1的類型以及其中元素的類型,結果是:

[object Array]
[object Object]

[object Array]
[object Object]

和咱們預想中的如出一轍,那問題不在這,那咱們就探究探究docs[0]以及docs1[0]的原型把,看看它的原型究竟是什麼呢?知道JS的人,應該都知道JS中的原型鏈。在此,咱們就經過__proto__來粗暴地獲取對象的原型:

console.dir(doc[0].__proto__);

console.dir(docs[0].__proto__);

結果是:

model {
  db:
   NativeConnection {
     base:
      Mongoose {
        connections: [Array],
        models: [Object],
        modelSchemas: [Object],
        options: [Object],
        plugins: [Array] },
     collections: { realestates: [Object] },
     models: { realestate: [Object] },
     config: { autoIndex: true },
     replica: false,
     hosts: null,
     host: '127.0.0.1',
     port: 27017,
     user: undefined,
     pass: undefined,
     name: 'real_estate_info',
     options:
      { db: [Object],
        auth: {},
        server: [Object],
        replset: [Object],
        mongos: undefined },
     otherDbs: [],
     _readyState: 1,
     _closeCalled: false,
     _hasOpened: true,
     _listening: false,
     db:
      Db {
        domain: null,
        _events: [Object],
        _eventsCount: 6,
        _maxListeners: undefined,
        s: [Object],
        serverConfig: [Getter],
        bufferMaxEntries: [Getter],
        databaseName: [Getter],
        _listening: true },
     _events:
      { connected: [Function],
        error: [Function: bound bound consoleCall],
        disconnected: [Function: bound bound consoleCall],
        reconnected: [Function: bound bound consoleCall] },
     _eventsCount: 4 },
  discriminators: undefined,
  id: [Getter/Setter],
  __v: [Getter/Setter],
  _id: [Getter/Setter],
  schema:
   Schema {
     obj: undefined,
     paths: { _id: [Object], __v: [Object] },
     aliases: {},
     subpaths: {},
     virtuals: { id: [Object] },
     singleNestedPaths: {},
     nested: {},
     inherits: {},
     callQueue: [ [Array], [Array], [Array], [Array], [Array], [Array] ],
     _indexes: [],
     methods: {},
     statics: {},
     tree: { _id: [Object], __v: [Function: Number], id: [Object] },
     query: {},
     childSchemas: [],
     plugins: [ [Object], [Object], [Object], [Object] ],
     s: { hooks: [Object], kareemHooks: [Object] },
     options:
      { retainKeyOrder: false,
        typeKey: 'type',
        id: true,
        noVirtualId: false,
        _id: true,
        noId: false,
        validateBeforeSave: true,
        read: null,
        shardKey: null,
        autoIndex: null,
        minimize: true,
        discriminatorKey: '__t',
        versionKey: '__v',
        capped: false,
        bufferCommands: true,
        strict: true,
        pluralization: true },
     '$globalPluginsApplied': true,
     _requiredpaths: [] },
  collection:
   NativeCollection {
     collection: Collection { s: [Object] },
     opts: { bufferCommands: true, capped: false },
     name: 'realestates',
     collectionName: 'realestates',
     conn:
      NativeConnection {
        base: [Object],
        collections: [Object],
        models: [Object],
        config: [Object],
        replica: false,
        hosts: null,
        host: '127.0.0.1',
        port: 27017,
        user: undefined,
        pass: undefined,
        name: 'real_estate_info',
        options: [Object],
        otherDbs: [],
        _readyState: 1,
        _closeCalled: false,
        _hasOpened: true,
        _listening: false,
        db: [Object],
        _events: [Object],
        _eventsCount: 4 },
     queue: [],
     buffer: false,
     emitter:
      EventEmitter {
        domain: null,
        _events: {},
        _eventsCount: 0,
        _maxListeners: undefined } },
  '$__original_save': { [Function] numAsyncPres: 0 },
  save: { [Function: wrappedPointCut] '$originalFunction': '$__original_save', '$isWrapped': true },
  _pres:
   { '$__original_save': [ [Object], [Object], [Object] ],
     '$__original_remove': [ [Object] ] },
  _posts: { '$__original_save': [], '$__original_remove': [] },
  '$__original_remove': { [Function] numAsyncPres: 1 },
  remove:
   { [Function: wrappedPointCut]
     '$originalFunction': '$__original_remove',
     '$isWrapped': true },
  '$__original_validate': [Function],
  validate:
   { [Function: wrappedPointCut]
     '$originalFunction': '$__original_validate',
     '$isWrapped': true } }

以及

{}

很顯然,問題就是在這裏,docs[0]docs[0]的原型並非一個東西。而js中對象經過.或者是[]訪問屬性都是調用了Object中的某個方法,但具體什麼方法我不太記得。然而docs中的原型或者其原型的原型也是沒有這個方法的,所以他就沒辦法去訪問這個屬性。

其實docs[0].__proto__.__proto__是Model,docs[0].__proto__.__proto__.__proto__是Document,docs[0].__proto__.__proto__.__proto__.__proto__纔是{}

至此,這個問題引發的一系列的探究也是告一段落了。其實Mongoose還有另一些奇怪的地方,被人所詬病,在此也不一一細數了。從問題的發現,到寫這篇文章大概花了大半天的時間,之前遇到問題就找到解決辦法就中止了,可是這一次經過這樣深刻地去發現,可能就會發掘到更多的東西。

相關文章
相關標籤/搜索