Object.create而不是Constructors用于继承

时间:2013-04-24 08:27:37

标签: javascript ecmascript-5

我希望能够学习按照新的JavaScript ECMA 5标准创建对象,并在我当前的项目中使用它们,而不会破坏功能。但我看到让我害怕的不可解释的东西

请考虑以下代码:

var foo = {
        oldProp: 'ECMA Script 3',
        newProp: 'ECMA Script 5'
    };

// The new way of inheritance
var bar = Object.create( foo );

// and of assigning properties, if I am right
var bar2 = Object.create( Object.prototype, {
        oldProp: { value: 'ECMA Script 3'},
        newProp: { value: 'ECMA Script 5'}
    });

// and then
bar; 
// console output
Object {oldProp: "ECMA Script 3", newProp: "ECMA Script 5"}

// whereas !!
bar2;
Object {} // Why this?

另外,

bar.hasOwnProperty('oldProp');  // false, whereas
bar2.hasOwnProperty('oldProp'); // true !! It should be the other way round, right? 

此外,我无法更改bar2

的属性
bar2.oldProp = "Marry had a little lamb";
bar2.oldProp; // "ECMA Script 3"

还有更多 -

for (k in bar2) console.log(bar2[k]) // undefined  

我是否以正确的方式创建对象? 我基本上想要实现的是摆脱构造函数。

旧代码:

var Slideshow = function(type) {
    this.type = type;
    this.deleteSlideshow = function() {
        // some code
    }
}
Slideshow.prototype.nextSlide = function() {
    // lotsa code
}
var vanityFairSlideshow = new Slideshow('wide-screen');

我想更改为Object.create,可能是这样的:

var Slideshow = Object.create({ /* prototype */ }, { /* properties and methods */ });
var vanityFairSlideshow = Object.create( Slideshow , { /* properties and methods */ });

这样做的正确方法是什么?

4 个答案:

答案 0 :(得分:2)

我会尝试回答这个问题:

  

我是否以正确的方式创建了对象?我基本上想要实现的是摆脱构造函数。

第一个小例子。想象一下,我们有这个简单的结构:

var Slideshow = function(type) {
  this.type = type;
}

Slideshow.prototype.nextSlide = function() {
    console.log('next slide');
}

Slideshow.prototype.deleteSlideshow = function() {
    console.log('delete slideshow');
}

var AdvancedSlideshow = function(bgColor) {
    this.bgColor = bgColor;
}

AdvancedSlideshow.prototype.showMeBgColor = function() {
    console.log('bgColor iS: ' + bgColor);
}

现在我们希望AdvancedSlideshow继承Slideshow。因此,我们希望父级的所有函数都可用于其子级的实例。没有Object.create,我们就这样做了:

// THIS CODE ISNT CORRECT
// code for slideshow is same

var AdvancedSlideshow = function(type, bgColor) {
    // this line can supply whole Slideshow constructor
    Slideshow.call( this, type ); 

    this.bgColor = bgColor;
}

AdvancedSlideshow.prototype = Slideshow.prototype; // there problems start!!!!

AdvancedSlideshow.prototype.showMeBgColor = function() { // we did augumentation of Slideshow
    console.log('bgColor is: ' + bgColor);
}

现在AdvancedSlideshow继承了Slideshow的所有内容,但 Slideshow继承了AdvancedSlideshow 的所有内容。这就是我们不想要的

var simpleSlideshow = new Slideshow('simple');
simpleSlideshow.showMeBgColor(); // ReferenceError: bgColor is not defined
                                 // but function exists and thats wrong!

所以我们必须使用更复杂的东西。很久以前,Douglas Crockford制造了一种填充物。

if (!Object.create) {
    Object.create = function (o) {
        if (arguments.length > 1) {
            throw new Error('Object.create implementation only accepts the first parameter.');
        }
        function F() {}
        F.prototype = o;
        return new F();
    };
}

首先,它不是polyfill,它是小图案,看起来不像。但模式正在发展,然后它变得非常好,它成为了javascript的一部分。所以现在我们只为浏览器提供了一个不支持ecmascript-5的polyfill。我们可以编码:

// THIS IS CORRECT
AdvancedSlideshow.prototype = Object.create(Slideshow.prototype);

而不是

function F() {};
F.prototype = Slideshow.prototype;
AdvancedSlideshow.prototype = new F();

然后AdvancedSlideshow继承自Slideshow,但 Slideshow不会继承AdvancedSlideshow

所以Object.create的目的不是要摆脱构造者或者#34;新的"关键字。它的目的是制作正确的原型链!

编辑20.2.2014: 几天前我重新确定,constructor属性是原型链的一部分。因此,如果我们使用Object.create进行继承,我们还会更改子对象的constructor属性(不是构造函数本身,只是属性)。因此,子对象的新实例的构造函数属性指向其父对象。我们可以使用简单的Child.prototype.constructor = Child修复此问题。

...
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

var first = new Child();
first.constructor === Child; // true
first.constructor === Parent; // false
first instanceOf Child; // true
first instanceOf Parent; // true 

现在,Object.create有第二个参数,它允许你让我们说出隐藏的秘密"对象但是我不推荐你这样做,因为你不需要它。它是先进的,也像某些模式一样工作。

如果你想避免构造函数," new"关键字或任何重要的东西,那么你可能正在寻找一些工厂模式。但请记住,例如" new"有一些弱点,但删除内置功能的模式通常更糟糕!无论如何,也许你可以在这里找到一些灵感:

http://www.2ality.com/2010/12/javascripts-prototypal-inheritance.html

答案 1 :(得分:1)

此处的问题是您为每个媒体资源省略的descriptorsvalue除外)的默认设置。

默认使用此语法(例如definePropertydefineProperties),writablefalseenumerablefalse

将这两者设置为true将导致您期望的行为。

var bar2 = Object.create( Object.prototype, {
    oldProp: { value: 'ECMA Script 3', writable: true, enumerable: true}
});

http://jsfiddle.net/YZmbg/

此外,希望您了解所有浏览器都不支持此功能;请注意compatibility table for Object.create。符合writableconfigurable属性的垫片不存在,也不会存在。

最后,+1这样一个写得很好的问题;这是一股清新的空气。

答案 2 :(得分:1)

创建

bar时没有任何属性,foo作为原型,因此bar.hasOwnProperty('oldProp');返回false。

对于bar2,您不提供描述符,enumerable和可写的默认值为false。因此,您无法在for...in循环中更改属性或枚举它们。

至于我是否以正确的方式创建了对象?:你在做什么是合理的。您可以创建Slideshow作为所有幻灯片的原型,并使用此原型和其他属性创建幻灯片。

答案 3 :(得分:0)

bar2 // logs Object {}
     

我无法更改bar2的属性

for (k in bar2) console.log(bar2[k]) // undefined

这是因为Object.create的第二个参数类似于Object.defineProperties - 这意味着您正在使用property descriptors。虽然您只设置,但enumerablewritableconfigurable标记默认为false

  

这样做的正确方法是什么?

您明确地将enumberablewritable标志明确设置为true,或者您不使用第二个参数:

 var bar2 = Object.create( Object.prototype ); // or Slideshow.prototype or whatever
 bar2.oldProp = 'ECMA Script 3';
 bar2.newProp = 'ECMA Script 5';

但是,如果这对你来说太长了,你会使用工厂实用程序函数 - 基本上是构造函数。