使用“Object.create”而不是“new”

时间:2010-04-25 19:36:08

标签: javascript constructor new-operator object-create

Javascript 1.9.3 / ECMAScript 5介绍了Object.create,Douglas Crockford等人长期以来一直advocating。如何使用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();

似乎没有任何优势,所以我想我没有得到它。我可能太新古典了。我应该如何使用Object.create创建用户'bob'?

15 个答案:

答案 0 :(得分:238)

只有一个级别的继承,您的示例可能无法让您看到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.definePropertiesObject.defineProperty方法类似的语法使用对象文字。

它允许您设置属性属性(enumerablewritableconfigurable),这可能非常有用。

答案 1 :(得分:50)

使用Object.create(...)而不是new object确实没有优势。

倡导这种方法的人通常会说出相当模糊的优点:"scalability"或“more natural to JavaScript”等。

但是,我还没有看到一个具体示例,表明Object.create比使用new具有任何优势。相反,它存在已知的问题。 Sam Elsamman describes what happens when there are nested objects and Object.create(...) is used

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(...)提倡使用 data 创建新对象的做法;这里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(...)添加这些属性。

答案 2 :(得分:41)

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

答案 3 :(得分:20)

TL; DR:

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

所有优势都基于这一点。

虽然由于某些内部引擎优化原因,Object.create可能会更慢,但这并不直观。

答案 4 :(得分:13)

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

答案 5 :(得分:7)

Object.create的另一个可能用途是克隆cheap and effective way中的不可变对象。

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

答案 6 :(得分:6)

我认为有问题的主要观点是了解newObject.create方法之间的区别。相应于this answerthis video new关键字会做下一件事:

  1. 创建新对象。

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

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

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

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

  6. 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中可用。 因此我们必须使用Object.create
    如果替换Guest.prototype = new SiteMember();
    Guest.prototype = Object.create(SiteMember.prototype);结果为:

    [ 'James' ]
    

答案 7 :(得分:5)

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

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

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

答案 8 :(得分:3)

优点是Object.create通常比大多数浏览器上的new

In this jsperf example,在Chromium中,浏览器new <{>> <{1>} <30>虽然两者都非常快。这一切都很奇怪,因为new会做更多的事情(比如调用构造函数),其中Object.create应该只是创建一个新的Object,传入的对象作为原型(Crockford中的秘密链接)

也许浏览器还没有让Object.create(obj)更有效率(也许它们基于Object.create封底......甚至在本机代码中)

答案 9 :(得分:2)

您必须制作自定义Object.create()功能。一个解决Crockfords问题,也调用你的init函数。

这将有效:

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');

答案 10 :(得分:2)

虽然Douglas Crockford曾经是Object.create()的热心倡导者,但他基本上就是这个结构实际上是javascript的原因,他不再有这个意见了。

他停止使用Object.create,因为他完全停止使用这个关键字,因为它会导致太多麻烦。例如,如果你不小心它可以很容易地指向全局对象,这可能会产生非常糟糕的后果。并且他声称不使用这个 Object.create就没有意义了。

您可以查看2014年他在Nordic.js会谈的视频:

https://www.youtube.com/watch?v=PSGEjv3Tqo0

enter image description here

答案 11 :(得分:2)

newObject.create有不同的用途。 new用于创建对象类型的新实例。 Object.create旨在简单地创建一个新对象并设置其原型。为什么这有用?在不访问__proto__属性的情况下实现继承。对象实例的原型称为[[Prototype]]是虚拟机的内部属性,不能直接访问。实际上可以直接访问[[Prototype]]作为__proto__属性的唯一原因是因为它一直是每个主要虚拟机实现ECMAScript的实际标准,因此将其删除破坏很多现有代码。

为响应7ochem的上述回答,对象绝对不应将其原型设置为new语句的结果,这不仅是因为没有必要多次调用同一个原型构造函数,而且还因为两个实例如果在创建原型后对其原型进行修改,则同一类的同一类可能会出现不同的行为。由于误解和破坏了原型继承链的预期行为,两个示例都只是错误的代码。

当使用__proto__创建实例原型时,而不是访问Object.create时,应使用实例实例的原型,然后使用Object.setPrototypeOf或{{ 1}}。

此外,正如Mozilla documentation of Object.setPrototypeOf所指出的那样,出于性能原因,在创建对象原型后修改对象原型也是一个坏主意,除了事实上,在创建对象之后修改对象原型也可以如果给定的一段代码可以在修改原型之前或之后执行,否则将导致未定义的行为,除非该代码非常小心地检查当前原型或不访问两者之间的任何不同属性。

给定

Object.getPrototypeOf

以下VM伪代码等效于语句Object.isPrototypeOf

const X = function (v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;

请注意,尽管构造函数可以返回任何值,但是const x0 = new X(1);语句始终忽略其返回值,并返回对新创建对象的引用。

并且以下伪代码等效于语句const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

new

如您所见,两者之间的唯一区别是const x1 = Object.create(X.prototype);不执行构造函数,该构造函数实际上可以返回任何值,而如果没有另外指定,则仅返回新对象引用const x0 = {}; x0.[[Prototype]] = X.prototype;

现在,如果我们要创建具有以下定义的子类Y:

Object.create

然后,我们可以通过写入this使它像这样从X继承:

const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;

尽管无需使用以下任何方法写给__proto__即可完成同一件事:

Y.prototype.__proto__ = X.prototype;

在后一种情况下,必须设置原型的构造函数属性,以便通过__proto__语句调用正确的构造函数,否则Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;将调用函数new Y。如果程序员确实希望new Y调用X,那么在Y的构造函数中使用new Y

会更合适。

答案 12 :(得分:2)

new运算符

  • 这用于从构造函数创建对象
  • new关键字还执行构造函数
function Car() {
  console.log(this) // this points to myCar
  this.name = "Honda";
}

var myCar = new Car()
console.log(myCar) // Car {name: "Honda", constructor: Object}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // true
console.log(myCar.constructor) // function Car() {}
console.log(myCar.constructor === Car) // true
console.log(typeof myCar) // object

Object.create

  • 您还可以使用Object.create创建一个新对象
  • 但是,它不执行构造函数
  • Object.create用于从另一个对象创建一个对象
const Car = {
  name: "Honda"
}

var myCar = Object.create(Car)
console.log(myCar) // Object {}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // ERROR
console.log(myCar.constructor) // Anonymous function object
console.log(myCar.constructor === Car) // false
console.log(typeof myCar) // object

答案 13 :(得分:1)

我更喜欢封闭式方法。

我仍然使用new。 我不使用Object.create。 我不使用this

我仍然使用new因为我喜欢它的声明性质。

将此视为简单继承。

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

鼓励反馈。

答案 14 :(得分:1)

摘要:

  • Object.create()是一个Javascript函数,它带有2个参数并返回一个新对象。
  • 第一个参数是一个对象,它将是新创建的对象的原型
  • 第二个参数是一个对象,它将是新创建的对象的属性

示例:

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. 以这种方式创建对象的主要优点是可以显式定义原型。使用对象文字或new关键字时,您无法对此进行控制(但是,您当然可以覆盖它们)。
  2. 如果我们想拥有原型new关键字调用构造函数。使用Object.create()不需要调用甚至不需要声明构造函数。
  3. 当您想以一种非常动态的方式创建对象时,它基本上可以是一个有用的工具。我们可以创建一个对象工厂函数,根据收到的参数创建具有不同原型的对象。