Как мне структурировать наследование для персистентности PouchDB? - PullRequest
0 голосов
/ 26 апреля 2019

Я пишу коллекцию связанных объектов в 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 (), но он может сильно изменить копируемые свойства.

Вполне вероятно, что есть простое решение, которого я не вижу, вы знаете, что это такое? или вы можете помочь мне найти его?

1 Ответ

0 голосов
/ 26 апреля 2019

Итак, оглянувшись вокруг и поэкспериментировав еще раз, я обнаружил, что замороженный прототип, похоже, работает, предполагая, что он не вызывает ошибок оптимизации, упомянутых выше. Структура, которую я имею сейчас, похожа на

const methods={},properties={};

properties.Shape=Object.freeze({
    x:{value: 0, writable: true, enumerable: true, configurable: true},
    y:{value: 0, writable: true, enumerable: true, configurable: true}, 
    color:{value: 'red', writable: false, enumerable: true, configurable: false},   
});

methods.Shape=Object.freeze({
        move:{value:function(x, y) {
        this.x += x;
        this.y += y;
        console.info('Shape moved.');
        return this;},writable: true, enumerable: true, configurable: true},
});

const Shape=function() {return Object.defineProperties(this,properties.Shape);}
Shape.prototype=Object.freeze(Object.defineProperties({},methods.Shape));



properties.Rectangle=Object.freeze({
    w:{value: 0, writable: true, enumerable: true, configurable: true},
    h:{value: 0, writable: true, enumerable: true, configurable: true},
  });

  methods.Rectangle=Object.freeze({
    zoom:{value:function(z){
    this.w=this.w*z;
    this.h=this.h*z;
    console.info('Rectangle zoomed');
    return this;},writable: true, enumerable: true, configurable: true},
});

const Rectangle=function() {    Shape.call(this); return Object.defineProperties(this,properties.Rectangle);};
Rectangle.prototype = Object.freeze(Object.create(Shape.prototype,methods.Rectangle));

var rect = new Rectangle();

  console.log('Is rect an instance of Rectangle?',
    rect instanceof Rectangle); // true
  console.log('Is rect an instance of Shape?',
    rect instanceof Shape); // true
  rect.move(1, 1); // Outputs, 'Shape moved.'
  rect.zoom(2); // {x:1,y:1,w:0,h:0}
  rect2=new Rectangle();
  console.log('Do rect2 and rect share a prototype?',
  rect2.prototype===rect.prototype); // yes
  shape=new Shape();
  shape.move(2,2);  // moves
  shape.zoom(2);    // Fails as expected

Очистка с помощью вспомогательной функции должна сделать его красивее и проще для обновления.

...