index.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. /*!
  2. * Module dependencies.
  3. */
  4. 'use strict';
  5. const Document = require('../../document');
  6. const mongooseArrayMethods = require('./methods');
  7. const arrayAtomicsSymbol = require('../../helpers/symbols').arrayAtomicsSymbol;
  8. const arrayAtomicsBackupSymbol = require('../../helpers/symbols').arrayAtomicsBackupSymbol;
  9. const arrayParentSymbol = require('../../helpers/symbols').arrayParentSymbol;
  10. const arrayPathSymbol = require('../../helpers/symbols').arrayPathSymbol;
  11. const arraySchemaSymbol = require('../../helpers/symbols').arraySchemaSymbol;
  12. /**
  13. * Mongoose Array constructor.
  14. *
  15. * #### Note:
  16. *
  17. * _Values always have to be passed to the constructor to initialize, otherwise `MongooseArray#push` will mark the array as modified._
  18. *
  19. * @param {Array} values
  20. * @param {String} path
  21. * @param {Document} doc parent document
  22. * @api private
  23. * @inherits Array
  24. * @see https://bit.ly/f6CnZU
  25. */
  26. const _basePush = Array.prototype.push;
  27. const numberRE = /^\d+$/;
  28. function MongooseArray(values, path, doc, schematype) {
  29. let __array;
  30. if (Array.isArray(values)) {
  31. const len = values.length;
  32. // Perf optimizations for small arrays: much faster to use `...` than `for` + `push`,
  33. // but large arrays may cause stack overflows. And for arrays of length 0/1, just
  34. // modifying the array is faster. Seems small, but adds up when you have a document
  35. // with thousands of nested arrays.
  36. if (len === 0) {
  37. __array = new Array();
  38. } else if (len === 1) {
  39. __array = new Array(1);
  40. __array[0] = values[0];
  41. } else if (len < 10000) {
  42. __array = new Array();
  43. _basePush.apply(__array, values);
  44. } else {
  45. __array = new Array();
  46. for (let i = 0; i < len; ++i) {
  47. _basePush.call(__array, values[i]);
  48. }
  49. }
  50. } else {
  51. __array = [];
  52. }
  53. const internals = {
  54. [arrayAtomicsSymbol]: {},
  55. [arrayAtomicsBackupSymbol]: void 0,
  56. [arrayPathSymbol]: path,
  57. [arraySchemaSymbol]: schematype,
  58. [arrayParentSymbol]: void 0,
  59. isMongooseArray: true,
  60. isMongooseArrayProxy: true,
  61. __array: __array
  62. };
  63. if (values && values[arrayAtomicsSymbol] != null) {
  64. internals[arrayAtomicsSymbol] = values[arrayAtomicsSymbol];
  65. }
  66. // Because doc comes from the context of another function, doc === global
  67. // can happen if there was a null somewhere up the chain (see #3020)
  68. // RB Jun 17, 2015 updated to check for presence of expected paths instead
  69. // to make more proof against unusual node environments
  70. if (doc != null && doc instanceof Document) {
  71. internals[arrayParentSymbol] = doc;
  72. internals[arraySchemaSymbol] = schematype || doc.schema.path(path);
  73. }
  74. const proxy = new Proxy(__array, {
  75. get: function(target, prop) {
  76. if (internals.hasOwnProperty(prop)) {
  77. return internals[prop];
  78. }
  79. if (mongooseArrayMethods.hasOwnProperty(prop)) {
  80. return mongooseArrayMethods[prop];
  81. }
  82. return __array[prop];
  83. },
  84. set: function(target, prop, value) {
  85. if (typeof prop === 'string' && numberRE.test(prop)) {
  86. mongooseArrayMethods.set.call(proxy, prop, value, false);
  87. } else if (internals.hasOwnProperty(prop)) {
  88. internals[prop] = value;
  89. } else {
  90. __array[prop] = value;
  91. }
  92. return true;
  93. }
  94. });
  95. return proxy;
  96. }
  97. /*!
  98. * Module exports.
  99. */
  100. module.exports = exports = MongooseArray;