Добавление прототипа конструктора к объекту javascript
У меня есть несколько объектов javascript, например:
var object = {
name: "object name",
description: "object description",
properties: [
{ name: "first", value: "1" },
{ name: "second", value: "2" },
{ name: "third", value: "3" }
]
};
Теперь я хотел изменить эти объекты на более умные объекты (добавив некоторые методы и т.д.).
Сначала я создал такую конструкторскую функцию:
SmartObject = function( object ){
this.name = object.name;
this.description = object.description;
this.properties = object.properties;
};
SmartObject.prototype.getName = function(){
return this.name;
};
SmartObject.prototype.getDescription = function(){
return this.description;
};
SmartObject.prototype.getProperies = function(){
return this.properties;
};
И затем я использовал этот конструктор для изменения моего объекта на экземпляр SmartObject
следующим образом:
var smartObject = new SmartObject( object );
Для этого кажется правильным объектно-ориентированным javascript-кодом, но это слишком сложно, поскольку все, что я действительно хочу сделать, это добавить некоторые методы, и теперь я копирую все свойства из object
в SmartObject
в SmartObject
в конструктор.
В этом примере есть только 3 свойства и некоторые простые методы, но в моем проекте есть несколько десятков (вложенных) свойств и более сложные методы.
Итак, я пробовал это:
object.__proto__ = SmartObject.prototype;
И это, по-видимому, приводит к точному такому же результату и кажется намного проще (проверьте эта скрипка для этого пример).
Является ли это правильным и приемлемым способом добавления прототипа к моему объекту? Или - это ломающиеся объектно-ориентированные шаблоны и считается плохой практикой, и я должен продолжать делать то, что сделал (используя конструктор).
Или может быть еще один приемлемый способ добавления методов к моему существующему объекту, не вытягивая его через конструктор?
Примечание. Я попытался найти такой пример в StackOverflow, но при поиске я всегда заканчиваю примерами, расширяющими прототип существующих классов javascript. Если есть такой вопрос, не стесняйтесь отмечать это как дубликат, и я снова закрою свой вопрос.
Ответы
Ответ 1
Как я уже упоминал ранее, изменение прототипа объекта окажет серьезное влияние на производительность вашего кода. (tbh, я никогда не занимал время, чтобы измерить воздействие). Эта страница MDN объясняет.
Однако, если речь идет о шаблоне, вы можете легко создать общий factory для своих объектов, например:
function genericFactory(proto, source) {
return Object.keys(source).reduce(function(target, key) {
target[key] = source[key];
return target;
}, Object.create(proto));
}
И теперь вы можете использовать его, передав SmartObject.prototype
и object
в качестве аргументов следующим образом:
var smartObject = genericFactory(SmartObject.prototype, object);
Ответ 2
Сочетание Object.create()
от @SebastienDaniel его ответа и @bloodyKnuckles его комментарий и метод Object.assign()
, предложенный @nnnnnn в его комментарий мне удалось с помощью следующего простого кода сделать именно то, что я хотел:
var smartObject = Object.assign( Object.create( SmartObject.prototype ), object );
Отметьте обновленную скрипку здесь
Ответ 3
Итак, я пробовал это:
object.__proto__ = SmartObject.prototype;
...
Является ли это правильным и приемлемым способом добавления прототипа к моему объекту? Или это разбитые объектно-ориентированные шаблоны и считается плохой практикой, и я должен продолжать делать, как я сделал (используя конструктор).
Я рекомендую против него, потому что:
-
Изменение прототипа объекта после факта разрушает его производительность в современных механизмах JavaScript.
-
Это необычно, и, таким образом, ваш код немного чужд кому-либо еще, что вы могли бы его сохранить.
-
Он зависит от браузера; свойство __proto__
определяется только в Приложении к спецификации JavaScript и только для браузеров (спецификация требует, чтобы браузеры реализовали его, хотя). Не-браузерный способ - Object.setPrototypeOf(object, SmartObject.prototype);
, но смотрите # 1 и # 2.
Вы видите, что он избыточный или повторяющийся, либо на уровне кодирования, либо на уровне памяти (я не уверен). Это не значит, что вы принимаете SmartObject
с самого начала, а не сначала создаете object
, а затем добавляете smarts позже:
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(
/*...*/
);
или еще лучше, с ES2015 (который вы можете использовать сегодня с транспилером, таким как 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(
/*...*/
);
Вы сказали, что не можете принять SmartObject
с самого начала, потому что они поступают из источника JSON. В этом случае вы можете включить ваш SmartObject
в синтаксический анализ JSON, используя функцию 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;
});
Хотя это означает, что объекты сначала создаются, а затем воссоздаются, создание объектов - очень дешевая операция; здесь пример, показывающий разницу во времени при анализе объектов 20k с и без редактора:
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());
Ответ 4
Учитывая, что ваш объект аналогичен объекту SmartObject в свойствах, вы можете найти такие вещи:
var obj = {
name: "object name",
description: "object description",
properties: [
{ name: "first", value: "1" },
{ name: "second", value: "2" },
{ name: "third", value: "3" }
]
},
SmartObject = function( object ){
this.name = object.name;
this.description = object.description;
this.properties = object.properties;
};
SmartObject.prototype.getName = function(){
return this.name;
};
SmartObject.prototype.getDescription = function(){
return this.description;
};
SmartObject.prototype.getProperties = function(){
return this.properties;
};
obj.constructor = SmartObject;
obj.__proto__ = obj.constructor.prototype;
console.log(obj.getName());