In Javascript, the difference between 'Object.create' and 'new'

点点圈 提交于 2019-12-04 08:12:41

问题


I think the difference has clicked in my head, but I'd just like to be sure.

On the Douglas Crockford page Prototypal Inheritance in JavaScript, he says

In a prototypal system, objects inherit from objects. JavaScript, however, lacks an operator that performs that operation. Instead it has a new operator, such that new f() produces a new object that inherits from f.prototype.

I didn't really understand what he was trying to say in that sentence so I performed some tests. It seems to me that the key difference is that if I create an object based on another object in a pure prototypal system, then all the parent parent members should be on the prototype of the new object, not on the new object itself.

Here's the test:

var Person = function(name, age) {
        this.name = name;
        this.age = age;
}
Person.prototype.toString = function(){return this.name + ', ' + this.age};

// The old way...
var jim = new Person("Jim",13);
for (n in jim) {
    if (jim.hasOwnProperty(n)) {
        console.log(n);
     }
}
// This will output 'name' and 'age'.

// The pure way...
var tim = Object.create(new Person("Tim",14));
for (n in tim) {
    if (tim.hasOwnProperty(n)) {
        console.log(n);
     }
}
// This will output nothing because all the members belong to the prototype.
// If I remove the hasOwnProperty check then 'name' and 'age' will be output.

Is my understanding correct that the difference only becomes apparent when testing for members on the object itself?


回答1:


Your assumptions are correct, but there is another pattern that Douglas doesn't talk much about - the prototype can be used for properties as well. Your person class could have been written as:

var Person = function(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.name = null; //default value if you don't init in ctor
Person.prototype.age = null;
Person.prototype.gender = "male";
Person.prototype.toString = function(){return this.name + ', ' + this.age;};

In this case, iterating over properties of an instance of this class, as you do in your example, would generate no output for the 'gender' property.

EDIT 1: The assignment of name and age in the constructor do make the properties visible by hasOwnProperty (thanks @matt for reminding me of this). The unassigned gender property would not be visible until someone sets it on the instance.

EDIT 2: To further add to this, I present an alternative inheritance pattern - one that I have personally used for very large projects:

var inherits = function(childCtor, parentCtor) {
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.superclass = parentCtor.prototype; 
  childCtor.prototype = new tempCtor();
  childCtor.prototype.constructor = childCtor;
};

var Person = function(name){
    this.name = name;
}
Person.prototype.name = "";
Person.prototype.toString = function(){
    return "My name is " + this.name;
}

var OldPerson = function(name, age){
    OldPerson.superclass.constructor.call(this);
    this.age = age
};
inherits(OldPerson, Person);
OldPerson.prototype.age = 0;
OldPerson.prototype.toString = function(){
    var oldString =  OldPerson.superclass.toString.call(this);
    return oldString + " and my age is " + this.age;
}

This is a fairly common pattern with a small twist - the parent class is attached to the child via the "superclass" property permitting you to access methods/properties overridden by the child. Technically, you could replace OldPerson.superclass with Person, however that is not ideal. If you ever changed OldPerson to inherit from a class other than Person, you would have to update all references to Person as well.

EDIT 3: Just to bring this full circle, here is a version of the "inherits" function which takes advantage of Object.create and functions exactly the same as I previously described:

var inherits = function(childCtor, parentCtor) {
    childCtor.prototype = Object.create(parentCtor.prototype);
    childCtor.superclass = parentCtor.prototype; 
};



回答2:


EDIT: This answer was originally a response to @jordancpaul's answer, which he has since corrected. I will leave the portion of my answer that helps explain the important difference between prototype properties and instance properties:

In some cases, properties are shared between all instances and you need to be very careful whenever you're declaring properties on the prototype. Consider this example:

Person.prototype.favoriteColors = []; //Do not do this!

Now, if you create a new Person instance using either Object.create or new, it doesn't work as you might expect...

var jim = new Person("Jim",13);
jim.favoriteColors.push('red');
var tim = new Person("Tim",14);
tim.favoriteColors.push('blue');

console.log(tim.favoriteColors); //outputs an array containing red AND blue!

This doesn't mean you can't ever declare properties on the prototype, but if you do, you and every developer who works on your code needs to be aware of this pitfall. In a case like this, if you prefer declaring properties on the prototype for whatever reason, you could do:

Person.prototype.favoriteColors = null

And initialize it to an empty array in the constructor:

var Person = function(name, age) {
    ...
    this.favoriteColors = [];
}

The general rule when using this method is that default values for simple literal properties (strings, numbers, booleans) can be set on the prototype directly, but any property that inherits from Object (including arrays and dates) should be set to null and then initialized in the constructor.

The safer way is to only declare methods on the prototype, and always declare properties in the constructor.

Anyway, the question was about Object.create...

The first argument passed to Object.create is set as the prototype of the new instance. A better usage would be:

var person = {
    initialize: function(name, age) {
        this.name = name;
        this.age = age;
        return this;
    },

    toString: function() {
        return this.name + ', ' + this.age;
    }
};

var tim = Object.create(person).initialize("Tim",14);

Now the output will be the same as in your first example.

As you can see, it's a different philosophical approach from the more classical style of OOP in Javascript. With Object.create, the emphasis is on creating new objects from existing objects, rather than on the constructor. Initialization then becomes a separate step.

Personally I have mixed feelings about the Object.create approach; it's very nice for inheritance because of the second parameter that you can use to add additional properties to an existing prototype, but it also is more verbose and makes it so instanceof checks no longer work (the alternative in this example would be to check person.isPrototypeOf(tim)).

The main reason I say Object.create is verbose is because of the second parameter, but there are some useful libraries out there that address that:

https://github.com/Gozala/selfish

https://github.com/Raynos/pd

(and others)

I hope that was more enlightening than confusing!



来源:https://stackoverflow.com/questions/10361803/in-javascript-the-difference-between-object-create-and-new

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!