Using “Object.create” instead of “new”

2018-12-31 09:00发布

Javascript 1.9.3 / ECMAScript 5 introduces Object.create, which Douglas Crockford amongst others has been advocating for a long time. How do I replace new in the code below with 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();

(Assume MY_GLOBAL.nextId exists).

The best I can come up with is:

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

There doesn't seem to be any advantage, so I think I'm not getting it. I'm probably being too neo-classical. How should I use Object.create to create user 'bob'?

14条回答
初与友歌
2楼-- · 2018-12-31 09:24

You could make the init method return this, and then chain the calls together, like 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');
查看更多
冷夜・残月
3楼-- · 2018-12-31 09:26

You have to make a custom Object.create() function. One that addresses Crockfords concerns and also calls your init function.

This will work:

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

Here UserB is like Object.create, but adjusted for our needs.

If you want, you can also call:

var bob = new UserB('bob');
查看更多
回忆,回不去的记忆
4楼-- · 2018-12-31 09:26

new and Object.create serve different purposes. new is intended to create a new instance of an object type. Object.create is intended to simply create a new object and set its prototype. Why is this useful? To implement inheritance without accessing the __proto__ property. An object instance's prototype referred to as [[Prototype]] is an internal property of the virtual machine and is not intended to be directly accessed. The only reason it is actually possible to directly access [[Prototype]] as the __proto__ property is because it has always been a de-facto standard of every major virtual machine's implementation of ECMAScript, and at this point removing it would break a lot of existing code.

In response to the answer above by 7ochem, objects should absolutely never have their prototype set to the result of a new statement, not only because there's no point calling the same prototype constructor multiple times but also because two instances of the same class can end up with different behavior if one's prototype is modified after being created. Both examples are simply bad code as a result of misunderstanding and breaking the intended behavior of the prototype inheritance chain.

Instead of accessing __proto__, an instance's prototype should be written to when an it is created with Object.create or afterward with Object.setPrototypeOf, and read with Object.getPrototypeOf or Object.isPrototypeOf.

Also, as the Mozilla documentation of Object.setPrototypeOf points out, it is a bad idea to modify the prototype of an object after it is created for performance reasons, in addition to the fact that modifying an object's prototype after it is created can cause undefined behavior if a given piece of code that accesses it can be executed before OR after the prototype is modified, unless that code is very careful to check the current prototype or not access any property that differs between the two.

Given

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

the following VM pseudo-code is equivalent to the statement const x0 = new X(1);:

const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

Note although the constructor can return any value, the new statement always ignores its return value and returns a reference to the newly created object.

And the following pseudo-code is equivalent to the statement const x1 = Object.create(X.prototype);:

const x0 = {}; x0.[[Prototype]] = X.prototype;

As you can see, the only difference between the two is that Object.create does not execute the constructor, which can actually return any value but simply returns the new object reference this if not otherwise specified.

Now, if we wanted to create a subclass Y with the following definition:

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

Then we can make it inherit from X like this by writing to __proto__:

Y.prototype.__proto__ = X.prototype;

While the same thing could be accomplished without ever writing to __proto__ with:

Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;

In the latter case, it is necessary to set the constructor property of the prototype so that the correct constructor is called by the new Y statement, otherwise new Y will call the function X. If the programmer does want new Y to call X, it would be more properly done in Y's constructor with X.call(this, u)

查看更多
泪湿衣
5楼-- · 2018-12-31 09:34

TL;DR:

new Computer() will invoke the constructor function Computer(){} for one time, while Object.create(Computer.prototype) won't.

All the advantages are based on this point.

Though for some inside-engine-optimization reasons, Object.create may be slower, which is not intuitive.

查看更多
浪荡孟婆
6楼-- · 2018-12-31 09:36

Sometimes you cannot create an object with NEW but are still able to invoke the CREATE method.

For example: if you want to define a Custom Element it must derive from HTMLElement.

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )
查看更多
君临天下
7楼-- · 2018-12-31 09:38

With only one level of inheritance, your example may not let you see the real benefits of Object.create.

This methods allows you to easily implement differential inheritance, where objects can directly inherit from other objects.

On your userB example, I don't think that your init method should be public or even exist, if you call again this method on an existing object instance, the id and name properties will change.

Object.create lets you initialize object properties using its second argument, e.g.:

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

As you can see, the properties can be initialized on the second argument of Object.create, with an object literal using a syntax similar to the used by the Object.defineProperties and Object.defineProperty methods.

It lets you set the property attributes (enumerable, writable, or configurable), which can be really useful.

查看更多
登录 后发表回答