使用'Object.create()'而不是'new'关键字来理解原型对象创建

10

我看到了一段包含以下代码的程序:

var data = function() {
    function Metadata() { /*some initialization here*/ }

    Metadata.prototype = Object.create(Backend.prototype);
    Metadata.prototype.constructor = Metadata;

    return Metadata;
}

我很难理解实际上正在发生什么,以及如何使用返回的对象。如果我理解得正确,data现在将是一个应该像这样初始化的对象

var d = new data()

但我不理解下面的代码为什么要使用Object.create()而不是new关键字:


Metadata.prototype = Object.create(Backend.prototype);
Metadata.prototype.constructor = Metadata;

他们做什么?他们是否必要?Object.createnew之间有什么区别?

4个回答

5
那两行是将Metadata扩展为Backend的原型方式。
JavaScript中的类被定义为函数。如果在函数上定义了prototype,则可以使用new关键字(或Object.create)创建类的实例。原型中的函数/属性将存在你创建的类的新实例中。
在上面的代码中,Metadata.prototype被设置为Backend的一个实例,因此Metadata.prototype将继承调用Backend.prototype上的方法/属性。
你可以在继承和原型链找到更多信息。

Metadata.prototype = Object.create(Backend.prototype);Metadata.prototype = new Backend() 有什么区别? - Karel Bílek
2
当您使用Object.create时,构造函数不会被调用。因此,如果您只想扩展Backend的原型,则使用Object.create是有意义的。 - wjohnsto

5
JavaScript是一种基于原型的语言,这意味着它不像其他语言那样使用class关键字。相反,JavaScript使用函数作为类。在您的示例中,data变量可以被视为一个类: var Data = function() { ... } 要创建此类的实例,我们使用new关键字将类型为对象的结果赋给变量。 var data = new Data() 自ECMA Script 6以来,我们可以使用Object.create()实例化方法来创建具有指定原型对象和属性的未初始化对象。它以参数形式接受应该是新创建的对象原型的对象。(它也复制构造函数)
因此,以下行是一种使元数据扩展后端对象并保持其自己的构造函数的方法:
// Metadata extends Backend
Metadata.prototype = Object.create(Backend.prototype);
Metadata.prototype.constructor = Metadata;

但是这段代码并不完全等同于Metadata.prototype = new Backend();。看下面的例子:
//Base class
var Backend = function(){ this.publicProperty='SomeValue'; }

//Extension class 1
var Metadata1 = function() { }
Metadata1.prototype = Object.create(Backend.prototype);
Metadata1.prototype.constructor = Metadata1;

//Extension class 2
var Metadata2 = function() { }
Metadata2.prototype = new Backend();


/*
 *  Then the results are different (see code snippet at the end of this post)
 */
//result1 = 'undefined'
var data1 = new Metadata1();
var result1 = data1.publicProperty;

//result2 = 'SomeValue'
var data2 = new Metadata2();
var result2 = data2.publicProperty;

实际上它们非常相似,主要区别在于new关键字实际运行构造函数代码,而Object.create不会执行代码。
另一个区别是,使用Object.create可以创建一个不继承任何东西的对象(Object.create(null))。而如果你执行Metadata.prototype = null,新创建的对象将继承自Object.prototype
注意:在一些老的浏览器(如IE8及以下版本),你可以使用下面等效的代码来代替Object.create
Object.create = function(o){
    function F(){}
    F.prototype=o;
    return new F();
}

这里是工作代码片段,展示了两种方法之间的差异。

//Base class
var Backend = function(){ this.publicProperty='SomeValue'; }

//Extension class 1
var Metadata1 = function() { }
Metadata1.prototype = Object.create(Backend.prototype);
Metadata1.prototype.constructor = Metadata1;

//Extension class 2
var Metadata2 = function() { }
Metadata2.prototype = new Backend();


//result: 'undefined'
var data1 = new Metadata1();
$("#result1").text("result1: " +  (typeof data1.publicProperty=='undefined' ? 'undefined' : data1.publicProperty));

//result: 'SomeValue'
var data2 = new Metadata2();
$("#result2").text("result2: " +  (typeof data2.publicProperty=='undefined' ? 'undefined' : data2.publicProperty));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result1"></div>
<div id="result2"></div>


1
谢谢。这太令人困惑了,我非常高兴ES6继承语义(“extends”,构造函数,“super”)更容易理解。 - Karel Bílek

3
在JavaScript中没有类,而是使用构造函数来创建新对象,可以使用new关键字调用它们,以获得与类和实例化相同的行为。
这两行代码用于表示继承,并使Metadata扩展Backend,其中一行如下:
Metadata.prototype = Object.create(Backend.prototype);

我们定义了 元数据 的原型,这个对象应该是新创建的对象的原型。
在这一行中:
Metadata.prototype.constructor = Metadata;

我们定义了 Metadata 的构造函数,用于创建新的实例。
继承可以通过以下方式进行测试:
var meta = new Metadata(); 

console.log('Is meta an instance of Metadata? ' + (meta instanceof Metadata)); // true
console.log('Is meta an instance of Backend? ' + (meta instanceof Backend)); // true

您可以在Object.create()文档中找到更多关于它的信息和另一个例子。


meta instanceof Backend 应该也是 true - Leo
@Leo 指出得很好,是的当然,我只是错过了它,我编辑了我的答案,谢谢。 - cнŝdk

2

Object.create是ES6的一个方法,它可以创建一个以给定对象为原型的对象。

Metadata.prototype = Object.create(Backend.prototype);

因此,上述代码意味着Metadata继承了Backend的所有属性和方法。这与ES6之前的以下代码有些相似:

Metadata.prototype = new Backend();

然而,元数据(Metadata)也从后端(Backend)继承了构造函数(constructor)属性:

var metaData = new Metadata();
metaData.constructor; // function Backend()

这看起来很奇怪,因为 metaData 实际上是用 Metadata 构建的,所以我们可以像这样修复它:

Metadata.prototype.constructor = Metadata;

var metaData = new Metadata();
metaData.constructor; // function Metadata()

请注意,这不会影响instanceof运算符:
metaData instanceof Metadata // true
metaData instanceof Backend  // true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接