Javascript 1.9.3/ECMAScript 5介绍Object.create
道格拉斯·克罗克福德等人长期以来一直在倡导.如何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();
似乎没有任何优势,所以我想我没有得到它.我可能过于新古典主义了.我应该如何使用MY_GLOBAL.nextId
创建用户'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(...)
结束确实没有优势new object
.
那些提倡这种方法的人通常会说出相当模糊的优点:"可扩展性",或" JavaScript更自然 "等.
但是,我还没有看到一个具体的例子,表明它比使用Object.create
有任何优势new
.相反,它存在已知的问题.Sam Elsamman描述了在Object.create(...)
使用嵌套对象时会发生什么:
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()
可能会更慢,这是不直观的.
您可以使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的另一个可能用法是以便宜有效的方式克隆不可变对象.
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
方法之间的区别.根据这个答案和这个视频 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();
但我们不需要执行父构造函数方法,我们只需要getName
在Guest中使用make方法.因此我们必须使用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 } )