【问题标题】:Object creation patterns using Object.create()使用 Object.create() 的对象创建模式
【发布时间】:2015-02-12 07:40:23
【问题描述】:

我一直在弄清楚如何在使用 Object.create() 而不是传统的构造函数时进行多级继承。我打开了一个小提琴 here

我们有两种类型的对象,程序员和“子类”前端开发人员。程序员继承自程序员原型对象,正如您所见,所有程序员都可能很尴尬。

var programmerProto = Object.create(Object.prototype,{    
   beAwkward: {value: function(){
          return "Yeah I uhh, AHEM, *shuffles feet*";
   }
  }    
});

function programmerFactory (name, age, neckbeard){
  return Object.create(programmerProto, {
    name: {writable: true, enumerable: true, value: name},
    age: {writable: false, enumerable: true, value: age},
    neckbeard: {writable: true, enumerable: true, value: neckbeard}
  });
}

现在对于前端开发人员来说,他们有自己的共同特征,所以我们需要一个新的原型对象。但是,前端开发者也是程序员,所以我们制作前端原型对象如下:

 var frontEndProto = Object.create(programmerProto, {
   beSmooth: {
     value: function(){
                 return "Excuse me ma'am, could I interest you in a, fish sandwich?";
             }        
 },
   preferredLanguage: {writable: true, enumerable: true, value: 'JS'}
});

现在我们遇到了问题。我想使用工厂方法来创建前端开发人员的实例,所以我可以添加一些前端特定的属性,比如“CSS3_skillz”之类的。但是如果我使用 Object.create(frontEndProto, "..."),那 ... 将是我已经在programmerFactoryFunction 中编写的所有代码。如果我从frontEndFactory 中调用programmerFactory,则返回对象的原型是programmerProto 而不是frontEndProto。处理这个问题的正确方法是什么?

【问题讨论】:

    标签: javascript inheritance prototype factory


    【解决方案1】:

    我认为您在继承链和Object.defineProperty() 样式属性的使用上有点过火了。

    除非您有充分的理由需要像instanceof 这样的东西,否则您可以只使用.extend() 函数来实现您的继承。我正在使用下面的下划线/lodash _.extend() 函数,但您几乎可以使用它的任何实现:

    var programmerProto = {    
        beAwkward: function(){
            return "Yeah I uhh, AHEM, *shuffles feet*";
        }
    };
    
    function programmerFactory (name, age, neckbeard){
        return _.extend({
            name: name,
            age: age,
            neckbeard: neckbeard
        }, programmerProto);
    }
    
    var frontEndProto = {
        beSmooth: function(){
            return "Excuse me ma'am, could I interest you in a, fish sandwich?";
        },
        preferredLanguage: 'JS'
    };
    
    function frontEndFactory(name, age, css3Skillz) {
        return _.extend(programmerFactory(name, age, true), {
            css3Skillz: css3Skills
        }, frontEndProto);
    }
    

    我觉得这个话题的演讲还不错:
    http://www.youtube.com/watch?v=PV_cFx29Xz0&t=28m57s

    【讨论】:

    • 这是有道理的。我被困在需要使用 Object.create 来指定原型的心态中,但是扩展可以解决问题,而无需使用属性描述符。我唯一担心的是,这不会使每个“实例”更重吗?原型的属性不再位于原型链中,而是附加到每个实例,对吗?
    • @Derek 是的,您的对象可能会占用一些额外的字节,因为它们直接引用了它们的属性,但请记住,原型链也不是没有成本的。使用原型链,您将获得链本身的内存成本,以及每次访问属性时遍历该链的 CPU 成本。 Douglas Crockford 在 33:50 here 讨论了这一点,他的结论是内存很便宜,CPU 周期不是,所以放弃原型链。
    • 好的,这是一个很好的解释。只是为了我自己的理智,我们通过扩展在“实例”上获得的新属性,这些属性只是持有对原型对象上的方法的引用,对吗?但如果我们在类原型对象上有原语,那么“实例”会有完整的副本吗?
    • @Derek 是的,没错。它将完整地复制原语,并将对象(包括字符串和函数)作为引用。
    【解决方案2】:

    您的问题的解决方案在于将一个对象的属性附加到另一个对象,这在这里很有用。

    首先,我使用的append函数——

    var appendObject = function(obj, toAppend) { 
        Object.keys(toAppend).forEach(function(el) { 
            Object.defineProperty(obj, el, Object.getOwnPropertyDescriptor(toAppend,el)); 
        }); 
        return obj; 
    }
    

    这样做非常简单——它遍历toAppend 的属性并使用它们的属性描述符来定义obj 上的新属性。它正在变异obj,但也会返回它,因此您可以在需要时将其链接起来。

    有了这个函数,你可以让他们像这样实现frontEndDevFactory -

    function frontEndDevFactory (name, age, pullupCount, rockStar){
        var programmer = programmerFactory(name, age); 
        var newDev = Object.create(frontEndProto, {
            pullupCount: {writable: true, enumerable: true, value: pullupCount},
            rockStar: {writable: false, enumerable: true, value: rockStar}
        }); 
        appendObject(newDev, programmer); 
        return newDev; 
    }
    

    这是一个 fiddle 修改您的代码来演示这一点。

    【讨论】:

    • 我认为这个例子减轻了我对@JLRishe 帖子的担忧。现在,newDev 不再将原型的属性直接放在对象上,而是拥有它的正确原型,并且 newDev 原型是从 Programmer.prototype 扩展而来的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    相关资源
    最近更新 更多