Javascript 1.9.3 / ECMAScript 5引入了Object.create,Douglas Crockford和其他人一直在倡导很长时间。如何将下面代码中的new替换为Object.create

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();


(假设存在MY_GLOBAL.nextId)。是:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();


似乎没有任何优势,所以我认为我没有。我可能太新古典了。我应该如何使用Object.create创建用户“ bob”?

评论

当接受的答案的投票数少于问题的投票数时,也许接受的答案是不可接受的? @CMS将让您编写一个工厂函数,该工厂函数在内部使用Object.create来获得与新UserA('bob');相同的单步功能。

考虑到它在所有6个答案中投票最多,也许是最可接受的答案。

另请参见了解Object.create()和new SomeFunction()之间的区别

克罗克福德(Crockford)是JS世界中的早期指导圣人,但他的鼎盛时期已经过去。即使Object.create在其他方面很有用,他也完全停止使用新的建议。

@Andy这样做的原因取决于您使用的范例。如果在JS中使用函数式编程,则更喜欢Object.create而不是new关键字。

#1 楼

仅具有一个继承级别,您的示例可能无法让您看到Object.create的真正好处。 >
在您的userB示例中,我不认为您的init方法应该是公共的,甚至不存在,如果您在现有对象实例上再次调用此方法,则idname属性将发生变化。
Object.create允许您使用第二个参数来初始化对象属性,例如:

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});


您可以看到,可以在Object.create的第二个参数上初始化属性,使用与Object.definePropertiesObject.defineProperty方法所使用的语法相似的对象文字。 br />

评论


1.感谢您指出差异继承。 2.这是否意味着不再有构造函数?我需要记住每次创建用户时都将“ id”设置为MY_GLOBAL.nextId()吗?

–格雷厄姆·金(Graham King)
2010-4-25 22:02

谢谢@Graham,是的,此方法不需要更多的构造函数,尽管Firefox 3.7apre5,最新的WebKit Nightly版本和Chrome 5 Beta的当前可用实现与普通的旧构造函数相比并没有那么快,希望这会在不久的将来改变。对于对象的创建,您可以创建一个工厂函数(即函数createUser(name){...),其中包含使用Object.create在其中创建用户对象所需的所有逻辑。

–基督徒C.Salvadó
2010-4-26的1:07



回复:不再有构造函数:通常,您会编写一个普通函数作为对象的“工厂”。在内部,它将使用Object.create制作一个空白对象,然后在返回之前根据需要对其进行修改。该工厂的调用者不必记住前缀new。

–丹尼尔(Daniel Earwicker)
2011年7月27日在9:11

@GrahamKing您可以使用闭包来初始化对象:jsfiddle.net/Prqdt

– ami
2012年6月25日14:31

@ryanve,如果对象具有一些您不想枚举的私有属性(例如,在for中,您不想列出对象元数据,而只是列出实际数据属性)。请参见MDN可枚举

–卡马羽
19-09-30在21:23



#2 楼

相对于Object.create(...),使用new object确实没有任何优势。但是,我还没有看到一个具体的示例,该示例表明Object.create与使用new相比具有任何优势。相反,存在已知问题。 Sam Elsamman描述了当存在嵌套对象并使用Object.create(...)时会发生的情况:

在这里,Object.create(...)基准成为Animallion原型的一部分,并且由于共享而引起问题。使用新原型时,显式继承是显式的:

评论


我不同意。正如链接文章所暗示的那样,Object.create既不强制也不鼓励使用原型作为任何类型的“默认数据值存储”的做法。适当的数据初始化是创建特定对象(例如OO设计中的工厂或建造者)的人的责任。 (在JS中,继承数据而不是行为是可行的,但不常见。)

–科斯
2012年11月14日12:54

只要您了解Object.create的参数应该是原型,就不会出现此问题。显然,如果您说Animal.prototype.traits = {} ;,则在使用new时也会出现同样的不良行为。很明显,您不应该这样做的唯一原因是您了解javascript原型的工作方式。

–plediii
13年1月3日,19:48



天哪!提供正确答案的否决方式很多:-)关键是Object.create不允许使用构造函数参数的机制,因此被迫扩展“​​数据”。现在,这些数据可能包含嵌套的对象,这导致了上述问题。另一方面,在原型继承的情况下,只有明确地写出Animal.prototype.traits = {};时,我们才会遇到这个问题。一种方法是隐式的,另一种是隐式的。不要选择导致问题的方法。

