12

From this blog post we have this example of a prototypal inheritance in JavaScript:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        alert(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        alert(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});

var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars'}
});

var jane = Object.create(female, {
    name: {value: 'Jane'}
});

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

Now, what I'm wondering is how does one properly "override", for example, the sayPlanet function?

I tried it like this:

jane.sayPlanet = function(){
    console.log("something different");
};

and this works.

However, I also tried it like this:

var jane = Object.create(female, {
    name: {value: 'Jane'},

    sayPlanet: function(){
        console.log("something different");
    }
});

but I get a type error.

My questions are:

  • how can I add the sayPlanet function inside the Object.create?
  • is this at all "a good way" or is there a better (best practice) way?

edit: I figured a way how I can add the sayPlanet inside the Object.create:

sayPlanet: {
    value: function(){
        console.log("something different");
    }
}

However, a second question remains. Also, I would appreciate if someone can explain it in a bit deeper level if this is "a good way" to use it like this.

edit #2: As Mahavir pointed below, this is an awful example, because as it turns out you can't (please correct me if I'm wrong) change the name of jane once it has been Object.created.

edit #3: (man oh man, this is going to get me in a certain facility where people wear white coats). As @WhiteHat pointed below, indeed you can set a name property to be updatable like this:

var jane = Object.create(female, {
    name: {
        value: 'Jane',
        writable: true
    }
});

and then you can do jane.name="Jane v2.0";.

I'll be honest here people - I do not have a clue as to which direction to take with seemingly so many options. And just today I read Eric Elliot https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3 and now I don't know what to think anymore because he goes on to argue that people at the ES6 aren't quite doing it right :O. Meh, I guess I'll have to revisit the Crockfords book yet again, decide on a "one way" and see how far it takes me.

Nikola
  • 14,888
  • 21
  • 101
  • 165
  • the type error is because you have a semi-colon after the function declaration inside your object – Ygg Sep 22 '15 at 11:11
  • ah, sorry, although you're correct (and I removed it) - i still get the same error. I fixed that in the original post now. Also, I managed to figure out the answer to question 1. The second one remains. – Nikola Sep 22 '15 at 11:12
  • 1
    to change the name of `jane`, include `writable: true` in your property definition... – WhiteHat Oct 01 '15 at 14:40
  • @WhiteHat: I have to agree with your point on the answer below - this is now a freaking mess. Through these days I've been reading like crazy about all this. Unfortunatelly nothing "clicked" in my head yet :/. I'll try your writable suggestion now. – Nikola Oct 01 '15 at 18:59
  • The `writable:true` works. I updated my edit. Thanks. – Nikola Oct 01 '15 at 20:17
  • There is no "one way". I use `__proto__` a lot for its simplicity, but I also use `Object.create` for its power. In fact, while most JS experts I know agree that object.create is more pure than constructor, I cannot say most of them side with Eric Elliot (I do). Use whatever you think is best for you and your team. – Sheepy Dec 17 '15 at 08:37

6 Answers6

6

The best practice as I've learned is to define write permissions at the Object prototype level. I learned this technique from reading Addy Osmani's JavaScript Design Patterns, it's very reputable and open source online: http://addyosmani.com/resources/essentialjsdesignpatterns/book/

Please check out the sayPlanet property in the example below. Keep in mind, you do not need to set all the other properties' "writeable" to false, I only did it in my example code to illustrate the point. This approach affords more flexibility and reusability than the other, still valid, approaches. Here, you may notice that this is the Object.defineProperties syntax within the prototype.

var human = {
  name: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  gender: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  planetOfBirth: {
    value: "Earth",
    configurable: false //only to illustrate default behavior not needed
  }

};

//here we define function properties at prototype level

Object.defineProperty(human, 'sayGender', {
  value: function() {
    alert(this.name + ' says my gender is ' + this.gender);
  },
  writable: false
});

Object.defineProperty(human, 'sayPlanet', {
  value: function() {
    alert(this.name + ' was born on ' + this.planetOfBirth);
  },
  writable: true
});

//end definition of function properties

var male = Object.create(human, {
  gender: {
    value: 'Male'
  }
});

