将构造函数原型添加到javascript对象

时间:2016-06-17 12:08:16

标签: javascript object methods extending

我有几个这样的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复制到我的{{1在构造函数中。

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

然后我尝试了这个:

SmartObject

这似乎会产生完全相同的结果并且看起来更容易(请查看 this fiddle 此示例)。

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

或者是否有另一种更可接受的方法将方法添加到现有对象而不通过构造函数拉出来?

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

4 个答案:

答案 0 :(得分:11)

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

但是,如果您的问题与样板有关,则可以轻松地为对象创建通用工厂,例如:

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

答案 1 :(得分:2)

结合 @SebastienDaniel token @bloodyKnuckles 他的评论以及 @nnnnnn <建议的Object.create()方法/ strong>在他的评论中,我使用以下简单代码来完成我想要的操作:

Object.assign()

检查the updated fiddle here

答案 2 :(得分:1)

  

然后我尝试了这个:

object.__proto__ = SmartObject.prototype;
     

...

     

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

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

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

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

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

  4. 你会发现它在编码级别或内存级别上是多余的或重复的(我不确定)。如果你从一开始就拥抱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源。在这种情况下,您可以使用 reviver 函数将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;
    });
    

    虽然这确实意味着首先创建对象然后重新创建,但创建对象是一个非常便宜的操作;这是一个示例,显示了使用和不使用reviver解析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); });

    getName

    无论哪种方式,其 略微 内存效率较低,因为每个对象最终都拥有自己的getDescriptiongetProperties和{ {1}}属性(函数不重复,它们是共享的,但引用它们的属性是重复的)。但是,这不太可能是一个问题。

    以下是再次使用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());

答案 3 :(得分:0)

鉴于你的对象与属性中的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;