–诺埃尔·亚伯拉罕(Noel Abrahams)
13年6月16日在10:14

请参阅这篇文章,以简单地解决2条腿的狮子。这是一些工作代码来说明

– d13
13-10-31在2:42



我建议阅读Kyle Simpson的这篇文章。这三个部分都很有趣,但是第3部分是关键。如果阅读完这些书后您仍然认为“新”要比Object.create()更好,那么您就没有希望了! :) davidwalsh.name/javascript-objects-deconstruction

– MindJuice
14年6月18日在17:12

#3 楼

Object.create在某些浏览器上还不是标准的,例如IE8,Opera v11.5,Konq 4.3都没有。您可以为这些浏览器使用Douglas Crockford的Object.create版本,但这不包括CMS答案中使用的第二个“初始化对象”参数。

对于跨浏览器代码,一种在同时要自定义Crockford的Object.create。这是一种方法:-

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}


这将维护Crockford原型继承,并检查对象中的任何init方法,然后使用您的参数运行它,就像说一个新人('John','Smith')。这样,您的代码将变为:-

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();


所以bob继承了sayHello方法,现在拥有自己的属性id = 1和name ='Bob'。这些属性当然是可写和可枚举的。与ECMA Object.create相比,这也是一种更简单的初始化方式,尤其是在您不关心可写,可枚举和可配置的属性的情况下。可以使用:-

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}


这将按照定义它们的顺序填充userB自己的属性,在userB参数之后从左到右使用Object.gen参数。它使用for(prop in o)循环,因此,根据ECMA标准,无法保证属性枚举的顺序与属性定义的顺序相同。但是,在使用(4)个主要浏览器测试的几个代码示例中,它们相同,只要使用了hasOwnProperty过滤器,有时甚至不使用。我要说的是Object.build,因为userB不需要init方法。同样,userB不是专门的构造函数,但看起来像普通的单例对象。因此,使用此方法可以从普通的普通对象构造和初始化。

评论


ES5 Shim中有一个用于Object.create的polyfill github.com/kriskowal/es5-shim

–ryanve
2012年1月27日19:19

很棒的文章!我添加了一个JSFiddle,因此您在一个文件中有一个适用于Object.build和Object.gen的示例。另外,我还添加了一些分号(仅在JSFiddle中可用)。

–马特
13年8月13日在11:31



#4 楼

TL; DR:

new Computer()将一次调用构造函数Computer(){},而不会调用Object.create(Computer.prototype)

所有优点都基于这一点。

关于性能的注释:像new Computer()这样的构造函数调用已由引擎进行了优化,因此它甚至可能比Object.create更快。

#5 楼

您可以使init方法返回this,然后将调用链接在一起,如下所示:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');


#6 楼

Object.create的另一种可能用法是以廉价有效的方式克隆不可变的对象。

而不是参考,例如cObj = aObj)。它优于copy-properties方法(请参见1),因为它不复制对象成员属性。而是创建另一个-destination-对象,并将其原型设置在源对象上。此外,当在目标对象上修改属性时,它们会“即时”创建,从而掩盖了原型的(src)属性。这是克隆不可变对象的快速有效方法。就是说这适用于在创建后不能修改(不可变)的源对象。如果在创建后修改了源对象,那么所有克隆的未屏蔽属性也将被修改。浏览器)。

评论


称呼这个克隆对我(可能还有许多其他人)感到困惑。对大多数人来说,克隆方法意味着对原始方法的更改不会影响克隆。

– kybernetikos
2012-09-26 13:56

可以理解,我将答案修改为仅考虑被认为是不可变的源对象。

–基础
2012年12月24日12:58

以这种方式重复克隆对象将很快使您陷入困境,因为您将拥有一个带有很长原型链的对象,该原型链会将所有这些旧对象保留在内存中。有时,我会在测试中使用Object.create进行模拟,这些模拟将覆盖类实例的某些属性,同时使其他属性保持不变。但是我很少使用它。

–安迪
17年4月21日在17:27

我检查了上面的代码,可能是Object.create已经进化,但目前Object.create并未克隆任何东西,代码的结果为var bObj = Object.create(anObj); console.log(bObj);是{}表示不复制任何属性。