var female = Object.create(human, {
  gender: {
    value: 'Female'
  }
});

var david = Object.create(male, {
  name: {
    value: 'David'
  },
  planetOfBirth: {
    value: 'Mars'
  }
});

var jane = Object.create(female, {
  name: {
    value: 'Jane'
  }
});

//define the writable sayPlanet function for Jane
jane.sayPlanet = function() {
  alert("something different");
};

//test cases

//call say gender before attempting to ovverride
david.sayGender(); // David says my gender is Male

//attempt to override the sayGender function for david 
david.sayGender = function() {
  alert("I overrode me!")
};
//I tried and failed to change an unwritable fucntion
david.sayGender(); //David maintains that my gender is Male

david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different
Nice-Guy
  • 1,457
  • 11
  • 20
4

Using Object.create is certainly a valid approach, but the example itself seems to me a bit misleading regarding the inner workings of Object.create. The blog post does a really good job of summarizing the different ways to create objects in javascript, but I don't think the example for Object.create gives a good idea of how it works, which is more similar to the new/constructor approach than it may seem.

Object.create allows to create an object based on a prototype, but without a constructor. This means that the prototype chain of the created object isn't dependent on a constructor, (which is why it may be simpler to follow, this prototype linked via the constructor is not very straightforward or easy to follow). But Object.create still creates a prototype chain, in the same way new does.

So in your example, when you define name in human for example here:

var human = {
    name: '',

And then when you create jane:

var jane = Object.create(female, {
    name: {value: 'Jane'}

You're not really assigning a value to the name property you defined in human. You're in fact adding a property to jane. But human.name still is a property in prototype chain of jane. It works because javascript will follow the prototype chain to find the first matching property, but human.name is still somehow linked to jane.

See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

Same thing happens if you use a constructor:

var Person = function(gender, name) {
     this.name = name;
}

var newPerson = new Person();

And same goes for sayPlanet function.

It's a valid approach, but can lead to strange behaviors. For example, you could decide to modifiy sayPlanet for all humans by assigning it this way:

human.sayPlanet = function(){console.log('new sayPlanet')}

This would then work for all humans, except the ones for which you have given a sayPlanet property of their own. Which in your case may be the expected result. But still, you have to see if sayPlanet really should be a property of human.

With gender, it's applied in human, and in male and female. So changing human.gender wouldn't work on any one. But it's still a property of human, which is a bit confusing when you want to work with these objects. You basically have a property that is defined, that is writable, but that when changed has no effect at all. It's mainly an indication as to which property you need to add to your humans instances or somewhere in the prototype chain. Again, it seems to be used a lot, but when explained with this kind of examples, it somehow gives the impression that Object.create just combines properties, but it's not what it does.

In the end, you need to chose if you want to work with prototypes or not. If not, then Functional inheritance is probably the best way. Then each object is different and has its own set of properties, that you can initialize, and you don't have to worry about prototypes.

If you want to use prototypes, then you can use new/constructor or Object.create approach. But Object.create will create a prototype chain the same way new does, it just gets rid of the constructors.

A small example of how Object.create and new share some behaviors:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});


var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars', configurable: true}
});

var jane = Object.create(female, {
    name: {value: 'Jane'},
    sayPlanet: {value: function(){
        console.log("something different");
        }, writable: true, enumerable: true, configurable: true
    }
});

var Male = function(name){ // in this case the real constructor is female or male. Name is the only property that 'needs' to be initialized
    this.name = name;
    this.planetOfBirth = 'Jupiter';
}

Male.prototype = Object.create(male);

var john = new Male('John')

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth


delete david.planetOfBirth; //just to show how human properties will still be in the chain even if you delete them
delete john.name; // It's true also if you use new. 
delete jane.sayPlanet;

console.log('\n','----after deleting properties----');

david.sayPlanet();
jane.sayPlanet();
john.sayGender();

human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created
                             
console.log('\n','----after assigning planetOfBirth on human----');

david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John still has planetOfBirth as its own property, since it wasn't deleted

delete john.planetOfBirth;

console.log('\n','----after deleting john planetOfBirth----');

