More Explorations in JavaScript Syntax
00:00 In the previous lesson, I covered a wide variety of JavaScript syntax. In this lesson, I will cover a wide variety of JavaScript syntax. It’s like that black cat in the matrix. I’m the king of dated references.
00:13
JavaScript does not have an enumeration. You can mimic the idea, though, by freezing an object. A frozen object does not allow you to add, remove, or alter any of its attributes. It doesn’t complain, it will let you perform the action, it just doesn’t have any effect. Note that this is different from the const
keyword I spoke about before. .freeze()
prevents changes inside the object.
00:36
const
prevents changing what a variable points to. I’ve demonstrated arrow functions in previous lessons, but here’s a quick recap. The first two examples here use the function
keyword, either creating a named function in the global namespace or assigning it to the add()
variable in the local context.
00:56
This third example is the arrow function shortcut. There are some subtle differences with scoping and the this
keyword, which I’ll describe later when I talk about language quirks.
01:05
But otherwise, this is equivalent to the above two. Using the arrow (=>
) means you no longer need the function
keyword and the return value is implicit.
01:15 Arrow function shortcuts typically get used for anonymous expressions like lambdas in Python but unlike lambdas they could be multiline and have side effects, like in this fourth example.
01:27 Because the curly brackets are used to define the code block of the function, if you want to return an object from an arrow function, you need to wrap it in parentheses.
01:36
This last example returns an object that has a key called result
, whose value is a + b
. So that the parser doesn’t get confused, this is wrapped in parentheses.
01:48 JavaScript has a concept called a labeled statement that can be used when continuing or breaking from a loop. It allows you to continue or break to the given label.
01:57
Why am I bringing up this tangent? Well, those labels are defined with a name and a colon. Without the parentheses in the fifth example, the curly brackets are seen as the arrow function definition and the result:
is seen as a label.
02:11 You won’t get an object back and it is still valid JavaScript, so you won’t get an error. Tricky bug defined. Like Python, JavaScript functions support default values and a variable number of parameters.
02:26
Let’s go to the REPL and see these in action. First, I will define a function called greet()
with a default value for the name
argument.
02:40
Now calling greet()
with a value.
02:45 And then without one. Here’s a more complicated example.
02:57 Unlike Python, the default arguments to a function in JavaScript get resolved on every call to the function. This means you can do calculations and use mutable types in the default arguments.
03:08
This method returns the array of the third parameter with the first and second parameter pushed inside of it. If c
isn’t given, the array returned starts out empty before a
and b
are pushed inside. And if b
isn’t given, it is set to the value of a + 1
.
03:25
Calling it with 1
means the empty array from c
is populated with 1
and 1 + 1
.
03:35
Here it is again with a different value. Note that the array of c
has no continuity between calls. It gets reset to empty.
03:47
If I pass in an array for the value of c
, then the a
and b
arguments are pushed on the end of it. Unlike Python, there is no named calling.
03:59
And, like a lot of things in JavaScript, it parses. It doesn’t complain, but it doesn’t do what you’d expect. Here, although I’ve named c
explicitly, JavaScript doesn’t care.
04:10
It treats it as the second argument, and you will end up with the real c
being an empty array and the value of a
being 41
and the value of b
being the array [9, 8, 7]
.
04:22 This is actually one of my frustrations with JavaScript and why I prefer Python. I’m okay with different language design decisions, but the “anything goes without complaining” thing can be painful as JavaScript.
04:34
As JavaScript doesn’t support named parameters in the function calls, I would expect it to give me a SyntaxError
, a warning, something—anything!
04:41 Instead, you end up tracking bugs. I switch back and forth between Python and JavaScript a lot when I code, which makes it easy for me to make this kind of mistake.
04:51 When I try to use Javascript-y things in Python, Python complains. When I try to do Python-y things in JavaScript, it just creates a bug. Okay. Moving on from the overly opinionated old man rant.
05:04 How about functions with a variable number of arguments?
05:22
JavaScript does a variable number of arguments through the “rest” operator, as in “Give me the rest of it.” That’s the triple dots (...
). Here, I’ve defined a function that can take any number of arguments.
05:34
I then sum them up using the .reduce()
function, then take the average. If nothing is passed in, the average is arbitrarily chosen as 0
.
05:42
The rest operator causes the numbers
argument to work like an array. Let’s try calling it a few times.
05:56 Looks all average-y. Good work. There’s one more place that the rest operator can be used, which is to access the remaining parts of an array. Flashback to some fruit.
06:16
This array was composed using the rest operator, acting on the redFruit
array.
06:23 The end value is a composed result.
06:28
In Python, you can put a tuple on the left-hand side of an assignment and destructure the right-hand side’s contents. JavaScript supports something similar but uses a raise
instead. Here, the fruits
array contains three items: 'apple'
, 'banana'
, and 'orange'
. On the second line, the array containing variables a
, b
, and c
are assigned to the fruits
array, a
, b
, and c
now contain 'apple'
, 'banana'
, and 'orange'
, respectively.
06:55
JavaScript also supports doing this with objects. In the second example, a person is created, then deconstructed into the fullName
and age
parameters.
07:05
fullName
and age
now contain the values of the attributes of the person
object.
07:11
This has nothing to do with the Python with
statement and is completely different. That’s unfortunate for those of us who use both languages.
07:19 This statement isn’t very common. In fact, if you turn on JavaScript’s strict mode, it disallows it. Strict mode is a way of turning some of the silent failings I spoke about earlier into actual errors. It was introduced in ES5 but did weird things to browsers that didn’t support it.
07:36
Support is pretty much across-the-board now but if you’re coding for older browsers, you may get into trouble. Anyhow, if you’re in sloppy mode—that’s what the cool kids term the opposite of strict—the with
keyword is like a code block based destructuring.
07:51
In the example here, with person
makes variables available in the block named for each of the attributes of person
. I wouldn’t recommend using this feature, it’s not very common anymore, but thought it should be explained in case you came across it.
08:07 JavaScript ES6 introduced iterables, iterators, and generators into the language. Python has equivalent concepts. If you’re not familiar, an iterable is anything that can be iterated over. Yeah, I know that’s not quite helpful. When you iterate over something, you’re getting part of it out in sequence.
08:26
When you use a for
loop to get the components of a list in Python, you’re iterating over it. The list
object conforms to the iterator protocol.
08:36 A generator is a special kind of function that returns a suspended generator object. Yeah, I did it again. Another definition based on its definition. Think of a function that returns a list. That function has to create the entire list in memory, which it then returns. A generator is a way around that.
08:53
Instead of returning the whole list, it returns a promise about the list. Iterators then say to the generator, “Give me the next item in the sequence,” and the suspended generator object complies. The yield
keyword in Python is what does this magic trick of suspending the generator and returning the value.
09:13 Generators are powerful tools that can save a lot of memory when dealing with large amounts of data. Let me show you this concept in Python and then compare it to how it is done in JavaScript.
09:25
I’m going to write a quick generator function that returns random values between 0
and 100
. First off, I’ll need to import randint
from the random
module.
09:37 Declaring a generator function in Python is no different than any other.
09:50
The key part of this is the yield
keyword. yield
indicates that this is a generator function. When the function is called, instead of doing anything, it will return an iterable generator object.
10:02
When the object is iterated on, the yield
keyword is what returns the next value in the sequence. In this case, it will be a random integer. Now I’ll call it the function.
10:17
See? x
contains a generator object. To get the next item in the sequence from the generator, I could put this in a for
loop, but all that loop actually does is use the built-in function next()
, so I’ll call that directly here. And again, and again, one more time.
10:38
This function stops the while
loop after count
iterations. When I called the function, count
was 3
, so I got three random integers in sequence.
10:47
The fourth iteration has the function leave the while
loop and return. When an active generator returns, it throws a StopIteration
exception signaling that it is done. If this were inside of a for
loop, the for
loop would see this exception and leave the loop.
11:03
Since I’m not in a for
loop, I see the exception. Any completion of the generator function will cause the same result. If you need to short circuit your iterator, you can use return
in your function. That’s the Python way.
11:17 Now, on the right-hand side, the JavaScript equivalent.
11:32
Declaring a generator in JavaScript does require a change to the function definition. The star (*
) here tells the parser that it is a generator.
11:40 I’m not a fan of this because JavaScript is very C-like, and this means something completely different in C. But nobody asked my opinion when they made the language spec. Inside the function, it’s pretty similar to Python.
11:52
There’s a local variable called sent
that is used to track the number of times the function has yielded. The yield
statement sends out the next item in the sequence and there’s a while
loop to do it count
number of times. There’s no randint()
method in JavaScript, so I’m doing a bit of math on the random()
function that returns a float.
12:12 Just like Python, you call the function and get back a generator object.
12:19 Instead of using a built-in function, JavaScript uses a method on the generator object itself. I kind of like this idea. It feels more logical to me. Oh no, did I just say I liked something in JavaScript better than Python?
12:33 Let’s agree as friends to never mention that I did that, okay? Moving on. Notice that what comes back isn’t the random value, but an object. The object has two attributes, the value and a Boolean to indicate whether the iterator is finished.
12:48
The Python way of dealing with this feels less clunky to me. Okay, that’s better. The universe is in balance again. Calling .next()
another time, and again, and the last time. Here, you see the empty value and the done
flag set to True
, the equivalent of the StopIteration
exception in the Python world.
13:13
How about if you want your generator to call a generator? You can do that as well. Python uses the yield from
keyword to achieve this. on the left-hand side, more Python.
13:37
This is similar to the code from before, but this time I yield from a list of random integers instead. Calling the generator with a count of 1
.
13:49
First .next()
, next .next()
. Hm, what happened here? The count was 1
. Well, the count was 1
, but the yield from
is like yielding in a loop.
14:00 As there were three things in the list being yielded from, there will be three results.
14:08
And there’s the expected StopIteration
. Now, on the right-hand side for the JavaScript equivalent.
14:30
Somewhat consistently, JavaScript uses the star operator (*
) to indicate yielding from a generator. I’d be remiss if I didn’t complain about this being painful for C programmers, but I think I did that already.
14:42
The rest of it is the same. Call the function, get a suspended object, .next()
the object, .next()
the object, .next()
the object, and the last time with the done
flag set to True
.
15:03 Both Python and JavaScript also allow you to send things into the iterator. This can be useful for producer/consumer patterns in coroutines. Let me quickly show you the idea in JavaScript.
15:19 Here’s a JavaScript function. It’s so tiny, it’s almost cute. Call the generator.
15:27
Here, I called .next()
on the suspended object, but passing in a parameter at this point the parameter does nothing. What you’re seeing is the result of the yield
from the function.
15:41
On the second call, the parameter is getting used. Think of this as the yield
statement returning the parameter that you passed into the call to .next()
.
15:50
The string 'pong'
is sent back from the yield, and as there was only the one yield
, the next item in the sequence is the end. The parameter that was passed back from the call to .next()
is then returned.
16:03
The result of the next call includes 'pong'
and the done
flag set to True
.
16:11 Like Python, JavaScript supports asynchronous functions. I’m not going to spend a lot of time on this simply because asynchronous programming is a complicated beast.
16:19
You’re definitely on the part of the map that says “Thar be dragons!” To create a function that runs asynchronously, you declare it with the async
keyword.
16:28 When such a function is called, instead of getting the result, you get a promise object. This is a similar concept to the generator. It is a proxy to the result.
16:37
You resolve such a promise with the await
keyword. Why would you do this? Well, if the greet()
function here were doing something terribly complicated like waiting for a result from a server, you could take advantage of that time and do a whole bunch of computation between the promise and the await
.
16:54
When you’re finally at a point where you absolutely must have the response back from the server, then you do the await
. If the response had come back, you’d get it immediately. If it hadn’t, you would block until it returned.
17:07
Sounds simple enough, so why all the the talk of dragons? Although JavaScript is inherently asynchronous, it isn’t inherently multithreaded. If I put a long running for
loop inside of the greet()
function, it would get run when the function was promised, not in a separate thread.
17:23
You’d end up with some very synchronous-looking asynchronous code. To make matters worse, async functions have to be chained. If the greet()
function called another function that wasn’t asynchronous, you could get some odd results.
17:37
As async
/await
was introduced in ES8, a lot of the existing functions in the JavaScript library are not async-aware. For example, Array
objects have an iterator method on them called .forEach()
. It isn’t built with async.
17:52 If you forget this and call one inside of your async function, it will mess up your async function. No, I didn’t say it would give you an error. It just messes with your results.
18:02 Once again, JavaScript is pretty happy to be silent when things go wrong. See? Dragons. Big, ugly, undead, ice-breathing dragons.
18:15 All righty then. that’s enough syntaxing. Next up, an entire lesson on dragons. Little dragons, bitey dragons, fiery dragons, and dragons that have guns that shoot you in the foot. Yes, JavaScript has foot dragons.
Become a Member to join the conversation.
Ranudar on Oct. 19, 2023
Loved the dragons, especially the foot dragon. Should be Christopher’s mascot.