Introduction
This is part 5 and my final post on The principles of object oriented programming in JavaScript by Nicholas C. Zakas. My next blog series will be on data structures and algorithms in JavaScript. This post will be on Inheritance. If you have questions or concerns about this post feel free to reach out to me on twitter.
Inheritance
- Learning how to create objects is only the first step. The second step is understanding inheritance. In traditional object oriented languages classes inherit from other classes. In JavaScript the inheritance mechanism is know as prototypes.
Prototype Chaining and Object.prototype
- JavaScript's built in approach for inheritance is called prototype chaining or prototypal inheritance. Object instances inherit properties from the prototype. The prototype is actually just an object and that means it has its own prototype. The prototype inherits properties from another prototype object until it reaches the end of the prototype chain(for most objects the end is Object.prototype). This continual inheritance is called the prototype chain. All objects inherit from Object.prototype unless otherwise specified. The consequence of this is that all objects have a certain number of methods automatically inherited thanks to the prototype chain.
1) hasOwnProperty() determines whether an object contains a property with a certain name.
2) propertyIsEnumerable() determines if a "own property" is enumerable or not.
3) isPrototypeOf() determines whether the object is the prototype of another.
4) valueOf() returns the value representation of the object
5) toString() returns a string representation of the object.
- The five methods mentioned above appear on all objects through inheritance. The last two methods are very important in JavaScript because they make objects behave in a more consistent manner. We will now dive more deeply into those two methods.
valueOf()
-
The valueOf() method is called whenever an operator is used on an object. By default, valueOf() returns the object instance. The primitive wrapper type overrides valueOf() making it return a String for a String, Boolean for Boolean and so on.
const num = 4 num.valueOf() // this will return the number 4
The above code demonstrates how the primitive wrapper types override valueOf(), so for our example the number returns a number.
toString()
- The toString() method is called as a fallback when valueOf() returns a reference value(object) instead of a primitive value. It is also called implicitly(automatically) whenever JavaScript is expecting a String. For example, when a String is used as one operand for the plus operator the other value is automatically converted a String. If the other value is a primitive value it gets converted to a string via the toString() method. If the other value is an Object, then valueOf() is first called. If valueOf() returns a reference type, then toString() is called and that value is used.
Summarize
-
When a string is used as one operand for the plus operator, the other operand is automatically converted to a string.
"string" + <primitive value> // always a string "1" + true // "1true"
-
When you try to combine a reference value(object) with a string, valueOf() is first called. If valueOf() returns a reference type then toString() is called and that value is used.
const person ={ name:"Bob" } "Book=" + person // will give us "Book=[objectObject]"
- From the code block above, the internal JavaScript operations go like this. 1) its a reference type so valueOf() is called. That will return {name:"Bob"}, this is not a string. Since the one operand is a String JavaScript is expecting a String.
2) toString() is called and returns "[objectObject]". This is a string so that value is used.
What is happening above is called coercion and it is a rather big topic. Here is a link an article explaining it rather well.
Object Inheritance.
-
The simplest type of inheritance is between objects. All you have to do is specify what object should be the new object's [[Prototype]]. Object literals have Object.prototype set as their prototype automatically, but you can set the prototype with the Object.create() method. Object.create() accepts two arguments, the first argument is the object used for [[Prototype]] in the new object. The second argument is optional and is an object of property descriptors.
const person ={ name:"Bob" } const person = Object.create(Object.prototype,{ name:{ configurable:true, enumerable:true, value:"Bob", writable:true } });
-
The code blocks above are equivalent to each other. However, you will most likely not use Object.create() to inherit from Object.prototype because you get that automatically. You will most likely use it to inherit from other objects.
const person1 = { name:"Bob", sayName: function(){ console.log(this.name) } } const person2 = Object.create(person1,{ name:{ configurable:true, enumeration:true, value:"Tim", writable:"true" } }) console.log(person1.sayName()) // Bob console.log(person2.sayName()) // Tim
Person2 inherits both sayName() and name from person1. person2 uses Object.create() to define its own name property. This means that the name property created on person2 is shadowing the name property on person1. It should be pointed out that sayName() only exists on person1. The person2 [[Prototype]] points to person1, person1 [[Prototype]] points to Object.prototype and its [[Prototype]] points to null. When you access the name property on a JavaScript object the JavaScript engine will take that path until it finds the property it is looking for. If the property is not found then it returns undefined.
Constructor Inheritance
-
Object Inheritance in JavaScript is also the basis of constructor inheritance. In JavaScript almost every function has a prototype property. That prototype property is automatically assigned to be a new generic object that inherits from Object.prototype and has a single property called constructor when we use a constructor function. The code block below shows what goes on "under the hook" when we define a constructor function.
function MyConstructor(){ // initializing code here } MyConstructor.prototype = Object.create(Object.prototype,{ constructor:{ configurable:true, enumerable:true, value:MyConstructor, writable:true, } })
Any time we define a constructor the Object.create() method is automatically called. So without doing anything extra JavaScript sets the constructor's prototype property to an object that inherits from Object.prototype
Constructor Stealing
-
Also called pseudoclassical inheritance, constructor stealing works thanks to us being able to use the call() and apply() methods. They allow us to call the supertype's constructor from the subtype and enables us to "steal" the supertype's constructor. The code block below will explain what is going on.
function Person3(firstName, lastName){ Person.call(this, firstName, lastName) }
-
This is us "stealing the supertype's constructor(supertype being Person). Below will show what is going on "under the hood"
Person3.prototype = Object.create(Person.prototype,{ constructor:{ enumerable:true, configurable:true, value: Person3, writable:true } })
Using this technique avoids redefining properties from a constructor which we inherit. we can then add new properties or override existing ones after calling the supertype's constructor.
-As you can see object oriented programming in JavaScript can get complicated at times. You may now understand why the ES6 class syntax was created. If you are interested in Object oriented programming in JavaScript I would recommend using that syntax. The class syntax is simply syntactic sugar over JavaScript's prototype based inheritance to give it a more familiar look to classical object oriented inheritance.
Conclusion
- This marks the end of part 5 and my series on object oriented programming in JavaScript. Please make sure to be on the look ok for my next blog series which will be on data structures and algorithms in JavaScript. The next post will be on Queues and continuing from there until Graphs. Once the data structures are done, I will create posts on algorithms. If you have any questions or concerns about this post or the up coming series please let me know on twitter.
Top comments (0)