john.sayPlanet();  // But it's still there

And in fact (just to be more confusing), some people combine Object.create with new/constructor or with Functional inheritance. Something like this for example:

https://john-dugan.com/object-oriented-javascript-pattern-comparison/#oloo-pattern

Applied to your example it would give something like this:

var human = {
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
              console.log(this.name + ' was born on ' + this.planetOfBirth); 
    },
    init: function(name){
        this.name = name;
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'} // This is part of male/female prototype and can't be written, which seems logical
   });

var female = Object.create(human, {
    gender: {value: 'Female'}
});


var david = Object.create(male).init('David');
david.planetOfBirth = 'Mars';

var jane =  Object.create(female).init('Jane')
jane.sayPlanet = function(){console.log('something different')};

var john =  Object.create(male).init('John');
john.planetOfBirth = 'Jupiter';




david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth


delete david.planetOfBirth; // Overridden properties will still exists after delete, but not the others.
delete john.name;
delete jane.sayPlanet;

console.log('\n','----after deleting properties----');

david.sayPlanet(); 
jane.sayPlanet(); 
john.sayPlanet(); 

human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created. 
                               // But not for humans woth overridden planetOfBirth.

console.log('\n','----after assigning planetOfBirth on human----');

david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John's name is now undefinded

delete john.planetOfBirth;

console.log('\n','----after deleting john planetOfBirth----');

john.sayPlanet();  //

Not necessarily better, but it works as well, and in my opinion has certain advantages.

In any case, as others said, there doesn't seem to be a standard or default way to do this.

Julien Grégoire
  • 16,864
  • 4
  • 32
  • 57
3

From MDN...

The second argument for Object.create calls for a propertiesObject.

The Syntax section of Object.defineProperties describes it best, see props.

In short, the second argument passed to Object.create should have enumerable properties, each with one or more of the following keys...

configurable
enumerable
value
writable
get
set

You should address each of these, for each class member you assign.
Depending on the intended functionality...

Drawing from the example in your question, in order to change the value after the object is created, just add writable: true.

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
  gender: {
    value: 'Male',
    writable: true
  }
});

var david = Object.create(male, {
  name: {
    value: 'David',
    writable: true
  },
  planetOfBirth: {
    value: 'Mars',
    writable: false  //only born once
  }
});

david.sayGender();
david.sayPlanet();

david.gender = 'Female';
david.name = 'Caitlyn';
david.planetOfBirth = 'Venus';  //(will not work, not writable)

