将构造函数原型添加到 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

在这个例子中,只有 3 个属性和一些简单的方法,但在我的项目中,有几十个(嵌套)属性和更复杂的方法。

然后我尝试了这个:

object.__proto__ = SmartObject.prototype;

这似乎会导致完全相同的结果,并且似乎要容易得多(检查此示例中的小提琴)。

这是将原型添加到我的对象的适当且可接受的方法吗?或者这是否打破了面向对象的模式并被认为是不好的做法,我应该继续像我一样做(使用构造函数)。

或者是否有另一种更可接受的方法可以将方法添加到我的现有对象中,而无需通过构造函数拉取它?


注意。我试图在StackOverflow上找到这样的例子,但是当我搜索时,我总是以扩展现有javascript类原型的示例结束。如果有这样的问题,请随时将其标记为重复,我将再次关闭我的问题。

正如我之前提到的,更改对象的原型会对代码的性能产生严重影响。(tbh,我从来没有花时间衡量影响)。这个MDN页面解释道。

但是,如果你的问题是关于样板的,你可以轻松地为你的对象创建一个泛型工厂,例如:

function genericFactory(proto, source) {
return Object.keys(source).reduce(function(target, key) {
target[key] = source[key];
return target;
}, Object.create(proto));
}

现在,您可以通过将SmartObject.prototypeobject作为参数传递来使用它,如下所示:

var smartObject = genericFactory(SmartObject.prototype, object);

结合@SebastienDaniel他的答案Object.create()@bloodyKnuckles他的评论和@nnnnnn在他的评论中建议的Object.assign()方法,我用以下简单的代码来做我想要的:

var smartObject = Object.assign( Object.create( SmartObject.prototype ), object );

在此处查看更新的小提琴

然后我尝试了这个:

object.__proto__ = SmartObject.prototype;

这是将原型添加到我的对象的适当且可接受的方法吗?或者这是否打破了面向对象的模式并被认为是不好的做法,我应该继续像我一样做(使用构造函数)。

我建议不要这样做,因为:

  1. 事后更改对象的原型会破坏其在当前 JavaScript 引擎中的性能。

  2. 这是不寻常的,因此使您的代码与您可能维护它的任何其他人来说有点陌生。

  3. 它是特定于浏览器的;__proto__属性仅在 JavaScript 规范的附录中定义,并且仅适用于浏览器(尽管该规范确实需要浏览器来实现它)。非浏览器特定方式Object.setPrototypeOf(object, SmartObject.prototype);但请参阅 #1 和 #2。

你会看到担心它是冗余的或重复的,无论是在编码级别还是在内存级别(我不确定)。如果你从一开始就接受你的SmartObject,而不是先创造object,然后再添加智慧,那就不是这样:

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解析中:

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());

在我的机器上,它的时间大约翻了一番,但我们谈论的是~60ms到~120ms,所以从绝对值来看,这没什么可担心 的——这是20k个对象。


或者,您可以混合使用您的方法,而不是拥有原型:

// 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有Object.assign,你可以用它来代替smartObjectMethodNames和内部forEach

// Once we have the options, we update them all:
objects.forEach(function(v) {
Object.assign(v, smartObjectMethods);
});

无论哪种方式,它的内存效率都略低,因为每个对象最终都有自己的getNamegetDescriptiongetProperties属性(函数不重复,它们是共享的,但引用它们的属性是重复的)。不过,这极不可能成为问题。

下面是一个包含 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 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());

鉴于您的对象在属性中与 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());

好的,__proto__属性现在包含在 ECMAScript 标准中,可以安全使用。但是,还有可以以相同方式使用的Object.setPrototypeOf()对象方法。所以你不妨喜欢Object.setPrototypeOf(obj, obj.constructor.prototype)代替obj.__proto__ = obj.constructor.prototype;

最新更新