Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement JSTP Record Data and JSTP Record Metadata #19

Open
aqrln opened this issue Nov 8, 2016 · 3 comments
Open

Implement JSTP Record Data and JSTP Record Metadata #19

aqrln opened this issue Nov 8, 2016 · 3 comments

Comments

@aqrln
Copy link
Member

aqrln commented Nov 8, 2016

No description provided.

@aqrln aqrln self-assigned this Nov 8, 2016
@aqrln
Copy link
Member Author

aqrln commented Nov 8, 2016

Conceptual code by @tshemsedinov copied from Impress:

// Assign metadata to array elements
//   data - array of objects serialized into arrays with JSTP single object
//   metadata - data describes PrototypeClass structure
// Returns: built PrototypeClass
//
api.jstp.assignMetadata = function(data, metadata) {
  var proto = api.jstp.buildPrototype(metadata);
  api.jstp.assignPrototype(data, proto);
  return proto;
};

// Assign prototype to records array or single record
//   data - array of objects serialized into arrays with JSTP single object
//   proto - dynamically built prototype to be assigned
//
api.jstp.assignPrototype = function(data, proto) {
  if (Array.isArray(data)) {
    data.forEach(function(item) {
      item.__proto__ = proto.prototype;
    });
  } else {
    data.__proto__ = proto.prototype;
  }
};

// Build Prototype from Metadata
//
api.jstp.buildPrototype = function(metadata) {
  var protoClass = function ProtoClass() {};
  var index = 0, fieldDef, buildGetter, fieldType;
  for (var name in metadata) {
    fieldDef = metadata[name];
    fieldType = typeof(fieldDef);
    if (fieldType !== 'function') fieldType = fieldDef;
    buildGetter = api.jstp.accessors[fieldType];
    if (buildGetter) buildGetter(protoClass, name, index++, fieldDef);
  }
  return protoClass;
};

api.jstp.accessors = {};

api.jstp.accessors.string = function(proto, name, index) {
  Object.defineProperty(proto.prototype, name, {
    get: function() {
      return this[index];
    },
    set: function(value) {
      this[index] = value;
    }
  });
};

api.jstp.accessors.Date = function(proto, name, index) {
  Object.defineProperty(proto.prototype, name, {
    get: function() {
      return new Date(this[index]);
    },
    set: function(value) {
      this[index] = value instanceof Date ? value.toISOString() : value;
    }
  });
};

api.jstp.accessors.function = function(proto, name, index, fieldDef) {
  Object.defineProperty(proto.prototype, name, { get: fieldDef });
};

@aqrln
Copy link
Member Author

aqrln commented Dec 2, 2016

The solution with assigning a dynamically generated prototype to an array is elegant but I see several problems here:

  1. Performance. Changing the prototype of an existing object is not only quite a slow operation itself but also can cause deopts in the code that references such objects.

    Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of altering inheritance are subtle and far-flung, and are not limited to simply the time spent in obj.__proto__ = ... statement, but may extend to any code that has access to any object whose [[Prototype]] has been altered. If you care about performance you should avoid setting the [[Prototype]] of an object.

    (source)

    This can be worked around, though, by constructing a new object using Object.create(newPrototype, oldObject). But after this operation JSRD array will definitely not be represented as an array internally. Instead, it will be a hash map with numeric keys, in addition to string keys defined in prototype, so we get an extra overhead.

  2. We lose some flexibility when working with such objects because it would be necessary to treat them specifically. Not much of JavaScript code expects any properties except for functions to be inherited via prototype chain so any function that relies on Object.keys() or hasOwnProperty() will be broken when such object is passed. For example, if we needed to iterate such object's keys, we would either need to use a slow for-in loop, or pass metadata along with the object, iterate its keys and check for corner cases such as optional keys manually.

To conclude, it will be better to construct an object instead of assigning a prototype. Such objects will not have subtle drawbacks when working with them and, even more importantly, will work significantly faster.

FYI @tshemsedinov

@aqrln
Copy link
Member Author

aqrln commented Dec 2, 2016

We will need custom prototypes, though. For functions.

Thus the basic algorithm is:

  1. If the prototype is not found in registry, create a prototype containing all the functions defined in metadata and cache it for later usage with all objects of that type.
  2. Create an empty object using Object.create(prototype).
  3. For each property declared in metadata, take the next value from record data and assign the property to the object.

@aqrln aqrln added this to the 1.0.0 milestone May 5, 2017
@aqrln aqrln removed this from the 1.0.0 milestone May 23, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant