Javascript 1.9.3 / ECMAScript 5介绍了Object.create
,Douglas Crockford等人长期以来一直是advocating。如何用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'?
只有一个级别的继承,你的例子可能不会让你看到Object.create
的真正好处。
此方法允许您轻松实现差异继承,其中对象可以直接从其他对象继承。
在你的userB
例子中,我不认为你的init
方法应该公开或甚至存在,如果你再次在现有的对象实例上调用这个方法,id
和name
属性将会改变。
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.defineProperties
和Object.defineProperty
方法使用的语法。
它允许您设置属性属性(enumerable
,writable
或configurable
),这可能非常有用。
你必须制作一个自定义的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就像Object.create,但根据我们的需要进行了调整。
如果需要,您也可以致电:
var bob = new UserB('bob');
虽然Douglas Crockford曾经是Object.create()的热心倡导者,但他基本上就是这个结构实际上是javascript的原因,他不再有这个观点。
他停止使用Object.create,因为他完全停止使用这个关键字,因为它会造成太多麻烦。例如,如果你不小心它可以很容易地指向全局对象,这可能会产生非常糟糕的后果。他声称不使用这个Object.create就没有意义了。
您可以查看2014年他在Nordic.js会谈的视频:
new
和Object.create
服务于不同的目的。 new
旨在创建对象类型的新实例。 Object.create
旨在简单地创建一个新对象并设置其原型。为什么这有用?在不访问__proto__
属性的情况下实现继承。对象实例的原型称为[[Prototype]]
,它是虚拟机的内部属性,不能直接访问。实际上可以直接访问[[Prototype]]
作为__proto__
属性的唯一原因是因为它一直是每个主要虚拟机实现ECMAScript的事实上的标准,并且此时删除它会破坏许多现有代码。
为了回应上面7ochem的回答,对象绝对不应该将它们的原型设置为new
语句的结果,这不仅是因为没有多少时间调用相同的原型构造函数,而且因为同一类的两个实例可能最终如果在创建后修改了一个原型,则具有不同的行为。由于误解和打破原型继承链的预期行为,这两个示例都只是错误的代码。
不是访问__proto__
,而是应该使用Object.create
或之后使用Object.setPrototypeOf
创建实例的原型,并使用Object.getPrototypeOf
或Object.isPrototypeOf
读取。
另外,正如Mozilla documentation of Object.setPrototypeOf指出的那样,在出于性能原因创建对象的原型之后修改对象的原型是一个坏主意,此外还有一个事实是在创建对象的原型之后修改对象的原型可能会导致一个给定的部分未定义的行为访问它的代码可以在原型被修改之后的OR之前执行,除非该代码非常小心地检查当前原型或不访问两者之间不同的任何属性。
特定
const X = function (v) { this.v = v };
X.prototype.whatAmI = 'X';
X.prototype.getWhatIAm = () => this.whatAmI;
X.prototype.getV = () => this.v;
以下VM伪代码等同于语句const x0 = new X(1);
:
const x0 = {};
x0.[[Prototype]] = X.prototype;
X.prototype.constructor.call(x0, 1);
请注意,虽然构造函数可以返回任何值,但new
语句始终忽略其返回值并返回对新创建的对象的引用。
以下伪代码相当于语句const x1 = Object.create(X.prototype);
:
const x0 = {};
x0.[[Prototype]] = X.prototype;
正如您所看到的,两者之间的唯一区别是Object.create
不执行构造函数,它实际上可以返回任何值,但如果没有另外指定则只返回新的对象引用this
。
现在,如果我们想要使用以下定义创建子类Y:
const Y = function(u) { this.u = u; }
Y.prototype.whatAmI = 'Y';
Y.prototype.getU = () => this.u;
然后我们可以通过写入__proto__
使它从X继承:
Y.prototype.__proto__ = X.prototype;
虽然可以在不写__proto__
的情况下完成同样的事情:
Y.prototype = Object.create(X.prototype);
Y.prototype.constructor = Y;
在后一种情况下,有必要设置原型的构造函数属性,以便new Y
语句调用正确的构造函数,否则new Y
将调用函数X
。如果程序员确实希望new Y
调用X
,那么在Y的构造函数中使用X.call(this, u)
会更合适
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()
,不需要调用甚至声明构造函数。我更喜欢封闭式方法。
我仍然使用new
。我不使用Object.create
。我不使用this
。
我仍然使用new
,因为我喜欢它的声明性质。
考虑这个简单的继承。
window.Quad = (function() {
function Quad() {
const wheels = 4;
const drivingWheels = 2;
let motorSize = 0;
function setMotorSize(_) {
motorSize = _;
}
function getMotorSize() {
return motorSize;
}
function getWheelCount() {
return wheels;
}
function getDrivingWheelCount() {
return drivingWheels;
}
return Object.freeze({
getWheelCount,
getDrivingWheelCount,
getMotorSize,
setMotorSize
});
}
return Object.freeze(Quad);
})();
window.Car4wd = (function() {
function Car4wd() {
const quad = new Quad();
const spareWheels = 1;
const extraDrivingWheels = 2;
function getSpareWheelCount() {
return spareWheels;
}
function getDrivingWheelCount() {
return quad.getDrivingWheelCount() + extraDrivingWheels;
}
return Object.freeze(Object.assign({}, quad, {
getSpareWheelCount,
getDrivingWheelCount
}));
}
return Object.freeze(Car4wd);
})();
let myQuad = new Quad();
let myCar = new Car4wd();
console.log(myQuad.getWheelCount()); // 4
console.log(myQuad.getDrivingWheelCount()); // 2
console.log(myCar.getWheelCount()); // 4
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
console.log(myCar.getSpareWheelCount()); // 1
鼓励反馈。
使用Object.create(...)
而不是new object
确实没有优势。
那些提倡这种方法的人通常会说出相当模糊的优点:"scalability",或“more natural to JavaScript”等。
但是,我还没有看到一个具体的例子,表明Object.create
比使用new
有任何优势。相反,它存在已知的问题。 Sam Elsamman describes what happens when there are nested objects and Object.create(...)
is used:
var Animal = {
traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!
发生这种情况是因为Object.create(...)
主张使用数据创建新对象的做法;在这里,Animal
数据成为lion
和bird
原型的一部分,并在共享时引起问题。使用new时,原型继承是明确的:
function Animal() {
this.traits = {};
}
function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();
var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4
关于传递给Object.create(...)
的可选属性属性,可以使用Object.defineProperties(...)
添加这些属性。
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方法,然后使用您的参数运行它,比如说new man('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更简单的初始化方法,特别是如果您不关心可写,可枚举和可配置的属性。
对于没有init方法的初始化,可以使用以下Crockford mod: -
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参数之后使用Object.gen参数从左到右填充userB自己的属性。它使用for(prop in o)循环,因此,根据ECMA标准,属性枚举的顺序不能保证与属性定义的顺序相同。但是,在(4)主要浏览器上测试的几个代码示例显示它们是相同的,只要使用hasOwnProperty过滤器,有时即使不使用。
MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}; // For example
var userB = {
name: null,
id: null,
sayHello: function() {
console.log('Hello '+ this.name);
}
}
var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());
我会说比Object.build更简单,因为userB不需要init方法。 userB也不是一个特定的构造函数,但看起来像一个普通的单例对象。因此,使用此方法,您可以从普通的普通对象构造和初始化。
TL; DR:
new Computer()
将调用构造函数Computer(){}
一次,而Object.create(Computer.prototype)
则不会。
所有优点都基于这一点。
关于性能的旁注:像new Computer()
这样的构造函数调用由引擎进行了大量优化,因此它可能比Object.create
更快。
您可以使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');
Object.create的另一种可能用法是在cheap and effective way中克隆不可变对象。
var anObj = {
a: "test",
b: "jest"
};
var bObj = Object.create(anObj);
bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj
// now bObj is {a: test, b: gone, c: brand}
注意:上面的代码片段创建了一个源对象的克隆(也就是cObj = aObj中的引用,而不是引用)。它比copy-properties方法(参见1)更有优势,因为它不会复制对象成员属性。相反,它创建了另一个-destination-对象,并在源对象上设置了原型。此外,当在dest对象上修改属性时,它们是“动态”创建的,掩盖了原型(src)的属性。这构成了克隆不可变对象的快速有效方法。
这里需要注意的是,这适用于创建后不应修改的源对象(不可变)。如果在创建后修改了源对象,则也将修改所有克隆的未屏蔽属性。
这里小提琴(http://jsfiddle.net/y5b5q/1/)(需要具有Object.create功能的浏览器)。
我认为主要问题是了解new
和Object.create
方法之间的区别。相应于this answer和this video new
关键字做下一件事:
prototype
)。this
变量指向新对象。return this
;constructor
。Object.create
只执行1st
和2nd
步骤!
在提供的代码示例中,这不是什么大问题,但在下一个示例中它是:
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' ]
因为Guest.prototype = new SiteMember();
但是我们不需要执行父构造函数方法,我们只需要在Guest中使用make方法getName
。因此我们必须使用Object.create
。
如果更换Guest.prototype = new SiteMember();
到Guest.prototype = Object.create(SiteMember.prototype);
的结果是:
[ 'James' ]
有时您无法使用NEW创建对象,但仍然可以调用CREATE方法。
例如:如果要定义自定义元素,则必须从HTMLElement派生。
proto = new HTMLElement //fail :(
proto = Object.create( HTMLElement.prototype ) //OK :)
document.registerElement( "custom-element", { prototype: proto } )
优点是Object.create
在大多数浏览器上通常比new
慢
In this jsperf example,在Chromium中,浏览器new
的速度是Object.create(obj)
的30倍,尽管两者都非常快。这一切都很奇怪,因为new会做更多事情(比如调用构造函数),其中Object.create应该只是创建一个新的Object,传入的对象作为原型(Crockford中的秘密链接)
也许浏览器没有赶上使Object.create
更有效率(也许他们的基础是new
在封面......甚至在本机代码中)