diff --git a/javascript/organizing_your_javascript_code/objects_and_object_constructors.md b/javascript/organizing_your_javascript_code/objects_and_object_constructors.md index 375e9908b11..6bc1171cb6a 100644 --- a/javascript/organizing_your_javascript_code/objects_and_object_constructors.md +++ b/javascript/organizing_your_javascript_code/objects_and_object_constructors.md @@ -1,103 +1,20 @@ ### Introduction -In our JavaScript fundamentals course, you should have learned the [basics of using objects](https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/fundamentals-part-5) to store and retrieve data. Let's start with a little refresher. - -There are multiple ways to define objects but in most cases, it is best to use the **object literal** syntax as follows: - -```javascript -const myObject = { - property: 'Value!', - otherProperty: 77, - "obnoxious property": function() { - // do stuff! - } -}; -``` - -There are also 2 ways to get information out of an object: dot notation and bracket notation. - -```javascript -// dot notation -myObject.property; // 'Value!' - -// bracket notation -myObject["obnoxious property"]; // [Function] -``` - -Which method you use will depend on context. Dot notation is cleaner and is usually preferred, but there are plenty of circumstances when it is not possible to use it. For example, `myObject."obnoxious property"` won't work because that property is a string with a space in it. Likewise, you cannot use variables in dot notation: - -```javascript -const variable = 'property'; - -myObject.variable; // this gives us 'undefined' because it's looking for a property named 'variable' in our object - -myObject[variable]; // this is equivalent to myObject['property'] and returns 'Value!' -``` - -If you are feeling rusty on using objects, now might be a good time to go back and review the content in our [object basics lesson](https://www.theodinproject.com/lessons/foundations-object-basics) from our JavaScript Basics course. +Now that you've got a basic understanding of *why* and *how* you might use objects to organize data and functionality, it's important to learn some basic strategies for creating duplicates (often called *instances*) of objects, and using existing types of objects as a base for creating new ones through *inheritance*. ### Lesson overview This section contains a general overview of topics that you will learn in this lesson. +- Explain how the `this` keyword behaves in different situations. - How to write an object constructor and instantiate the object. - Describe what a prototype is and how it can be used. - Explain prototypal inheritance. - Understand the basic do's and don't's of prototypal inheritance. -- Explain what the `this` keyword is. - -### Objects as a design pattern - -One of the simplest ways you can begin to organize your code is by grouping things into objects. Take these examples from a 'tic tac toe' game: - -```javascript -// example one -const playerOneName = "tim"; -const playerTwoName = "jenn"; -const playerOneMarker = "X"; -const playerTwoMarker = "O"; - -// example two -const playerOne = { - name: "tim", - marker: "X" -}; - -const playerTwo = { - name: "jenn", - marker: "O" -}; -``` - -At first glance, the first doesn't seem so bad... and it actually takes fewer lines to write than the example using objects, but the benefits of the second approach are huge! Let me demonstrate: - -```javascript -function printName(player) { - console.log(player.name); -} -``` - -This is something that you just could NOT do with the example one setup. Instead, every time you wanted to print a specific player's name, you would have to remember the correct variable name and then manually `console.log` it: - -```javascript -console.log(playerOneName); -console.log(playerTwoName); -``` - -Again, this isn't *that* bad... but what if you *don't know* which player's name you want to print? - -```javascript -function gameOver(winningPlayer){ - console.log("Congratulations!"); - console.log(winningPlayer.name + " is the winner!"); -} -``` - -Or, what if we aren't making a 2 player game, but something more complicated such as an online shopping site with a large inventory? In that case, using objects to keep track of an item's name, price, description and other things is the only way to go. Unfortunately, in that type of situation, manually typing out the contents of our objects is not feasible either. We need a cleaner way to create our objects, which brings us to... ### Object constructors -When you have a specific type of object that you need to duplicate like our player or inventory items, a better way to create them is using an object constructor, which is a function that looks like this: +Manually typing out the contents of our objects with Object Literals is not always feasible. When you have a specific type of object that you need to duplicate, a better way to create them is using an object constructor, which is a function that looks like this: ```javascript function Player(name, marker) { @@ -356,10 +273,9 @@ If we had used `Object.setPrototypeOf()` in this example, then we could safely e 1. Read up on the concept of the prototype from the articles below. 1. Read the article [Understanding Prototypes and Inheritance in JavaScript](https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript) from Digital Ocean. This is a good review of prototype inheritance and constructor functions, featuring some examples. 1. To go a bit deeper into both the chain and inheritance, spend some time with [JavaScript.Info's article on Prototypal Inheritance](http://javascript.info/prototype-inheritance). As usual, doing the exercises at the end will help cement this knowledge in your mind. Don't skip them! Important note: This article makes heavy use of `__proto__` which is not generally recommended. The concepts here are what we're looking for at the moment. We will soon learn another method or two for setting the prototype. -1. You might have noticed us using the `this` keyword in object constructors and prototype methods in the examples above. - - 1. [JavaScript Tutorial's article on the `this` keyword](https://www.javascripttutorial.net/javascript-this/) covers how `this` changes in various situations. Pay special attention to the pitfalls mentioned in each section. -1. Read the article [[[Prototype]] vs __proto__ vs .prototype in Javascript](https://medium.com/@eamonocallaghan/prototype-vs-proto-vs-prototype-in-javascript-6758cadcbae8) +1. You might have noticed us using the this keyword in object constructors and prototype methods in the examples above. + 1. [JavaScript Tutorial's article on the `this` keyword](https://www.javascripttutorial.net/javascript-this/) covers how this changes in various situations, including examples with constructor functions and the prototype. +1. Read the article [[[Prototype]] vs \_\_proto\_\_ vs .prototype in JavaScript](https://medium.com/@eamonocallaghan/prototype-vs-proto-vs-prototype-in-javascript-6758cadcbae8). @@ -380,4 +296,3 @@ This section contains helpful links to related content. It isn't required, so co - This [`Object.create` method video by techsith](https://www.youtube.com/watch?v=MACDGu96wrA) provides another point of view on how to use `Object.create` to extend objects by setting the prototype. - The first answer on this StackOverflow question regarding [defining methods via the prototype vs in the constructor](https://stackoverflow.com/questions/9772307/declaring-javascript-object-method-in-constructor-function-vs-in-prototype/9772864#9772864) helps explain when you might want to use one over the other. - [Interactive Scrim on objects and object constructors.](https://scrimba.com/scrim/co2624f87981575448091d5a2) -- Check out this video explanation on the [`this` keyword from DevSage](https://www.youtube.com/watch?v=cwChC4BQF0Q) that gives a different perspective on how its context changes, as well as scenarios in which `this` behaves unexpectedly. diff --git a/javascript/organizing_your_javascript_code/organizing_code_with_objects.md b/javascript/organizing_your_javascript_code/organizing_code_with_objects.md new file mode 100644 index 00000000000..47d61b821d0 --- /dev/null +++ b/javascript/organizing_your_javascript_code/organizing_code_with_objects.md @@ -0,0 +1,339 @@ +### Introduction + +In our JavaScript fundamentals course, you should have learned the [basics of using objects](https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/fundamentals-part-5) to store and retrieve data. In this lesson, we'll start with a little refresher, then explore using objects in more detail. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- Using objects to organize data. +- Using objects to organize functionality. +- Object methods and the `this` keyword. +- Public and private interfaces. + +### Refresher + +There are multiple ways to define objects, but in many cases **object literal** syntax is used as follows: + +```javascript +const myObject = { + property: 'Value!', + otherProperty: 77, + "obnoxious property": function() { + // do stuff! + } +}; +``` + +There are also 2 ways to get information out of an object: dot notation and bracket notation. + +```javascript +// dot notation +myObject.property; // 'Value!' + +// bracket notation +myObject["obnoxious property"]; // [Function] +``` + +Which method you use will depend on context. Dot notation is cleaner and is usually preferred, but there are plenty of circumstances when it is not possible to use it. For example, `myObject."obnoxious property"` won't work because that property is a string with a space in it. Likewise, you cannot use variables in dot notation: + +```javascript +const variable = "property"; + +// 'undefined' because it's looking for a property named 'variable' in our object +myObject.variable; + +// this is equivalent to myObject['property'] and returns 'Value!' +myObject[variable]; +``` + +### Objects as a data structure + +You've already been introduced to the basic use of a JavaScript object - storing related information with key/value pairs. This is one of the simplest ways you can begin to organize your code! Take these examples from a 'tic tac toe' game: + +```javascript +// without objects +const playerOneName = "tim"; +const playerTwoName = "jenn"; +const playerOneMarker = "X"; +const playerTwoMarker = "O"; + +// with objects +const playerOne = { + name: "tim", + marker: "X" +}; + +const playerTwo = { + name: "jenn", + marker: "O" +}; +``` + +At first glance, the first doesn't seem so bad.. and it actually takes fewer lines to write than the example using objects, but the benefits of the second approach are huge! Grouping related data together into objects allows you to pass the data around easily. Let me demonstrate: + +```javascript +function printName(player) { + console.log(player.name); +} +``` + +This is something that you just could NOT do with the example one setup. Instead, every time you wanted to print a specific player's name, you would have to remember the correct variable name and then manually console.log it: + +```javascript +console.log(playerOneName); +console.log(playerTwoName); +``` + +Again, this isn't *that* bad... but what if you *don't know* which player's name you want to print? + +```javascript +function gameOver(winningPlayer){ + console.log("Congratulations!"); + console.log(winningPlayer.name + " is the winner!"); +} +``` + +Or, what if we aren't making a 2 player game, but something more complicated such as an online shopping site with a large inventory? In that case, using objects to keep track of each particular item's name, price, description and other things is the only way to go. You will continue to use and see objects used in this way throughout the curriculum. + +### Objects as a design pattern + +The grouping power of objects isn't just useful for organizing data - it's useful for organizing *functionality* as well! Using objects for this purpose is one of the core tenants of Object Oriented Programming (OOP). + +The introductory paragraph for Object Oriented Programming on Wikipedia says this: + +> Object-oriented programming (OOP) is a programming paradigm based on the concept of objects, which can contain data and code: data in the form of fields (often known as attributes or properties), and code in the form of procedures (often known as methods). In OOP, computer programs are designed by making them out of objects that interact with one another. + +Essentially, what this means is that code can be organized into objects that contain not only data, but also **methods** (or functions in an object) that interact with that data. + +Nearly *anything* you can think about can be described as an object. To do so, all you have to do is ask yourself is "What properties (physical or conceptual) does my thing have?", and "How can I interact with it?". The properties or attributes of a *thing* are expressed as properties, and the ways you can interact with that thing are expressed as methods. + +Let's take an example of some *thing* - we'll choose a lightbulb. A lightbulb can have a color, and it can be in either an 'on' state, or an 'off' state. These might be expressed as properties of a lightbulb object: + +```javascript +const lightbulb = { + lightColor: 'fluorescent white', // this lightbulb is white, + lit: false // and is currently 'off' +} +``` + +You may want to have the ability to switch a lightbulb from it's unlit state to it's lit state, or vice-versa. To do that, you might add a *method*. + +The easiest way to get started creating methods to interact with your objects might be combining Object Literal syntax with JavaScript's `this` keyword. The `this` keyword is used to refer to the object a particular method is called from. + +The following is an example of using the `this` keyword to add two methods to our object, `switchOn`, and `switchOff`: + +```javascript +const lightbulb = { + lightColor: 'fluorescent white', + lit: false, + + // shorthand syntax for adding methods to objects + switchOn() { + this.lit = true + }, + switchOff() { + this.lit = false + } +} + +lightbulb.switchOn() +lightbulb.lit // true - we switched it on +``` + +These methods use the `this` keyword to refer to the object they get called from (`lightbulb`). The `this` keyword can be used to access and modify properties of an object in exactly the same way you would for any other variable that points to an object. + +Feel free to copy this code in the console and experiment with it! If you're inclined, perhaps you could create a method to change the color of the light, as if it's one of those fancy RGB LEDs those gamer nerds and keyboard enthusiasts seem to like so much. + +Moving past physical items, we could also try to describe something a little bit more abstract like a game as an object. Since we've already explored Rock Paper Scissors in Foundations, let's use that as an example. + +A rock paper scissors game might involve a couple basic things: + +- Players' scores +- The ability to play a round (and playing a round should update a player's score) + +And might also include a couple nice-to-haves + +- The ability to determine the current winning player +- The ability to restart the game + +So, at its most basic, an object that represents the game might look something like this (assuming we're playing against a computer player): + +```javascript +const rps = { + playerScore: 0, + computerScore: 0, + playRound(playerChoice) { + // code to play the round... (and update the scores when a player wins) + } +} +``` + +And if we fleshed it out, our object may come out to look something like this: + +```javascript +const rps = { + playerScore: 0, + computerScore: 0, + playRound(playerChoice) { + /* + 1. if an invalid choice is chosen, throw an error + 2. get the computer's choice + 3. determine the winner using the player's choice and the computer's choice + - apply points if necessary to the playerScore/computerScore properties + - return who won ('player', 'computer', or 'tie') + */ + }, + getWinningPlayer() { + // return the player with the most points ('player', 'computer', or 'tie') + }, + reset() { + // reset both player's scores to 0 + } +} + +rps.playRound('rock') // returns 'player' if we win... +rps.playerScore // ...and our score would have increased + +// We also have the ability to check the winning player and reset the game at any time +rps.getWinningPlayer() // 'player', if we won above round +rps.reset() +``` + +Take the time to fill in the blanks for the code above, in order to make it work as the comments describe. Use your new knowledge of the `this` keyword to update the player's scores! A working object may be useful for your understanding in the following paragraphs, and provide you with a space to experiment with the ideas in this lesson. + +### Private methods/properties + +Once you've got a working object that allows you to play rounds of Rock, Paper, Scissors, you may be look at this code and feel that you personally prefer to split your code between more functions/methods than you see here, but also recognize that those functions may not really be a useful interaction point for anyone using your object. + +But, there is no rule saying that you can't add those functions to your object as well! A common convention is to prefix methods and properties that you don't intend other people to use with an underscore (`_`). This convention conveys to others that "These things are meant to be used internally by this object, please interact with the other available methods and properties on this object's interface instead" + +Another name for properties like this might be **private properties**/**private methods**, and even though object literal syntax doesn't provide a way to truly *prevent* people from accessing them, you will learn in later lessons about other methods of creating objects that *can*. + +Some additional methods or properties one might add to *this* object may be: + +- A method that grabs a random computer choice +- A method that determines if two choices are a tie +- A method that determines if the player is the winner +- A list containing all the valid choices + +Let's see what that looks like! + +```javascript +const rps = { + _options: ['rock', 'paper', 'scissors'], + _getRandomChoice() { + // returns 'rock', 'paper', or 'scissors' randomly + }, + _isTie(playerChoice, computerChoice) { + // determines if two choices are a tie, and returns `true` or `false` + }, + _isPlayerWinner(playerChoice, computerChoice) { + // returns `true` if the player is the winner, and `false` if not + }, + playerScore: 0, + computerScore: 0, + playRound(playerChoice) {/* ... */}, + getWinningPlayer() {/* ... */}, + reset() {/* ... */} +} +``` + +Private properties/methods aren't strictly required, but they can help make the intended use of the object more understandable, and when used thoughtfully, even protect certain properties (like the player's scores) from being modified in ways that you may not have intended. Back off, cheaters! + +If you feel inclined to create and use any of these new methods or properties in your previous `playRound`, `getWinningPlayer`, or `reset` methods, have at it! You may appreciate some readability gained from extracting logic out into private methods. + +### Public interfaces + +The methods and properties you *do* intend for others to use on your objects might be considered your object's **public interface**, or **public properties**/**public methods**. Having a good, well thought out interface on your objects is important - not only because it makes your object pleasant to use by you and others, but also to keep objects flexible and extensible in the future. + +This idea of grouping related functionality within an object is *extremely powerful*, and can often result in more organized, understandable code. + +Furthermore, with the various object creation methods you'll learn throughout this section of the curriculum, you'll be able to easily duplicate and reuse objects like these! Imagine you have an app where users can create and play *multiple* rock-paper-scissor games at once. Managing the data and interacting with each of those games would be no sweat with objects! + +