Я пишу коллекцию связанных объектов в JS, которую хочу сохранить в PouchDB, но поведение наследования прототипа по умолчанию не подходит для слоев персистентности. Как я могу иметь структуру, которая может наследовать некоторые свойства от родителя как перечисляемые OwnProperties, но другие свойства (функции) как прототип? Мне нужно будет сделать некоторые свойства записываемыми / настраиваемыми, а некоторые замороженными, и у меня будет 10e5-10e6 объектов, поэтому важно учитывать объем памяти и производительность.
PouchDB будет принимать любой объект JS и очищать его перечисляемые свойства, пропуская свойства прототипа. Это имеет смысл, и PouchDB потрясающий.
Мои объекты - это семейство, которое разделяет некоторые свойства и методы с каждым поколением, добавляя еще несколько каждого и сохраняя все родительские объекты.
Для простоты я буду использовать фигуру Shapes.
Shape.properties={
x:0, // Vanilla x displacement
y:0, // Vanilla y displacement
color:red, // We want this to be immutable
};
Shape.methods={
move:function(x,y){this.x+=x; this.y+=y;}, // Change displacement
reset:function(){this.x=0; this.y=0;}, // Back to origin
save:function(){db.put(this);}, // Persist to DB
}
Rectangle.properties={
w:2, // Width
h:1, // Height
};
Rectangle.methods={
aspect:function(w,h){this.w=(w/h)*this.h;}, // Stretch
};
Cube.properties={
z:0, // Elevation
};
Cube.methods={
lift:function(z){this.z+=z;}; // Float up
};
Если я использую обычное наследование прототипов JS и создаю куб, у него будут только OwnProperties, равные z, что делает постоянство бесполезным.
Поэтому я сделал вспомогательный модуль, чтобы исправить это, у него есть функции, которые я могу использовать для быстрого создания PropertyDescriptors с использованием битовой маски и функции объединения для объединения всех частей:
// writable=4 | enumerable=2 | configurable=1
/* Process a bitMask that describes the desired property descriptor. We then use the combine method to add
* specifically described properties to an object. PouchDB will grab any enumerable properties, some should be write-protected
* but vanilla properties are Accessors with bitmask 6(enumerable and writable). Based on code from elsewhere. */
accessor:function(bMask,val){ // Accessor descriptor: bitmask , value
return {configurable:Boolean(bMask & 1),enumerable:Boolean(bMask & 2),writable:Boolean(bMask & 4),value:val};
},
combine:function(root,properties,methods){ // This is a naive implementation, ask SO for help?
for (let i in root){ // Iterate over properties.
properties[i]=Object.getOwnPropertyDescriptor(root,i); // Combine the root properties and the given properties objects,
}
methods=Object.defineProperties(Object.getPrototypeOf(root),methods);// Add the methods to the root prototype.
return Object.create(methods,properties); // Combine the prototype and the properties
},
Мои структуры теперь выглядят как
Shape.properties={
x:accessor(6,0),
y:accessor(6,0),
color:accesor(2,red), // Bitmask 2: enumerable only
};
Shape.methods={
move:accessor(0,function(x,y){this.x+=x; this.y+=y;}), // Bitmask 0: 'Frozen' property
reset:accessor(0,function(){this.x=0; this.y=0;}),
save:accessor(0,function(){db.put(this);}),
}
var Shape=function(){
return combine(new Object(),Shape.properties, Shape.methods);
};
Rectangle.properties={
w:accessor(6,0),
h:accessor(6,0),
};
Rectangle.methods={
aspect:accessor(0,function(w,h){this.w=(w/h)*this.h;}),
};
var Rectangle=function(){
return combine(new Shape(),Rectangle.properties, Rectangle.methods);
};
//...
Так что это работает, НО два объекта одного типа больше не имеют прототипа, то есть каждый объект имеет уникальный экземпляр прототипа.
Это дерьмо на память и плохая практика в целом. С таким же успехом я могу написать методы для каждого объекта и позволить мешочку выплевывать их обратно мне как ошибки.
Я посмотрел другие варианты и наткнулся на эту статью , в которой есть несколько замечательных идей, но потом я прочитал это , что наводит меня на мысль, что, возможно, мне следует создавать статические / замороженные объекты-прототипы и строить оттуда или использовать совершенно другой подход.
Я также рассмотрел использование Object.assign (), но он может сильно изменить копируемые свойства.
Вполне вероятно, что есть простое решение, которого я не вижу, вы знаете, что это такое? или вы можете помочь мне найти его?