FREELessons: 29Length: 8 hours

Next lesson playing in 5 seconds

  • Overview
  • Transcript

3.8 Prototypal Inheritance and Refactoring the Slider

In the previous lesson, we took our first stab at creating a functioning slider. There certainly wasn't anything wrong with that approach, but, today, we'll try again, and pay special attention to more sophisticated math/calculation techniques, as well as prototypal inheritance. Gasp!

Don't worry; make it through this lesson, and you'll be well on your way to digging deeper into the JavaScript language.

Show Links

3.8 Prototypal Inheritance and Refactoring the Slider

[NOISE] In lesson 14 of this course we focused on building a slider. And here's the code that we came up with. And it's okay, there's nothing necessarily wrong with this. But I do want you to keep in mind one thing, this is the code required for a very simple slider. But already we have around 40 lines of code. It's a little difficult to understand. And already if I want to add maybe a new ability or feature, I need to figure out exactly where to put it. I think we can do better than this. Again, for small projects if you can get this to work, that's perfectly fine. Because extending it probably isn't going to be something that you'll be doing. However, as you grow into larger and larger projects, code organization is going to be something that is vital. So what we will do in this lesson is we're going to rebuild the JavaScript but we're going to take advantage of prototypal inheritance which is a scary job JavaScript term, but I'll do my best to explain it. So I'll close this out and I'm going to open up lesson 15. And here's our finished code, but we're going to start from scratch as we always do. Now the first step is some reading assignments. Now, you can do these where you're finished with the video or you can go through these first. The first article is called "Prototypes and inheritance in JavaScript" on Script Junkie. I think this is easily one of the easiest articles to understand. Now again, if you are literally brand new to JavaScript and this course is actually your introduction to JavaScript, it might be okay to skip this lesson because we're gonna take things a little bit easier after day 15. However, if you have a, a modest understanding of JavaScript and you're ready to begin learning about some of these concepts definitely give this article a read through. And we're also going to be taking a look at modulus, the mod operator. And we'll go over this shortly. But at mathform dot org they go over pretty well exactly how you would use it in your program. So those links will be available in the show notes. Alright. So we are ready to begin. Once again, I'll switch to full screen and we have a clean slate. We'll begin by creating a new constructor function. So we'll call this function Slider. Now what do I mean by constructor function. Well, let's go into Google Chrome and we will switch to and I'm gonna up Chrome Developer Tools just so we can toy around a bit. Now, think of an array. And we know that we can create an array with var arr equals 1, 2, 3 and now have an array of items. Now, if you have a little bit of JavaScript experience, you know that you can add items to this. So if I want to push a new item to this array, I can do so. And now array has four items. But have you ever thought to yourself, where is push coming from? JavaScript doesn't have classes, so how does array have access to these methods? Well, if we take a look at array, we can see that it is a function. Well that's a little odd. Type of array, it is a function. Well what about type of object? Well that's a function as well. So now we're starting to realize okay, well an array is just a constructor function. So, lets do it one more time. We're going to dig in to array, and I'm going to open up the function, and what you'll see here is this function. Has properties and methods, but it's a little weird, how can a function have a property or a method? And that's because pretty much everything in Java script is an object, even functions, so a function can easily have properties or methods, as we can see right here. Now, the main thing is though, if we're looking through here, well we're still not seeing that push method. Where is that? And that's contained within the arrays prototype, and don't let this scare you. A prototype is simply an object that any array you create will inherit from. So, if we reference our array, we can access its prototype. And because prototype is just an object, we can freely add our own properties and methods to it. So if we take a look at a raised prototype right here, let's open that up. Now we can see that's all the methods that we are familiar with. Those [UNKNOWN] specific methods like splice or map or push, and that's how we can access them. And they're available to the prototype. That way any array we create can have access to that. But every array doesn't have to have all of these methods in memory, they're contained within the array functions prototype. Now I know that's confusing but stick with me on this. Now, back to our code. When we create this function we know that this is going to refer by default to the global window object. However, when we treat it like a constructor function. We create a new instance of it. And when we do that, among other things that are probably a little over your head at this point. Among other things, this will no longer refer to the window. So if I make this equal to slider and we create a new instance of slider, watch what happens when we console.log this, and we can see that, now, this refers to slider, so when we create a new instance of slider, it actually creates an object, it sets up the prototype relationship, which, it's okay if you don't quite understand that yet. It does take a little while. And then, finally, it sets this equal to, in this case, slider. So now within here, we can say things like this dot direction, and we'll say forward. And now we can access that value. I'll delete console. And within here, we'll say console.log, slider.direction. And now we can see we can gain access to that. And we can even add methods here. We can say this.move is a function. And once again, I'm gonna console.log moving, just for our example. And then if we call slider dot move and we try it out now we get moving. Now here's the thing though. If I check out slider we'll see that this is what we get. We get this object and we have the direction property and we do have the move method. But watch what happens if I create another instance of Slider? We'll say slider2. Refresh once again. Now we have two objects here. But now we're dealing with the move method taking up all of this extra memory. Because it's declared for every single instance. And that seems a little silly. Wouldn't it be nice if every object we create will inherit that method, and that way it only has to exist in memory once. And we can do that with prototypal inheritance. I can say slider, and we're going to get that prototype, that object there, that we can add to. And we're going to add that move method to it. And now we'll console dot log moving again. This time, we'll remove the method. But if we return and reload the page and we take a look, now both of them just have that direction property that will be unique to the instance, but if we take a look at proto, we can see that both of them have access to that movement that still, they're simply inheriting it. And that's how we deal with inheritance in JavaScript. And what's nice about this is we can access it in the same way. So once again, slider.move, slider2.move. And now we get that. And now if you're still confused. And you probably should be if these are brand new concepts to you. You'll definitely wanna read as many articles as you can find. Because these things begin to stick when you just keep reading it over and over and over and picking up just a little piece of the whole puzzle with every read through and eventually you'll get to the point where you get the whole puzzle together. So let's continue with this example before we complete our slider. Let's say when you create a new instance of slider you wanna pass in the direction. So right here, I'm going to pass in forward and for this next slider, we're going to pass in a direction of backward. Now when we create that instance, we can create a new property called direction and we'll make it equal to what the user types in. So now we have a property of the slider object and that property is called Direction. And it will be equal to either forward or backward. So lets try this out I'm gonna delete these two. We'll stick with one slider for now and we will console dot log slider dot direction. Reload and we do get forward. But if we create another instance of it, as we did before, "slider2,' this one is going to go backward. Let's try it. And now we have forward and backward. Now this "move" method that's part of the prototype, we could say "moving + this.direction." Notice that "this" is going to refer to that "slider" object. So in the first case when we create this instance we specified that we're moving forward, so the move method, slider.move, is going to log moving forward. But in this next example, slider2.move is going to reference moving backward. Reload and we get moving forward and moving backward. So this is your very crash course. It's a very complicated subject at least at first. And honestly, take a look at this article because Scott Allen, who wrote this, can explain it far better than I'm capable of in a video. So with that out of the way, let's get started building this. Once again, I'm going to remove all of this. And we have our slider constructor function. We're going to begin by setting our parameters. So once again, the container. That is going to be equal to whatever the user pass is. So we'll accept the parameter called Container. And that way when we call, new slider, we can pass in what the container is. And if we go back to index.html, you'll remember that the container is the div with a class of slider. So we could pass slider like that. Now, this jQuery object where we search for the div with the class of slider is represented within this container argument. And now we're going to add a new property to the slider object called container. So at this point, we can assign it to slider. And we console, dot log slider, dot container. Close these out. Refresh and now slider dot container is equal to the location of the slider element. Good so lets do the rest of our properties. We need a path to the navigation and we're gonna store that as the second argument like so. So now, when we create a new instance of slider, we are going to reference a path to the slider, and we'll also reference a path to the navigation. And we know that's the div with an ID of slider.nav. There we go. Let's continue on. Now, we need to find our images, so we'll say this.img is going to be equal to this.container. Now, we're referencing that slider ref. And we're going to find the images. Next, we're going to have the width and this is where we figure out what the width is of the image. And we know from the last lesson we can easily grab the first image and figure out what the width is. In our case, that's 600 pixels. And then we need to know how many images are in our slider, and we know we can do that by getting the images in the jQuery object and retrieving the length, the number in that collection. And then the final thing we want to do, as with the last lesson, is set the current image that we're on, and before we were doing it as one, but this time I wanna show you a different technique. We're going to stick with how Java script works in a lot of ways, using a zero base, where the first item is actually set to zero. So now, when a user creates a new instance of the slider, they pass in the location of where slider is and where the navigation for the slider is, and then we create a handful of properties. Now the next thing is, because we're dealing with methods and because we're taking this route It forces us to be a little smarter with how we structure our code. The idea behind a function is that each function should serve exactly one purpose. It does one thing. And that makes it much more maintainable as your projects get larger. Because one function isn't doing 50 different things. You have perhaps 50 different functions that each handle each task, and that way when you're debugging and you know there's a problem in one function, it's much easier to handle. So we'll create a method that will handle the process of transitioning from one image to another. So we will extend the prototype. Remember, it is possible to do this.transition equals function, but then every new instance of this slider is going to have this transition method in memory. And we probably don't want that. Let's instead use inheritance so that. Every instance of Slider inherits this method, Slider.prototype.transition. And that's going to be equal to a function. And within this function we will simply grab the container. And we know that's the div with the classless Slider. And we're going to animate it. That's the only thing this method is responsible for taking care of. And it will animate the margin left. And we're going to make this equal too. And we're going to take a slightly different approach. Before we were trying to deal with relative, and we'd say minus equals 600 each time. But you notice that in that code we had to figure out a little too much. And we can make this easier on ourselves rather than hard coding and using this relative syntax, instead we're simply going to do this number instead. This dot current times this dot I-M-G width. And that's a much easier solution. So think about it. We clicked to the third image. So at that point, this dot current is equal to two. So once we multiply two times the width of each image, 600, we get 1,200, so we're saying margin left, 1,200, or when the user clicks again, current is equal to three, and then we would switch to 1800. But as we know from the previous lesson, we really want our margin left to be negative. And that way, we push the images to the left because we're applying negative margin. So we really want that to be negative 1800 rather than 1800. So I'm simply going to prepend a dash right here. And we're going to say negative, and then the result of this calculation negative 1800 or negative 1200. And that's an easier and more elegant way to solve this. Now it would also be nice though to overwrite this. So if the user wants to call this method and rather than using. The current image to calculated. It would be nice if they could also just say nope transition to this value. So for instance, if they would rather pass a value like zero and we'll store that within coordinates then we would immediately transition to zero and ignore any of the default calculations. And we can set defaults really easily. Just like that. Now we're saying, when this method is called, if the coordinates argument is not undefined, then we're going to set the margin left equal to that. So if they call transition and pass zero, we're going to set the margin left equal to zero. However, if they don't pass anything at all. In that case, that fails so then we use this operation to determine where we animate. So now that we have a basic structure in place notice that we're not really doing much DOM manipulation, we're going to save this for the actual core of our JavaScript and we will take of the DOM manipulation somewhere else and that way we're not hard coding anything specific to a project within these methods. So I'm gonna go back to index.html, and you can place this in a different script or, for convenience we'll place this at the bottom. Now, we'll create a variable called slider and make that equal to a new instance of the slider object that we created. Now within here it needs to know the location of our container. And we know that slider. Next we know that we need to pass the location to the navigation, so we'll do that one as well. Slider, nav, so now we've created a new instance, as we did before. The next step is to listen for when the user clicks on one of the navigation buttons, and if we refer back to our constructor. We know that we can access the navigation now by using the nav property. So if we return and we console dot log slider dot nav. Now, slider dot nav is equal to a path to where the navigation buttons are. So that'll be our first step. We'll say slider dot nav. And we want to find those buttons. And when they're clicked, we will execute a function. So for now let's see if our transition method is working. We'll call slider dot transition and we're going to hard code this value in: 1800 pixels. Reload the page, I'll bring this down. And we do not see those buttons because, if you remember, we hide the buttons using "display: none." And that way we can show it with JavaScript. So right here we'll say, "," and then continue on. Reload the page and now we have our buttons. And when we click on it, we transition to 1,800 pixels. Now what's happening here is our transition method is working, but we're applying it to the wrong thing. If you think about it we had diff slider and that's our main raf, but what we want to manipulate is the unordered list. So why don't we add that on like so, div slider UL. Now we're going to click on it and we're going to transition 1800 pixels. And so you can see how helpful that is especially when dealing with larger projects. Admittedly, this is a small project to be worrying about. But now we have this method where we simply call it and we can immediately switch to anywhere we want. I'll click next. And now we've switched number two or if we want to go to number three, like so. So that's very helpful. But let's just leave it blank and see what happens. Reload, and I'll click next and nothing's happening. So we need to debug. And this will be an easy one. We'll go back to slider.js, and we call this method and we animate it and we're going to animate this dot current. Well, that's equal to zero times the image width. So we're having zero times 600 and if you know your math you know anything times zero is going to be equal to zero. So that's a first step, is when they use a clicks, we need to update the value of the current property. So let's create a new method that will handle this process. Slider.prototype and we'll call this set coordinates. This will specify the coordinates of where we are going. Now, the only thing that this will require is whether we are going forward or backward. And I'm going to store that within a variable called dir. And you'll remember that we stored that within the data-dir attribute. So now we'll replace this and call slider.setCoordinates, and now we're going to pass in this .data('dir'). We call the setCoordinates method, and then we get the button that was clicked and then we look for the data dir attribute. And that will either be equal to prev or next, and we send that through to the method. So now at this point, dir will equal next or prev. So now let's take care of this. Within here, we're going to have some fun with java script and take a look at some of the different ways we can cast the values. So that we can shorten code. Now the basic idea is we want to take this.current, the current number and let's just hard code that in. We're going to add one to it, but what if they click on the previous button? Well in that case, we would want to subtract one from it. So we need to figure out a way to determine whether we add one or we subtract. So let's do it the long way. We could say if direction is next, then we know that we are adding to an image. Because now we're on the next image. We went from two and now we're on the third image. So if that's the case, we could say this dot current plus equals one or this dot current plus plus. And on the other hand, we could say this dot current minus minus because if this method was called. And the direction is not next, it had to be the previous button that was clicked. But now at this point we're taking up a handful of lines and, this is perfectly readable, absolutely nothing wrong with this, but if we want to shorten it, we could do a different route. And we could say this thought current, and we could say if the direction equals next, and in that case, this dot current plus plus, and if it's false, this dot current minus minus. And that will work as well, but if you wanna have some fun we can even shorten this a little bit more. So I am going to delete all of these and we're going to do this [UNKNOWN] here. This dot current and we're going to plus equals, and we're going to first detect whether direction equals next. That's going to be our test. And if that is the case, then we're going to cast the value to an integer. And this is a neat little trick to have. I'm gonna open up Chrome Developer Tools again. And we're gonna use these two tildes. And if you use a tilde with a boolean, take a look. That's going to convert the boolean to an integer. Type of true. And that is equal to a number at this point. So that means, by default, type of true of course is a boolean. But if we use these two tildas we can cast it to a number. And I'm sorry, type of true. Now that's been cast to a number. What about if we do false? Well, we know tilda tilda true turns to one, so tilda tilda false will be cast to zero. So, let's try this. Var dir equals next. We're creating a variable called dir and making it equal to next. And now, we're going to say tilde tilde does dir equal next. So this is our test, we are determining if the DIR, dir variable equals the value next. We know it does so that will essentially be switched out with true, and then we are casting true to an imager. Let's try it, and we get one. But what if we check to see if it's a different value like [UNKNOWN]. Well in that case, the test is going to fail. And now we are casting false to a value. And we get zero. So now we have an easy way to translate true to one and false to zero. And because zero is what we call a falsey value, we can use this to our advantage. We'll cast this to an integer, or set it equal to negative one. So let's try that out. That looks a little scary. And maybe a little less readable depending on where you are, and those are all things you need to keep in mind. We take the this.current property and we're going to add to that plus equals and let's do this one first. Dir equals 'next' so we're casting that test, true or false, to an integer. Now let's say the user clicks on the next button. This is going to be equal to true, and when we [UNKNOWN] that to an integer, that will be translated to 1. So that means dot current plus equals 1. 1 is a true value so that means this or this. So this is true, this never runs. This dot current plus equals 1. Hopefully, that makes sense. But now, let's imagine that the user clicks if I back up a few steps. They click on the previous button. Okay. Well and now, dir equals next. Nope. So that's equal to false. Then we cast false to a number. And that will cast to 0, which is a falsy value. So now we're saying this.current plus equals 0 or negative 1. Zero is a falsy value so it does translate this. Because remember, this is or. Check this value, if if it's true, return that. If this value is false, then go to the other side. This, or negative one. So in that case, we set that equal to negative one, like so. Hopefully, it seems a little confusing at first, but I promise when you've been working with these things for a while, it makes perfect sense. And the final thing I'm going to do just for convenience is wrap all of this with in parenthesis. So yeah, that looks a little scary, but it's really not. We're simply adding or subtracting one based upon the results of a test or you can stick with one of the other methods that we did before. I'm mostly trying to show you different ways that you can cast values and you can use the or. So I'm trying to wrap a lot of different little techniques up so you can learn them all at once. So at this point let's console.log this.current. Reload and we'll click on the button, and now current is 1. Now it's 2. Now it's 3. But what if we click on Prev? Now it's 2. Now it's 1. Now it's 0, perfect. So that is doing exactly what we want it to. But as we remember from the last lesson. We have to be careful about going into the negative territory. Now every time we click previous we're going to negative four, negative three and that's not very helpful because that's not what we want. We never want this.current to be below zero. So the next step is we'll say this.current and we're gonna make that equal to and we're gonna run a little test. Is this ".current" less tha 0? And that would only be the case on the condition that the user is on the first one and then they click on the previous button. And when they do, we know, "Wait a minute, -1? There is no -1 image." So in that case we need to do that little trick where we switch to the very last image in the set. So, if "this.current" is less than 0, on that condition we'll use a ternary operator. This.current, and I'll fix that typo, needs to be equal to the total number of images, and we stored that within the ing's length property, but because we're dealing with zero base, we're gonna subtract one to get the total number of images there. But now what if current is not less than zero? Well, in that case we'll set that equal to this dot current. Mod, this dot ings length. Oh, another scary one. Don't worry, we're gonna explain all of this. It looks a lot scarier than it really is. Now, the first thing that I see here is that we are referencing this dot current a bunch of times. And there's' s nothing overtly wrong with that. But why don't we have a tiny performance improvement and make it a little more readable by creating a local variable called pos and making that equal to this.current. Now we can replace this, pos, and we can also replace it right here, like so. Now you might be wondering, well why aren't we updating this value as well. And that's because the pos value only stores the value itself. It is not a pointer to the this.current property. So when we update pos, we are not updating this.current. We are updating pos. So, ultimately, what we want to do, is update the value. Of the current property. So we're going to keep that as it is. Now after we run this, let's console.log(this.current) and see what we're doing. And the first step we'll do is click Previous. And when we click it, you'll see that we correctly update the current property to three. And that's where we want it to be. If we reload and click Next. We get 1, 2. I'll click Previous back to 1, and now I click it. We're at 0, and one more time it goes back to 3 because we're doing that calculation where we say if they clicked previous and there's no more images that go that low, we're going to update the current image to be the very last one in the set. Now, if that is not the case then this will always run. This is what's executing here. I'm going to copy this and we'll paste it in here and let's replace these with actual values. Let's say the current image is two and the total number of images is four. Two mod four, that equals two. What about one mod four? That equals one. So you'll see when the number is less than the total length. Three mod 4. it's always going to return that current value. But watch what happens when we set it to 4 mod 4. Now we get 0. So that is really helpful for us, because in most of the cases we're making this dot current equal to itself. However, once we get to the very last image in the set, this thought current is four, so we're saying four mod the number of images. Four mod four and then we're setting this thought current back to zero so that we can reset. So modulus may be something that's new to you. Let's take a quick example here. Let's say that we have six modulus four, and the way we figure out what the solution is, is to figure out what the remainder is when you divide them. So I have six, modulus four. Okay. Well, four goes into six one time, one times four is four, six minus four is two, that is the solution. Six mod four, let's try it, six mod four, and we get two. Let's try another one, let's do 17. Three. All right, well in that case, three goes into 17 five times. Five times three is 15. And what is the remainder? 17 minus 15 is two. So 17 mod three is two. Let's try it. 17 mod. Three is two and you'll find that when you're doing programming, especially for things like this, that technique can be very very handy, cuz what we're doing here is, and if this is easy for you imagine that we only have this, this dot current equals this dot current. Mod the number of lengths. So if we make this easier to understand, let's say we're on two. And we're saying two equals two mod and the total number of images, four. And that would be equal to two. Or if we set that to three mod four, it's three. Or if we set that to one mod four, if we know that's one. But as soon as we get to four mod four, four mod four is zero. And so this is how we are updating the current image back to zero. So hopefully, that makes sense. If you need to, go back and review the article that I will have in the show notes on working with modulus. The last thing we'll do is return pos. So now we've mostly finished this method. But I decided that set coordinates. We're not exactly determining coordinates. We're setting the current positioning. So we're going to change that to setCurrent. So let's return to index.html, update this to setCurrent. We're going to pass in whether the user clicked on the next or previous button. We then take that value and we either if they click on next we add one to the current or we subtract one if they clicked on the previous button. And then we do a test to see, did they click the previous button but they were already on the first image? If that's the case, we're going to set the current equal to the very last image in the set. Otherwise, we will set it either to itself or back to 0. Final thing we need to do is call the transition method. I'll go back to index.html and we'll call slider.transition. So, let's try this out and see if we made any mistakes. I will bring this down and we will click on the Next button. Two, three, four. And if we did it right, we click it again and now it goes back to the very first one. Let's go back, previous, previous. There we go. And now it's working exactly the way we want it to. The only thing I notice here though is, we still have those scroll bars. And if you remember from the last lesson, that's because we set overflow to scroll so that if JavaScript is disabled the user can still view the images. But we want to get rid of that for JavaScript users. So I'll go back to index dot html and right here lets create a new variable called container and that's going to be equal to the div with a class of slider. We'll update the CSS. Like so, or you could adjust the class so you could a class called hidden and that would set the overflow and then you could use your JavaScript to remove that class. Lots of different ways to do this. Then we'll grab the children UL, like so. Now, when we create a new instance of slider rather than duplicating, we'll reference container. There we go. Create a self-invoking anonymous function, we'll take all of this and put it inside there. And the last thing I'll do is rather than showing the navigation right here, why don't we let the constructor function take care of that. When we grab the navigation, we set a navigation property and make it equal to whatever the user passed in, which in this case is the div with an id of slider nav. We're going to go ahead and show that and even though we're showing it we're still returning that object into the nav property. So lets go back and try this one more time. Refresh the scroll bars are gone. Next, next, next, good. Let's simulate how it will look if JavaScript is disabled. So I'm going to remove all of our scripts. Try it one more time. And they simply get scrolling. And that'll be just fine for them. But if JavaScript is enabled, we can give them a slightly better system for filtering through the images nicely. Another thing I'll say in this lesson is, the truth is for a slider, you probably don't need to go this route. But it's important to take a very small step when learning about prototypal inheritance, because many times articles you read will focus on a car or a person to demonstrate an object and how it can be extended or it can inherit from other objects. So this gives us a nice way to have a real world example. We have our slider constructor function. We add these extra methods that all slider instances will inherit from. And then because each method performs exactly one thing, we can focus a little bit more. And when we need to debug, for example, the setCurrent method, we're not having to deal with any extra dom manipulation or access or these animation methods. We can stick with the core of what that method is doing. So in this lesson, not only have we reduced the amount of code from the previous lesson, by about 10 or 15 lines or so, we have also structured it in a far cleaner manner.

Back to the top