使用“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方法应该是公有的,或者甚至是存在的,如果你在现有的对象实例上再次调用这个方法, 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方法使用的语法类似的语法。

    它可以让你设置属性的属性( enumerablewritable或可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中,浏览器newObject.create(obj) 快了30倍,虽然两者相当快。 这是非常奇怪的,因为新做更多的事情(如调用构造函数),其中Object.create应该只是创建一个新的对象与传入的对象作为原型(克罗克福德说话的秘密链接)

    也许浏览器还没有赶上使Object.create更有效率(也许他们是基于new的底层…即使在本机代码)

    我认为主要的问题是要了解newObject.create方法的区别。 根据这个答案和这个视频 new关键字做下一件事情:

    1. 创建新的对象。

    2. 将新对象链接到构造函数( prototype )。

    3. 使this变量指向新的对象。

    4. 使用新对象执行构造函数并隐式执行return this ;

    5. 将构造函数名称分配给新对象的属性constructor

    Object.create只执行1st2nd

    在提供的代码示例中,这不是什么大问题,但在下一个示例中是:

     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' ]