Using “Object.create” instead of “new”
Javascript 1.9.3 / ECMAScript 5介绍了
1 2 3 4 5 6 7 8 9 | 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(); |
(假设存在
我能想到的最好的是:
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); |
似乎没有任何优势,所以我想我没有得到它。 我可能太新古典了。 我应该如何使用
只有一个级别的继承,您的示例可能无法让您看到
此方法允许您轻松实现差异继承,其中对象可以直接从其他对象继承。
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 } }); |
如您所见,属性可以在
它允许您设置属性属性(
使用
那些提倡这种方法的人通常会说出相当模糊的优点:"可扩展性",或"JavaScript更自然"等。
但是,我还没有看到一个具体的例子,表明
1 2 3 4 5 6 7 8 | 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!!! |
发生这种情况是因为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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还不是几个浏览器的标准,例如IE8,Opera v11.5,Konq 4.3没有它。您可以为这些浏览器使用Douglas Crockford的Object.create版本,但这不包括CMS答案中使用的第二个"初始化对象"参数。
对于跨浏览器代码,在此期间获得对象初始化的一种方法是定制Crockford的Object.create。这是一种方法: -
1 2 3 4 5 6 7 8 9 10 | 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')。你的代码变成: -
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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: -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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过滤器,有时即使不使用。
1 2 3 4 5 6 7 8 9 10 11 | 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:
所有优点都基于这一点。
关于性能的旁注:构造函数调用如
您可以使
1 2 3 4 5 6 7 8 9 10 11 12 | 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的另一个可能用法是以便宜有效的方式克隆不可变对象。
1 2 3 4 5 6 7 8 9 10 11 | 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功能的浏览器)。
我认为主要问题是理解
创建新对象。
将新对象链接到构造函数(
使
使用新对象执行构造函数并隐式执行
将构造函数名称分配给新对象的属性
在提供的代码示例中,这不是什么大问题,但在下一个示例中它是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); |
副作用结果将是:
1 | [ undefined, 'James' ] |
因为
但是我们不需要执行父构造函数方法,我们只需要让方法
因此我们必须使用
如果替换
到
1 | [ 'James' ] |
有时您无法使用NEW创建对象,但仍然可以调用CREATE方法。
例如:如果要定义自定义元素,则必须从HTMLElement派生。
1 2 3 | proto = new HTMLElement //fail :( proto = Object.create( HTMLElement.prototype ) //OK :) document.registerElement("custom-element", { prototype: proto } ) |
优点是在大多数浏览器中
在这个jsperf示例中,在Chromium中,浏览器
也许浏览器还没有提高
在回应上面7ochem的回答时,对象绝对不应该将它们的原型设置为
不是访问
另外,正如Object.setPrototypeOf的Mozilla文档所指出的那样,在创建对象的原型之后,出于性能原因修改对象的原型是一个坏主意,此外还有一个事实是在创建对象的原型之后修改对象的原型会导致未定义如果访问它的给定代码片段可以在修改原型之后的OR之前执行,除非该代码非常小心地检查当前原型或不访问两者之间不同的任何属性。
特定
X.prototype.whatAmI = 'X';
X.prototype.getWhatIAm = () => this.whatAmI;
X.prototype.getV = () => this.v;
以下VM伪代码等同于语句
x0.[[Prototype]] = X.prototype;
X.prototype.constructor.call(x0, 1);
请注意,虽然构造函数可以返回任何值,但
以下伪代码等同于语句
x0.[[Prototype]] = X.prototype;
正如您所看到的,两者之间的唯一区别是
现在,如果我们想要使用以下定义创建子类Y:
Y.prototype.whatAmI = 'Y';
Y.prototype.getU = () => this.u;
然后我们可以通过写入
虽然可以在不写入
Y.prototype.constructor = Y;
在后一种情况下,有必要设置原型的构造函数属性,以便
虽然Douglas Crockford曾经是Object.create()的热心倡导者,但他基本上就是这个结构实际上是javascript的原因,他不再有这个观点。
他停止使用Object.create,因为他完全停止使用这个关键字,因为它会造成太多麻烦。例如,如果你不小心它可以很容易地指向全局对象,这可能会产生非常糟糕的后果。他声称不使用这个Object.create就没有意义了。
您可以查看2014年他在Nordic.js会谈的视频:
您必须创建自定义
这将有效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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,但根据我们的需要进行了调整。
如果需要,您也可以致电:
1 | var bob = new UserB('bob'); |
摘要:
-
Object.create() 是一个Javascript函数,它接受2个参数并返回一个新对象。 - 第一个参数是一个对象,它将成为新创建的对象的原型
- 第二个参数是一个对象,它将是新创建的对象的属性
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
实际应用:
我更喜欢封闭式方法。
我仍然使用
我不使用
我不使用
我仍然使用
考虑这个简单的继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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 |
鼓励反馈。