What do you want to learn?
Skip to main content
by Jim Cooper
Resume CourseBookmarkAdd to Channel
Table of contents
Getting Started with Plunker
So far we've looked at two different ways of creating objects--using object literals and using constructor functions with the new keyword. It's worth noting that these are basically just syntactic sugar for object.create. We could create these same objects using the object.create syntax as follows. Let's go ahead and comment this out. And in place of this, we'll use this syntax. You can see here that we're using the object.create function to create our new object. And we're passing in the object that will become the prototype for our new object. And then down here, you can see that we're creating the name and color properties. For each of those properties, you can see that we're assigning the value and setting the enumerable, writable, and configurable properties to true. This all happens for us when using either object literals or constructor functions. Now aren't you glad that you don't have to do all that every time you create an object? The constructor function approach actually does a little bit more than this with regards to the prototype and the constructor. But essentially this is what's happening in both cases with respect to creating the objects. You can see that it's much nicer to be able to use object literals or constructor functions instead of having to do all of this. You may be wondering what those enumerable, writable, and configurable attributes are. We'll talk about that in the next module about properties. But before we jump into that, let's take a look at ES6 classes, another way to create objects.
In the next couple of examples, you'll see me using the bracket notation for properties. This can be very useful in a few cases. And here's how you use it. First, notice that we can look at the cat's color property as we have been with dot notation like this. But we can also use bracket notation like this. Notice those produce identical results. So why would you ever use bracket notation? Well, what if you for some reason needed to create a property on an object using a property name that is not a valid identifier? For example, let's create a property on our cat called Eye Color. We'd do that like this. Now let's take a look at that value. You may still be wondering why you might ever do this. Well, what if you wanted to create an object out of values being entered by a user? Or it's possible you have a source of JSON data that has property names that are not valid identifiers. It's not too common that you run into this, and it's best to use valid identifiers for your property names for simplicity, but it is nice in the rare cases that you need it.
Now let's take a closer look at properties. You may be surprised to learn that a property is more than just a name and a value. Every property has a property descriptor that we can use to see the attributes of that property. To demonstrate this, notice that I changed my cat object back to a simple object literal for simplicity's sake. So let's take a look at the property descriptor for the name property of our cat object. We can do that like this. So we're printing out the property descriptor for our name property. And you can see that in addition to the name property having a value, it also has writable, enumerable, and configurable attributes. And these are all set to true by default. In the next view clips, we'll take a look at each of these attributes and what they're used for and how you can change them.
Using the Writable Attribute
The writable attribute does what you would probably expect--it defines whether the property's value can be changed from its initial value. So let's make the name property non-writable. We can change the property attributes using the Object.defineProperty method like this. There, now you can see that the name property is not writable. So let's see what happens if we try to change the name of our cat. First, I'll open the error console so you can see any errors. Now let's change the name of our cat. You can see that I've thrown an error because my property is not writable. Now it's very important to know that this behavior of throwing an error only occurs in strict mode. Notice that if I clear my console errors and then go up and remove 'use strict' that I don't get any errors. That's kind of scary. One of many reasons why it's best to always run in strict mode because, in this case, you would think that you're assigning the value Scratchy to your cat's name, and it would just silently fail and you would have no idea. So it's always best to run in strict mode. So we'll go ahead and put that back. Now let's take a look at what happens if a non-writable property contains an object. So let's make the value of our name an object, and that object will have a first and a last name like this. So now my name property's set to this object, and I'm trying to set it to Scratchy here still, and it's failing and throwing errors. That's why we're not getting a display. But here's the interesting thing. I actually can change the object that is pointed to by that property like this. Now you can see we're not throwing an error anymore because our display is working. Let's go ahead and display the cat name. So there you can see that even though the name property was read-only, I was able to change the value of a property of the object that the name property was pointing to. This makes sense when you think about it since name is really just a pointer. And when you make it read-only, you're only preventing that pointer from being changed. As a side note, you actually can prevent the object from being changed by using object.freeze like this. And now that that's frozen, you can see that I'm getting an error because the entire name object is now read-only in addition to the actual name property. So just keep that in mind if you ever make a property read-only that if the property contains an object, you'll need to freeze the object also in order to prevent it from being changed.
Using the Enumerable Attribute
Okay, now let's take a look at the enumerable attribute. Before we look at that, though, let's take a look at the for…in loop. You can use the for…in loop to loop over each of the properties in any object like this. Okay, so you can see here that we're looping over each property in the cat object, and each time through the loop, it returns the name of the property and assigns that to our property name variable. And then we're displaying each one of those. Now we can use those property names to get the value of each property using the bracket notation we talked about earlier like this. So now we're displaying both the name of the property and the value of the property using bracket notation. You can see that the name property is set to an object and that the color property is set to white. Now back to the enumerable attribute. By default, properties on an object are enumerable, meaning we can loop over them using a for…in loop. But we can change that. Let's make enumerable false for the name property. There, now notice that even though I'm still looping over all the properties in my cat object that the name property was not actually returned in my loop. That is one of the main use cases for the enumerable property. Setting enumerable to false also makes it so the property does not show up in the object keys. You can look at an object's keys like this. Now let's just display that. Notice that object.keys returned an array with all the properties except notice that name is not showing up because it's not enumerable. If we make name enumerable again, then you can see that it does show up in the object keys. And, lastly, setting enumerable to false affects JSON serialization of the object. Notice if I serialize my cat object to JSON that I can get the whole object. I'll have to scroll over for you to see all of that. So there you can see our color property and our name property both got output in the JSON. But if I change my name property to not be enumerable again, then you can see that that property is not JSON serialized. So that shows you the power of the enumerable property. Note that you can still look at the property with bracket notation. So setting enumerable to false does not change the ability to look at the property. You just can't enumerate it, see it in the object's keys, or serialize it.
Using the Configurable Attribute
The configurable property locks down a property to prevent certain attributes from being changed. It also prevents the property from being deleted from the object. Let's take a look at that. Say we want to lock down the cat's name property so that its attributes can't be changed. We'd do that like this. Now notice that if I try to change the property's enumerable property that I get an error. I'll open the error console first so that we can see the errors. There, so you can see that I get an error if I try to change the enumerable property indicating that I cannot redefine the property. It is interesting that once you have made a property non-configurable, you can't make it configurable again. Notice if I try to change this that I get a second error. You can see that with the #2 here. So this threw another error. You can, however, still change the writable attribute. You can see that did not throw another error. There is one more thing that changing the configurable property does. It makes it so that you can't delete a property. So notice that if I try to delete the name property, that I get this error Cannot delete property name of object. And if I get rid of this so that the property is still configurable, you can see I don't get an error. And if I display cat.name, it's undefined because I've deleted that property. So to recap, there are three things that are affected by making a property nonconfigurable. If configurable is false for a property, you cannot change the enumerable attribute, you cannot change the configurable attribute, and you cannot delete the property. You can, however, change the property's writable attribute. cool
Using Getters and Setters
In this module, you learned about the writable, enumerable, and configurable property attributes. We also looked at how to use property Getters and Setters to create powerful properties backed by functions. In the next module, you'll learn about prototypes, which will allow you to use them in your code to inherit from or extend functionality in other objects and to recognize when they're being used in powerful frameworks like React and Angular.
Introduction to Prototypal Inheritance
What Is a Prototype?
Instance vs. Prototype Properties
A Graphical Overview of Prototypes
Changing a Function's Prototype
In the definition of a function's prototype, we said the function's prototype is the object instance that will become the prototype for all objects created using this function. Let's take a look at a quick example that illustrates that. Here we have our Cat function and two cats, Fluffy and Muffin, which were derived from that function. If we look at their ages, you can see that they're both inheriting an age of 4 from the Cat function's prototype. We know that if we change the age of the prototype, that that will also change the age of Fluffy and Muffin through inheritance. But what if we actually change the function's prototype to point to a completely different object. Let's try that. Notice that that did not change the age of our two cats. And if we look at the Cat function's prototype, we can see that it does have an age of 5. Notice also that if I create a new cat after having changed the function's prototype, notice that Snowbell has an age of 5. So what exactly is going on here? This really highlights that the Fluffy and Muffin objects have prototypes that are pointing to an instance of an object in memory. Let's take a look at what really happened. Here we have our diagram from earlier showing our Cat function and our two instances of cats. And you can see that they are all pointing to the same prototype in memory. When we changed the prototype of our function, what we really did was create a new object in memory and changed the function's prototype property to point at that new object. However, the existing Fluffy and Muffin instances of our cat still have prototypes that are pointing to the old prototype object. When we then created our new Snowbell instance of a cat, it created a new object and set its prototype to point to the current prototype of the Cat function. This really highlights the fact that prototypes really are objects that live in memory. And as you would expect, they behave like any other objects with regards to pointers.
Multiple Levels of Inheritance
Creating Your Own Prototypal Inheritance Chains
Creating Prototypes with Classes
If you prefer a more class-like structure to your code, you can build the same types of prototypal inheritance chain using a class-like syntax. Whether you use traditional constructor functions or classes, the end result is very similar. Everything you do with classes is mostly just syntactic sugar on top of everything you've learned about prototypes with some small differences. Let's take a look at how you'd accomplish the same thing using classes. It actually is a little cleaner. So, first, we'll create a class called Animal, and it'll have a constructor like this. So here's our Animal class, and you can see it has a constructor that takes in a voice and defaults its voice to grunt if no voice is passed in. And that replaces this code here. And then we'll add a speak method to our class. And that replaces this code here. Now let's create our Cat class, and it will extend from our Animal class like this. So that extends keyword is new, and so is super. Extend is what you use to set up your inheritance chain. And you can see that Cat still has a constructor function that sets the name and color, but you can see that it also calls its parent's class's constructor using the super keyword. So that will call Animal's constructor prior to setting the name and color of the cat. So that all replaces this code here. So now we have our cat that inherits from Animal. And we've created a new instance of our cat here. So let's see if Fluffy can speak. You can see that because Fluffy is a cat, when we called Speak, it displayed Meow. And if we look at Fluffy, you can see that Fluffy still has a voice, name, and color. There are a couple of minor differences that, really, you won't typically care about, but they are worth noting. Notice that my display function when we're displaying Fluffy is failing to indicate the type here. Remember, using the constructor function, this said Cat before. If we go look at my display function, that's just because the regex that I'm using in this function here is not an appropriate regex for classes. Notice here that I'm looking for something named function in the constructor. Here you can see that I'm looking up the constructor, and I'm using this regex to try to parse what the name of the function is. But in the case of classes, the constructor is not a function, it's a class. So if we look at fluffy.constructor, you can see it does still have the constructor, that that constructor is a class, whereas before it was a function. So that's really the only difference there is that the constructor is a class. And then there is one other difference to take note of when using the class syntax. Members of classes are not enumerable by default. So this speak function is not an enumerable property of the Animal class. That means if we look at the object.keys for Fluffy's Animal ancestor, notice that that is empty, even though if we look at hasOwnProperty for that speak function, that returns true. So while there is a speak function on Fluffy's Animal prototype, it is not enumerable, and so you don't see it in the object.keys, and you won't see it if you try to loop over the properties of the Animal class. So that is another minor difference between this and the constructor syntax. You won't typically run into these differences, however, so don't worry too much about them. But it is a difference. Other than these two minor and relatively unimportant differences, the class syntax works the same as constructor functions. Everything that you've learned about prototypes still applies to objects created using classes.
Jim Cooper is a software developer at Pluralsight. With more than 20 years of software development experience, he has gained a passion for Agile software development -- especially Lean.
Released10 Dec 2015
How likely are you to recommend Pluralsight to a friend or co-worker?