forked from yilenpan/week3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
day2ScopesAndClosures.js
189 lines (157 loc) · 12.7 KB
/
day2ScopesAndClosures.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Welcome to the exercises for scopes and closures!
// Much like yesterday, a lot of today is going to involve explaining your code to your pair. Follow a similar pattern to yesterday: Think through what you expect the code to do, explain that your pair, and then test it out to see what it actually does (with clear labels!!). Once you see the actual results (which will often be different than your expectations), explain why we're seeing that behavior to your pair.
// Today we're going to use a restaurant's kitchen as our example. We're going to have a couple different areas to our fancy kitchen:
// bbqMeatCooker
// vegetarianCorner
// pastryConcoctions
// Clearly, each of these will have their own set of tools, and we need to make sure they share some things (a common restaurant name) and not others (say the cutting board from the bbqMeatCooker and the vegetarian friendly one from vegetarianCorner).
// First, let's put the name and address of our restaurant in the global scope.
var restaurantName = 'Telegraph Cook';
var address = '1600 Shattuck Ave';
// Next we'll create the vegetarianCorner. We want to hold some variables here that are only accessible inside vegetarianCorner, so we can keep it vegan friendly. How do we keep some variables private in JS? Local Scope! And what's the only way to create local scope in JS? Inside a function body, using the var keyword! So we'll create a function for our vegetarianCorner.
var vegetarianCorner = function() {
// We have a dedicated veggie chef (yeah, we're a super fancy restaurant). Let's set her name here:
var chef = 'Amanda';
// And a favorite dish:
var favoriteDish = 'saag paneer';
// To warm up, what do you expect chef to be if we console log it right here?
// Explain to your partner the process the JS interpreter will go through to figure out what chef is.
// My explanation would go something like "First, it encounters chef, which it recognizes is a variable since it's not wrapped in quotes. It goes to look up what that variable is equal to. It looks in it's own scope first, and finds chef there! chef is equal to the string 'Amanda', and so it console.logs that string."
// And what would we expect address to be right here?
};
vegetarianCorner();
// Now that we're back in the global scope (we're no longer inside the function body), what is chef?
// Explain why to your pair.
// What is address in this scope? Why?
// Great! Now let's create the rival bbqMeatCooker section of our kitchen.
var bbqMeatCooker = function() {
var chef = 'Raka';
var favoriteDish = 'grilled salmon';
// Whoa, see what we did there? We have the same variable names in this function as we do in the vegetarianCorner function. Are they going to conflict at all?
// Talk through this with your pair, then console.log chef and favoriteDish (with labels saying that we're inside bbqMeatCooker) to see the results!
// If you forget what we mean by labels, check out this slide, and the one below it: http://slides.com/climbsrocks/debugging#/7/3
// Each function body has it's own scope. Variables created in there using the var keyword will ONLY exist within that scope. So everything inside our bbqMeatCooker has absolutely no idea what's happening inside our vegetarianCorner, as long as we use the var keyword.
// Our bbqMeatCooker got a bit too smoky to share a kitchen with everything else, so we had to move it out back.
var address = 'shed in the back yard at 1600 Shattuck Ave';
// What happens if we console.log address here?
// Explain why to your pair!
// My explanation would go something like "the interpreter goes to look up what the address variable is equal to. Just like in our topSecretClearance example, it first looks in it's own scope, before heading out into the broader scopes. While checking in it's own local scope, it finds address! We don't have to look any further, so we log 'shed in the back yard...', and never have to reach out to the global scope to try to find this variable."
// Ok, we realized pretty quickly that we need something slightly nicer than a shed to house our bbqMeatCooker in. So we move the address again:
address = '1610 Shattuck Ave';
// What is address here? What is the process by which it figures that out? Talk through this with your partner- this will likely trip up some of you!
};
bbqMeatCooker();
// Now that we're back in the global scope, what is address here? Have we changed it at all based on what we did inside bbqMeatCooker?
// Explain why or why not to your pair, then console.log address here (with a label!), and explain why again once you've seen the results.
// Time to create our next area, pastryConcoctions!
var pastryConcoctions = function() {
// Our pastry chef is a bit haughty.
chef = 'Michael';
address = '1517 Shattuck Ave';
// What are the values of chef and address here?
};
pastryConcoctions();
// What are the values of chef and address here?
// Explain why to your pair!
// Talk through what happens when we try to access address inside of vegetarianCorner and bbqMeatCooker.
vegetarianCorner = function() {
var chef = 'Amanda';
var favoriteDish = 'saag paneer';
// What is the value of address here?
// Why?
};
vegetarianCorner();
bbqMeatCooker = function() {
var chef = 'Raka';
var favoriteDish = 'grilled salmon';
var address = 'shed in the back yard at 1600 Shattuck Ave';
address = '1610 Shattuck Ave';
// What is the value of address here?
// Why?
// What is the value of chef here?
// Why?
window.chef = 'Raka rules!';
// What is the window object? What happens when we set properties on it?
// What is the value of chef right here after that update?
//
};
bbqMeatCooker();
// Ok, by now you should have solidified on a couple of key points:
// 1. Each function body has it's own local scope.
// 2. Each function invocation gets it's own scope too, but we'll get to that next :)
// 3. To create a 'hidden' variable inside that local scope, you must use the var keyword to make sure the variable exists in that local scope.
// 4. Not using the var keyword modifies global variables.
// 5. functions will first look to their own local scope before reaching out to the global scope. If two variables with the same name exist in local and global scope, the function will first find the value in the lcoal scope, and never have to reach outside to the global scope.
// 6. The window object is synonymous with global scope. Properties set on the window object are available in the global scope.
// Whew, that's a lot of learning so far!
// One of the key uses of scoping and closures is to 'stash' or 'save' a variable for later use.
// When might this be useful for us?
// When a variable is changing frequently, and we want to save it's current value.
// One example of this is inside for loops. We might want to save what i is for that particular iteration.
// If we stash i inside a local scope by passing it into a function, it will get to exist there, untouched and unchanged, until we do something inside that function later on.
// There are certain concepts in programming that are incredibly powerful tools for complex situations.
// Oftentimes, these concepts seem a bit useless in less complex situations.
// This is one of those moments.
// Our example below won't go into this level of complexity, but let me illustrate a moment where I used this pattern of using closures to save the current value of i inside a for loop.
// And hopefully then you can see how this is a valuable pattern to learn, even if you don't see an immediate use for it.
// I was working through a table in a database recently. In that table, we had the locations for each of our users. But the problem was, they just typed in a string of their location ('New York City', 'NYC', 'New York, NY', etc.), rather than latitude and longitude which we could plot on a map.
// To get the latitude and longitude, I had to iterate through the entire table, grabbing each row one by one, and then make a request to an external API to get the lat/long for that row.
// My connection let me make three concurrent requests, which was great, because I had 60,000 users to go through!
// But this mean that my value for i, which represented the row number, was changing constantly, and I couldn't count on it being stable by the time I got data back from the external API.
// To get around this, I stashed i into a local scope by passing it into a function, and inside that function doing the API call.
// Each function invocation gets it's own local scope. You can think of it kind of like a secret, peaceful cave. The rest of the program can be changing like mad, but that doesn't affect things inside our peaceful little cave.
// So even though my row number was changing once every millisecond or so, and my API requests took several dozen milliseconds to complete, the value of i that I'd stashed into a local scope, was totally protected, unchanged, stable.
// Let's see what this looks like in practice!
var iSaver = function(index, val) {
return function() {
console.log('inside of our closure scope, i is still:',index,'val is:',val);
};
}
var testArr = [1,2,3,4];
for (var i = 0; i < testArr.length; i++) {
if(i === 1) {
var func1 = iSaver(i, testArr[i]);
}
}
// What is i at this point in our code?
// console.log('at this point in our code, i is:', i);
// What do you expect to get when we invoke func1?
// func1();
// what iSaver returns to us is a function. Which is really just a fancy object.
// We can push objects into arrays, and then access their values, doing something like arr[i].propName.
// We can also push fancy function objects into arrays, and then invoke them, doing something like arr[i]().
// Remember that JS is super modular.
// First, it will evaluate the arr variable, which is a link pointing to a certain spot in memory.
// Then, it will enter the brackets.
// Next, it will evaluate whatever is inside those brackets, and look up the results of that as a property name inside our array.
// In this case, it will recognize that i is a variable (since we don't have quotes around it), and evaluate what that variable is equal to. Let's suppose i is 1.
// It will then grab the thing at the 1 index position in our array.
// Now arr[i] has evaluated to what that thing is in our array. In this case, it is a function.
// We can then invoke that function with the open and closed parens ().
// Let's put that pattern to use!
// Create an empty array called closureFuncs.
// Create a new for loop.
// Let's create a new iSaver function for every even i in the testArr array, and push that function into closureFuncs.
// Then, once you're done with the entire for loop, log what the value of i is outside the for loop.
// And then go through and invoke each function in our closureFuncs array.
// Remember, when in doubt, console.log things with labels!
// What you should see logged out is
// 'inside of our closure scope, i is still: 0, val is: 1'
// 'inside of our closure scope, i is still: 2, val is: 3'
// Try doing this again with a longer array! Or try building out different logic for what we save into our closure scopes. Generally, do what you can to get comfortable with saving values for future access into closure scopes.
// Extra Credit:
// Using the module pattern, create a toaster.
// The cool part about this is that you get to decide what the user should be able to access/modify, and what we'll keep private from them.
var toaster = function() {
//some private properties here
return {
// public properties and methods here.
// Those public methods can access and change the private variables.
}
};
// Extra Credit2:
// Let's imagine you're heading off to a huge family reunion. And you nicely volunteered to make name tags for everyone! Oddly, your family is super well organized, and everyone has the same last name, Thinklestein.
// Create a function called nameMaker that takes in a first name as it's only argument, and then returns the robotic greeting "Welcome to the party, FIRSTNAME Thinklestein.", where FIRSTNAME is replaced by the person's first name.
// Now we're going to generalize this:
// First, let's take the minor step of removing the hard-coded value of 'Thinklestein' from the function, and allow the user to pass in both a first and last name.
// Next, Create an entirely new function called reunionGreeter. We've found that we're just so good at greeting people at reunions that we decide to expand and do this for more families! reunionGreeter will let us modularize our code to the point we can do this. reunionGreeter takes in a last name, and returns a function that greets people with the first name they pass in.