david.sayGender();
david.sayPlanet();
WhiteHat
  • 59,912
  • 7
  • 51
  • 133
  • Thanks for your answer. And, what I'm actually wondering is - is this a "best practice". Would you use it (are you using it) like this? Or are you using it with for example functions and `new`, or some other way? – Nikola Sep 30 '15 at 08:54
  • 1
    It is hard for anyone to speak best practice when it comes to JavaScript and I'm not going to try. The pattern in question is modern and used extensively. `new` is *out*, `object.create` is *in*. Personally, I think it is easier to understand than constantly modifying the `prototype`. I'm currently drowning in SharePoint 2010, all of my current stuff is in `MicrosoftAjax.js`. The last module pattern was `function` and `new`... – WhiteHat Sep 30 '15 at 11:28
  • Thank you. Let's just hope ES7 will be "it" in terms of "standard", so that one would not have to have questions like these :) – Nikola Sep 30 '15 at 11:30
  • have you tried Babel maybe? – Nikola Sep 30 '15 at 11:40
  • Mahavirs answer below brings real light to the subject. Unfortunately the way I wanted to make it indeed indicates that it's just flat out wrong to do it like this. For example - you can't change anything after the object is created. – Nikola Oct 01 '15 at 07:57
  • That is why you need to use more than just `value` in your property definition. Typically, you would set a `value`, along with a `get` and `set` function. Sorry, I was more focused on the concept, rather than the example... – WhiteHat Oct 01 '15 at 11:53
  • 1
    "new is out, object.create is in" - Evidence? – a better oliver Oct 02 '15 at 20:25
  • For me anyway, based on [growing](https://www.google.com/#q=object.create%20browser%20compatibility) -- [browser support](http://kangax.github.io/compat-table/es5/) for `Object.create`. You may [form your own opinion](https://www.google.com/#q=javascript+object.create+vs+new), but the best guidance I can provide would be to find a pattern that works, and you understand, then use it consistently. – WhiteHat Oct 03 '15 at 02:06
2

Very bad way to implement inheritance, I really don't feel well about the example.

What if you want to change some value, say 'planetOfBirth/gender' later sometime after you created the object.

  • As the object properties are default not enumerable, not configurable, not writable.

The best practice always depends on your model, structure and your end goal. Below analysis is one way.

//Following the same example: lets understand this like a real life problem
//Creates human objects
var human = {
  name: '',
  gender: '',
  planetOfBirth: 'Earth',
  sayGender: function () {
      console.log(this.name + ' says my gender is ' + this.gender);
  },
  sayPlanet: function () {
      console.log(this.name + ' was born on ' + this.planetOfBirth);
  }
};

//Creates Person constructor to use later to create Male/Female objects like David/Jane
var Person = function(gender, name) {
 this.gender = gender;
 this.name = name;
};

//Assigning the human as the prototype of Person constructor
Person.prototype = Object.create(human);

//Setter function to change the planet of birth of a person
Person.prototype.setPlanetOfBirth = function(planetOfBirth){
  this.planetOfBirth = planetOfBirth;
};

//Creating david object using Person constructor
var david = new Person('Male', 'David');
//change the planet of birth for David as Mars
david.setPlanetOfBirth('Mars');

//Creating jane object using Person constructor
var jane = new Person('Female', 'Jane');

//A object specific function the will only available to Jane to use
jane.sayPlanet = function(){
 console.log("something different");
};

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different

Even after so many day I'm confused and digging more into the JavaScript object concept.

I believe, there is no such official documents. Below some documents might help you understand the JavaScript Object and Inheritance.

Introduction to Object-Oriented JavaScript

Object.create()

Why is it necessary to set the prototype constructor?

Prototypal inheritance

Common Misconceptions About Inheritance in JavaScript

search: Object-Oriented JavaScript.pdf

Community
  • 1
  • 1
Mahavir
  • 322
  • 4
  • 8
  • Indeed you are correct about the inability to change the members of the object once it has been created. Do you maybe have some official document which explains a bit more about the subject so that I could learn more? Btw, I'll leave the question open for some time, but if nothing else happens I'll mark yours as the correct one. – Nikola Oct 01 '15 at 07:58
  • Hi Nikola, I have edited the the response. – Mahavir Oct 01 '15 at 09:17
  • Thanks for the links. And then I read this: https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3. And now I'm totally baffled. – Nikola Oct 01 '15 at 10:58
  • This answer is sort of a mish mash of different concepts. Typically, folks either use `Object.create` or `= new Function` but not both. And I realize it was drawn from the question, but in my opinion, creates even more confusion. This [article](http://www.adobe.com/devnet/archive/html5/articles/javascript-object-creation.html) _may_ help. – WhiteHat Oct 01 '15 at 15:01
0

1-it should be like below block of code :

var jane = Object.create(Female.prototype, {
    name: {value: 'Jane'},

    sayPlanet:{ value : function(){ alert("something different");}}
});

2-this solution is good and you can see this reference

Nikola
  • 14,888
  • 21
  • 101
  • 165
0

I am not sure if there is a best practice for prototype inheritance in javascript, but the classic way to assign prototype to an object is to create a constructor function:

var human = {
  sayPlanet:function(){
    // old sayPlanet
  }
}

function Person(name, sayPlanet)
{
  this.name = name;
  this.sayPlanet = sayPlanet;
}

Person.prototype = human;

// use your constructor function
var david = new Person('david', function(){/*new sayPlanet*/});

There is also class .. extends proposed in ES6 standard, but you must remember that it is a syntactical sugar over the javascript's existing prototype-based inheritance and there are still no native classes in javascript.

Eugene Tiurin
  • 4,019
  • 4
  • 33
  • 32