– dev_khan
19年11月22日在7:10

#7 楼

我认为主要问题-是了解newObject.create方法之间的区别。相应于此答案和此视频,new关键字还会执行以下操作:


创建新对象。
将新对象链接到构造函数(prototype)。变量指向新对象。
使用新对象执行构造函数并隐式执行this;
将构造函数名称分配给新对象的属性return this

constructor仅执行Object.create1st步骤!!!

在所提供的代码示例中没什么大不了的,但是在下一个示例中是:

var onlineUsers = [];
function SiteMember(name) {
    this.name = name;
    onlineUsers.push(name);
}
SiteMember.prototype.getName = function() {
    return this.name;
}
function Guest(name) {
    SiteMember.call(this, name);
}
Guest.prototype = new SiteMember();

var g = new Guest('James');
console.log(onlineUsers);


作为副作用的结果是:

[ undefined, 'James' ]


由于2nd
,但是我们不需要执行父构造函数方法,我们只需要使方法Guest.prototype = new SiteMember();成为可以在Guest中使用。
因此,我们必须使用getName
如果将Object.create
替换为Guest.prototype = new SiteMember();结果是:

[ 'James' ]


评论


这是使用Object,create来解决组合继承(构造函数窃取+原型链)的主要失败,这是父构造函数被调用两次的错误示例。 Object.create使用现有对象作为原型创建新对象,这意味着链中还有一个原型。可以通过使用寄生组合继承(构造函数窃取+混合原型链)解决上述故障。我们只需要复制父原型,就可以像这样完成:var prototype = new Object(SiteMember.prototype); prototype.constructor = Guest; Guest.prototype =原型;

– Dalibor
19年11月30日在23:59



#8 楼

有时您无法使用NEW创建对象,但仍然可以调用CREATE方法。例如,如果要定义自定义元素,则必须从HTMLElement派生。

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )


#9 楼

优点是Object.create在大多数浏览器中通常比new慢。在此jsperf示例中,在Chromium中,浏览器new的速度是Object.create(obj)的30倍,尽管两者都相当快。这一切都是很奇怪的,因为new要做更多的事情(例如调用构造函数),其中Object.create应该只是创建一个新对象,并将传入的对象作为原型(Crockford说的秘密链接)

也许浏览器没有赶上Object.create的更高效率(也许它们是基于new的底线...甚至是本机代码)

评论


那个jsperf似乎不见了。

–UpTheCreek
2014年9月21日15:14

#10 楼

摘要:Object.create()是一个Javascript函数,它带有2个参数并返回一个新对象。
第一个参数是一个对象,它将是新创建的对象的原型
第二个参数是一个对象,它将是新创建的对象的属性

示例:




 const proto = {
  talk : () => console.log('hi')
}

const props = {
  age: {
    writable: true,
    configurable: true,
    value: 26
  }
}


let Person = Object.create(proto, props)

console.log(Person.age);
Person.talk(); 





实际应用:


以这种方式创建对象的主要优点是可以显式定义原型。使用对象文字或new关键字时,您无法对此进行控制(但是,您当然可以覆盖它们)。
如果我们想拥有原型,则new关键字会调用构造函数。使用Object.create()不需要调用甚至声明构造函数。
当您想以一种非常动态的方式创建对象时,它基本上是一个有用的工具。我们可以使对象工厂函数根据接收到的参数创建具有不同原型的对象。


#11 楼

您必须制作一个自定义的Object.create()函数。一个可以解决Crockfords问题的方法,也可以调用您的init函数。

这将起作用:根据我们的需求进行调整。

如果需要,您也可以致电:

var userBPrototype = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};


function UserB(name) {
    function F() {};
    F.prototype = userBPrototype;
    var f = new F;
    f.init(name);
    return f;
}

var bob = UserB('bob');
bob.sayHello();


评论


为什么不让UserB说var f = Object.create(userPrototype); f.init(名称);返回f; ?

–丹尼尔(Daniel Earwicker)
2011年7月27日在9:14

初始化可能被调用多次。没有什么可以阻止的。

–寡核苷酸
13年10月31日在13:55

#12 楼

