使用“Object.create”而不是“新”
JavaScript 1.9.3 / ECMAScript 5引入了Object.create
,道格拉斯·克罗克福德(Douglas Crockford)等人长期以来一直主张 。 如何用Object.create
替换下面的代码中的new
?
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(…)上使用新对象确实没有什么优势。
那些提倡这种方法的人通常会说出一些比较模棱两可的优点: “可扩展性” ,或者“ 对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(…)提倡使用数据创建新对象的做法; 在这里,动物数据成为狮子和鸟类的原型的一部分,并在共享时引起问题。 当使用新的原型继承是明确的:
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方法,然后用你的参数运行它,就像说新人('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自己的属性,在userB参数之后使用从左到右的Object.gen参数。 它使用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)
则不会。
所有的优点都基于这一点。
尽管对于一些内部引擎优化的原因, 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的另一个可能用法是以廉价和有效的方式克隆不可变的对象。
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 ),因为它不复制对象成员属性。 相反,它会在源对象上创建另一个目标对象。 而且,当dest对象上的属性被修改时,它们被“即时”创建,掩盖了原型的(src)属性。这构成了克隆不可变对象的有效方法。
这里需要注意的是,这适用于创建之后不应该修改的源对象(不可变)。 如果源对象在创建之后被修改,则所有克隆的未被屏蔽的属性也将被修改。
小提琴在这里( http://jsfiddle.net/y5b5q/1/ )(需要Object.create功能的浏览器)。
有时你不能用NEW创建一个对象,但仍然能够调用CREATE方法。
例如:如果你想定义一个自定义元素,它必须来自HTMLElement。
proto = new HTMLElement //fail :( proto = Object.create( HTMLElement.prototype ) //OK :) document.registerElement( "custom-element", { prototype: proto } )
你必须做一个自定义的Object.create()
函数。 一个解决Crockfords关注,也称为你的初始化函数。
这将工作:
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');
好处是Object.create
在大多数浏览器上通常比new
要慢
在这个jsperf例子中 ,在Chromium中,浏览器new
比Object.create(obj)
快了30倍,虽然两者相当快。 这是非常奇怪的,因为新做更多的事情(如调用构造函数),其中Object.create应该只是创建一个新的对象与传入的对象作为原型(克罗克福德说话的秘密链接)
也许浏览器还没有赶上使Object.create
更有效率(也许他们是基于new
的底层…即使在本机代码)
我认为主要的问题是要了解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();
但是我们不需要执行父构造方法,我们只需要在Guest中使方法getName
可用。 因此我们必须使用Object.create
。
如果替换Guest.prototype = new SiteMember();
到Guest.prototype = Object.create(SiteMember.prototype);
结果是:
[ 'James' ]