So then I tried this:
object.__proto__ = SmartObject.prototype;
...
Is this a proper and acceptable way to add the prototype to my object? Or is this breaking object oriented patterns and considered bad practice and should I continue doing like I did (using the constructor).
I recommend against it, because:
Changing the prototype of an object after-the-fact ruins its performance in current JavaScript engines.
It's unusual, and thus makes your code a bit alien to anyone else you might have maintain it.
It's browser-specific; the __proto__
property is only defined in an Appendix to the JavaScript spec, and only for browsers (the spec does require browsers to implement it, though). The non-browser-specific way is Object.setPrototypeOf(object, SmartObject.prototype);
but see #1 and #2.
You see concerned that it's redundant or repetitive, either at the coding level or at the memory level (I'm not sure). It isn't if you embrace your SmartObject
from the beginning rather than creating object
first and then adding the smarts later:
var SmartObject = function(name, description, properties) {
this.name = name;
this.description = description;
this.properties = properties;
};
SmartObject.prototype.getName = function(){
return this.name;
};
SmartObject.prototype.getDescription = function(){
return this.description;
};
SmartObject.prototype.getProperies = function(){
return this.properties;
};
var object = new SmartObject(
"object name",
"object description",
[
{ name: "first", value: "1" },
{ name: "second", value: "2" },
{ name: "third", value: "3" }
]
);
var anotherObject = new SmartObject(
/*...*/
);
var yetAnotherObject = new SmartObject(
/*...*/
);
or better yet, with ES2015 (which you can use today with a transpiler like Babel):
class SmartObject {
constructor() {
this.name = name;
this.description = description;
this.properties = properties;
}
getName() {
return this.name;
}
getDescription() {
return this.description;
}
getProperies(){
return this.properties;
}
}
let object = new SmartObject(
"object name",
"object description",
[
{ name: "first", value: "1" },
{ name: "second", value: "2" },
{ name: "third", value: "3" }
]
);
let anotherObject = new SmartObject(
/*...*/
);
let yetAnotherObject = new SmartObject(
/*...*/
);
You've said that you can't embrace SmartObject
from the beginning because they're coming from a JSON source. In that case, you can incorporate your SmartObject
into the JSON parsing using a reviver function:
var objects = JSON.parse(json, function(k, v) {
if (typeof v === "object" && v.name && v.description && v.properties) {
v = new SmartObject(v.name, v.description, v.properties);
}
return v;
});
While that does mean that the objects are first created and then recreated, creating objects is a very cheap operation; here's an example showing the time difference when parsing 20k objects with and without a reviver:
var json = '[';
for (var n = 0; n < 20000; ++n) {
if (n > 0) {
json += ',';
}
json += '{' +
' "name": "obj' + n + '",' +
' "description": "Object ' + n + '",' +
' "properties": [' +
' {' +
' "name": "first",' +
' "value": "' + Math.random() + '"' +
' },' +
' {' +
' "name": "second",' +
' "value": "' + Math.random() + '"' +
' }' +
' ]' +
'}';
}
json += ']';
var SmartObject = function(name, description, properties) {
this.name = name;
this.description = description;
this.properties = properties;
};
SmartObject.prototype.getName = function() {
return this.name;
};
SmartObject.prototype.getDescription = function() {
return this.description;
};
SmartObject.prototype.getProperies = function() {
return this.properties;
};
console.time("parse without reviver");
console.log("count:", JSON.parse(json).length);
console.timeEnd("parse without reviver");
console.time("parse with reviver");
var objects = JSON.parse(json, function(k, v) {
if (typeof v === "object" && v.name && v.description && v.properties) {
v = new SmartObject(v.name, v.description, v.properties);
}
return v;
});
console.log("count:", objects.length);
console.timeEnd("parse with reviver");
console.log("Name of first:", objects[0].getName());
On my machine, it roughly doubles the time, but we're talking ~60ms to ~120ms, so in absolute terms it's nothing to worry about — and that's for 20k objects.
Alternately, you could mix in your methods rather than having a prototype:
// The methods to mix in
var smartObjectMethods = {
getName() {
return this.name;
},
getDescription() {
return this.description;
},
getProperies() {
return this.properties;
}
};
// Remember their names to make it faster adding them later
var smartObjectMethodNames = Object.keys(smartObjectMethods);
// Once we have the options, we update them all:
objects.forEach(function(v) {
smartObjectMethodNames.forEach(function(name) {
v[name] = smartObjectMethods[name];
});
});
ES2015 has Object.assign
which you could use instead of smartObjectMethodNames
and the inner forEach
:
// Once we have the options, we update them all:
objects.forEach(function(v) {
Object.assign(v, smartObjectMethods);
});
Either way, its slightly less memory-efficient because each of the objects ends up having its own getName
, getDescription
, and getProperties
properties (the functions aren't duplicated, they're shared, but the properties to refer to them are duplicated). That's extremely unlikely to be a problem, though.
Here's an example with 20k objects again:
var json = '[';
for (var n = 0; n < 20000; ++n) {
if (n > 0) {
json += ',';
}
json += '{' +
' "name": "obj' + n + '",' +
' "description": "Object ' + n + '",' +
' "properties": [' +
' {' +
' "name": "first",' +
' "value": "' + Math.random() + '"' +
' },' +
' {' +
' "name": "second",' +
' "value": "' + Math.random() + '"' +
' }' +
' ]' +
'}';
}
json += ']';
var smartObjectMethods = {
getName() {
return this.name;
},
getDescription() {
return this.description;
},
getProperies() {
return this.properties;
}
};
var smartObjectMethodNames = Object.keys(smartObjectMethods);
console.time("without adding methods");
console.log("count:", JSON.parse(json).length);
console.timeEnd("without adding methods");
console.time("with adding methods");
var objects = JSON.parse(json);
objects.forEach(function(v) {
smartObjectMethodNames.forEach(function(name) {
v[name] = smartObjectMethods[name];
});
});
console.log("count:", objects.length);
console.timeEnd("with adding methods");
if (Object.assign) { // browser has it
console.time("with assign");
var objects = JSON.parse(json);
objects.forEach(function(v) {
Object.assign(v, smartObjectMethods);
});
console.log("count:", objects.length);
console.timeEnd("with assign");
}
console.log("Name of first:", objects[0].getName());