道格拉斯·克罗克福德(Douglas Crockford)过去一直是Object.create()的热心拥护者,而他基本上是该构造实际上是在javascript中使用的原因,但他不再有这种看法。

他停止使用Object.create。 ,因为他完全停止使用此关键字,因为这会带来太多麻烦。例如,如果您不小心,它很容易指向全局对象,这可能会带来严重的后果。他声称不使用Object.create就不再有意义。

您可以查看2014年的这段视频,他在Nordic.js上发表演讲:

https:/ /www.youtube.com/watch?v=PSGEjv3Tqo0



#13 楼

newObject.create具有不同的用途。 new旨在创建对象类型的新实例。 Object.create旨在简单地创建一个新对象并设置其原型。为什么这有用?在不访问__proto__属性的情况下实现继承。对象实例的原型称为[[Prototype]]是虚拟机的内部属性,不能直接访问。实际上可以直接访问[[Prototype]]作为__proto__属性的唯一原因是,它一直是每个主要虚拟机的ECMAScript实现的事实上的标准,并且在这一点上删除它会破坏很多现有代码。 br />
为了回答7ochem的上述回答,对象绝对不应该将其原型设置为new语句的结果,这不仅是因为没有必要多次调用同一原型构造函数,而且还因为两个实例如果在创建原型后对其原型进行修改,则同一类的同一类可能会出现不同的行为。由于误解和破坏了原型继承链的预期行为,这两个示例都只是错误的代码。之后使用__proto__并使用Object.createObject.setPrototypeOf进行读取。

此外,正如Object.setPrototypeOf的Mozilla文档指出的那样,在创建对象原型以提高性能后修改它是一个坏主意原因除了在创建对象后修改对象的原型可能导致未定义行为的事实之外,如果可以在修改原型后进行OR之前执行访问对象的给定代码段,除非该代码非常仔细地检查当前原型或无法访问两者之间任何不同的属性。

给出Object.getPrototypeOf
以下VM伪代码等效于语句Object.isPrototypeOfconst X = function (v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;
请注意,尽管构造函数可以返回任何值,但const x0 = new X(1);语句始终忽略其返回值并返回对新创建对象的引用。以下伪代码等效于const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);语句:new
如您所见,两者之间的唯一区别是const x1 = Object.create(X.prototype);不执行构造函数,该构造函数实际上可以返回任何值,而只是返回新对象引用现在,如果我们要创建具有以下定义的子类Y:const x0 = {}; x0.[[Prototype]] = X.prototype;
那么我们可以通过写Object.create使它像X这样继承:
尽管无需写以下命令即可完成同样的事情:this
在后一种情况下,必须设置原型的构造函数属性,以便q431调用正确的构造函数2079q语句,否则const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;将调用函数__proto__。如果程序员确实希望Y.prototype.__proto__ = X.prototype;调用__proto__,那么在Y的构造函数中使用Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;可以更正确地完成

#14 楼

我更喜欢使用封闭方法。

我仍然使用new
我不使用Object.create
我不使用this

我仍然喜欢使用new,因为我喜欢它的声明性。

请考虑将其用于简单继承。

评论


这很有趣。您可以扩展该示例以显示如何实现setter吗?还是Object.freeze()可以防止这种情况?

– jk7
19年4月3日在17:42

冻结只是停止“类”的运行时修改。我用闭包中的局部变量以及getter和setter扩展了父级的示例,以显示在闭包中管理“私有”变量

–p0wdr.com
19年4月4日在21:14

#15 楼


new运算符


用于从构造函数创建对象
new关键字也执行构造函数

function Car() {
  console.log(this) // this points to myCar
  this.name = "Honda";
}

var myCar = new Car()
console.log(myCar) // Car {name: "Honda", constructor: Object}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // true
console.log(myCar.constructor) // function Car() {}
console.log(myCar.constructor === Car) // true
console.log(typeof myCar) // object



Object.create


您还可以使用Object.create创建新对象
,但它不执行构造函数

Object.create用于从另一个对象创建对象

const Car = {
  name: "Honda"
}

var myCar = Object.create(Car)
console.log(myCar) // Object {}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // ERROR
console.log(myCar.constructor) // Anonymous function object
console.log(myCar.constructor === Car) // false
console.log(typeof myCar) // object



评论


好的收获-有一个错字。立即修复。

– Shardul
5月11日23:00