当前位置:  开发笔记 > 编程语言 > 正文

使用"Object.create"而不是"new"

如何解决《使用"Object.create"而不是"new"》经验,为你挑选了8个好方法。

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



1> CMS..:

只有一个级别的继承,你的例子可能不会让你看到真正的好处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方法使用的语法.

它可以让你设置属性的属性(enumerable,writable,或configurable),它可以是非常有用的.


1.感谢指向差异继承的指针.这是否意味着没有更多的构造函数?我需要记得每次创建用户时都将'id'设置为MY_GLOBAL.nextId()吗?
Re:没有更多的构造函数:通常你会编写一个普通函数作为对象的"工厂".在内部,它将使用`Object.create`来创建一个空白对象,然后在返回之前根据需要进行修改.该工厂的调用者不必记住前缀`new`.
你很受欢迎@Graham,你是对的,不再需要这种方法的构造函数,尽管目前可用的Firefox 3.7apre5实现,最新的WebKit Nightly版本和Chrome 5 Beta,[不那么快](http:/ /webreflection.blogspot.com/2010/03/new-constructor-vs-objectcreate.html)与普通的旧构造函数相比,希望这在不久的将来会发生变化.对于对象创建,您可以创建一个*factory*函数(即`function createUser(name){...}`,其中包含使用`Object.create`创建用户对象所需的所有逻辑.
@GrahamKing你可以用一个闭包来初始化你的对象:http://jsfiddle.net/Prqdt/

2> Noel Abraham..:

使用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基准变的原型的部分lionbird,并且当它是共享的会引起问题.使用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的参数应该是原型,就不应该出现这个问题.显然,如果你说Animal.prototype.traits = {},你在使用**new**时会遇到同样的不良行为; 明确你不应该这样做的唯一原因是你了解javascript原型是如何工作的.
我不同意.正如链接文章所暗示的那样,`Object.create`既不强制也不鼓励将原型用作任何类型的"默认数据值存储".正确的数据初始化是创建特定对象(如OO设计中的工厂或构建器)的责任.*(在JS中继承数据而不是行为是可行的,但不是常见的情况.)*
天哪!如此多的downvotes提供了正确的答案:-)重点是Object.create不允许构造函数参数的机制,所以强制扩展"数据".现在这个数据可能包含嵌套对象,这导致了上面的问题.另一方面,如果我们要_explicitly_写出`Animal.prototype.traits = {};`,那么我们只会遇到原型继承问题.一种方法隐含另一种方法.不要选择导致问题的那个.
我建议读凯尔辛普森的这篇文章.这三个部分都很有趣,但第3部分是关键.如果在读完之后你仍然认为"新"比Object.create()更好,那么你就没有希望了!:) http://davidwalsh.name/javascript-objects-deconstruction
[请参阅这篇文章](http://stackoverflow.com/questions/3191103/javascript-object-create-inheriting-nested-properties)以获得2腿狮子的简单解决方案.[这里有一些工作代码来说明它](http://jsfiddle.net/d131/a6xhu/)
如果您使用Object.create并对原型有正确的概念理解,那么您会说"当然它不会那样工作!共享原型是一个功能,而不是一个错误!"

3> 小智..:

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也不是一个特定的构造函数,但看起来像一个普通的单例对象.因此,使用此方法,您可以从普通的普通对象构造和初始化.


在ES5 Shim中有一个用于`Object.create`的polyfill https://github.com/kriskowal/es5-shim

4> Nami WANG..:

TL; DR:

new Computer()将调用构造函数Computer(){}一次,而Object.create(Computer.prototype)不会.

所有优点都基于这一点.

虽然对于一些内部引擎优化的原因,new Computer()可能会更慢,这是不直观的.



5> samfrances..:

您可以使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');



6> basos..:

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功能的浏览器).


调用这个克隆对我来说很困惑(可能还有很多其他人).对于大多数人来说,`clone`方法意味着对原始文件的更改不会影响克隆.

7> Vladimir Kov..:

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

    创建新对象.

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

    使this变量指向新对象.

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

    将构造函数名称分配给新对象的属性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();
但我们不需要执行父构造函数方法,我们只需要getName在Guest中使用make方法.因此我们必须使用Object.create.
如果替换Guest.prototype = new SiteMember();
Guest.prototype = Object.create(SiteMember.prototype);结果:

[ 'James' ]



8> Supersharp..:

有时您无法使用NEW创建对象,但仍然可以调用CREATE方法.

例如:如果要定义自定义元素,则必须从HTMLElement派生.

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )

推荐阅读
pan2502851807
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有