ARROW FUNCTIONS vs FUNCTION EXPRESSIONS vs FUNCTION DECLARATIONS (BYTE CODE)| Advanced JavaScript

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey welcome back and in this video we're gonna deep dive into function declarations function expressions and arrow functions in javascript now i'm sure you know the difference between all three of these things already but actually what we're gonna do this time is we're going to pull back the covers and we're going to deep dive underneath the hood and look at how it's represented at a bytecode level in the v8 engine so that when you write a function declaration expression or an arrow function you know exactly what's happening underneath the hood and what the differences are okay so the first thing i'm going to do is create a typical function declaration which i'm sure you've all used many times so we'll just type in function hello uh we'll have open brackets we don't need any parameters in this one and then we will just return 20 and then we will console log that out so console.log hello and we are good to go so if i open that up in uh my terminal and just run that node.js node example.js you see it returns 20. so now that i've done that what i'm now going to do is create the equivalent function expression which is the s6 syntax now i'm sure loads of you used that already but we're just going to sort of compare and contrast and then we'll get underneath the hood and look at the bytecode so what i'm going to do now is create the equivalent function expression so to do that we will make it a a constant declaration i'm going to give it the name hello2 and we will equal to a function i'm going to give the function a name for just now and then we'll tidy that up a little bit later so we'll call that uh hello two and then uh we will just put some parentheses in there and then we will i think we'll return uh 30 in this case and then if i just cancel that logout there we'll do console log and we'll say hello to and and that is fine and if we go back to our terminal and we run example.js it should say 20 then 30 which it does so i'm sure a lot of you are thinking to yourself actually i would normally use the arrow function format and we'll get on to that in a second but for just now let's just stick with function expressions because that's what they are in the purest form here okay so now that i've got my function expression um there is a couple of subtle differences i'm sure everyone knows this between a function declaration and function expression the first one is going to be hoisting so let's take for example if i take this console log hello here and then run this before my declaration so now what i'm doing is executing my hello before i've made the declaration and if i save that it will work and the reason that works is because this function gets hoisted to the top like this and then um technically the code that you run after that is being executed after the declaration so that is something that happens with the function declaration syntax but that doesn't happen with function expression so if i were to take this console log hello too and i were to execute it before i declare my hello to constant then what you're going to see there is when i run the code in my terminal it's going to throw back a parser error so if i run node example.js you can see this error that says cannot access hello to before initialization so that's one of the sort of subtle differences between the two now i promised that we would look at this and see what happens underneath the hood at a bytecode level and i think we will do that now so to do that we will take this syntax here and then we will just uh do an execution i don't want i'm not going to run console.log just now uh on this because i want to keep this as pure as possible and i want to see what this looks like at a bytecode level so we will run hello and we'll run hello too and the reason i'm executing them is that if i don't execute the functions then what's going to happen is that the compiler is essentially going to do a bit of optimization and throw the code away and not generate it so that's why i'm doing that just now okay so now that i've done that what i'm going to do now is i'm going to look at this from a byte code level so i'm going to go to my terminal i'm just going to paste in this command here so if you've never used that before there is a whole video on looking at javascript engines underneath the hood and that will appear in the top corner there but very very quickly if i want to access this in node.js then i just type in node as it would do normally and then i use the dash dash print dash byte code parameter and then i can also filter by function and and to filter by function i can do print dashboard code filter equals hello and then i just put in the name of the file i want to execute with it which is example.js so if we come back to vs code what i'm expecting is that to return the bytecode for hello so if i run that you can see the bytecode is returned and there's a few things i sort of want to point out in this screen so you can see this is shared function info has come back and then it's hello so we have got a function called the low we've got some shared function info and then what you're seeing underneath the hood for the bytecode is it's basically loading into the accumulator a small integer of 20 and then it's returning whatever is in the accumulator which in this case is 20. i have a whole video on the javascript engine and the accumulator feel free to check that out if you want a deeper dive into what all of these bytecode instructions means but i'm not going to cover that today okay so i've got my function and actually i can run the same thing but if i run this and then this time put in hello to which is my function expression rather than my declaration come back to vs code for a second you can see i named this function hello2 let's come back to that and run that and you can see i have a function called hello2 shared function hello2 and then pretty much i've got the exact same bytecode the only difference is i'm running loading 30 into at the accumulator rather than 20. so so what i want you to understand is that when you use a function declaration or when you use a function expression add a bytecode level the generated byte code is the same it's the exact same i still have a function represented in bytecode it generates the same bytecode everything happens is the same there is a difference because of hoisting which is picked up at a parson level but that happens before the bytecode generation but once it ends up once that person is done and once the bytecode is generated it generates the exact same byte code underneath and again i can remove the filters so if i take away this filter so that i'm just running the node print byte code on example js and then we run the two you can you can see side by side here is hello and here is hello to generate the same byte code and then if i go a little bit further up uh here um i'm not going to go into this this here but uh i'll do another video enclosures etc but what i want you to see here is call undefined receiver is basically meaning call function and this one is called function so i'm calling hello then i'm calling hello to and then you can see zero relates to here which is zero and one is related to here which is hello two um and that's generated by the fact that i've created closures here stored in register zero stored in register one so essentially that piece of code is is say call hello which is a function declaration and then it's saying call hello 2 which was a function expression but underneath the hood the byte code is exactly the same so that is the sort of key thing i want you to understand okay so if we come back to vs code there's a few things i want you to sort of look at even further so in this case i called my expression hello2 and then i've named my function hello2 as well so can you guess if i were to give this a different name let's say i call this hello3 what do you think is going to name the function in the bytecode well let's find out so let's save that and then we just uh we're going to run node example.js with the dash dash print bytecode flag again uh we'll look at the bottom and you see there's my hello that i had before but now it's named the function hello three so from a generated by code perspective the name is actually hello three it's not whatever you call your expression in this case you've named the function and then the back end in the bytecode it's gonna declare that function in the back end as hello 3 exactly in the same way as you would do when you're calling function here okay so we've looked at the generated byte code for the functions let's take a quick look at the generated byte code for the calling function it calls undefined receiver which means call the function and it's got a mapping to hello and hello three now from a bytecode perspective bytecode doesn't care about variable names what actually happens underneath the hood is every variable that you declare will be mapped to a virtual register of some sort and you can see that so if i if i took the hello2 function that we created and maybe i stored the result into a variable so if i call let's say const a equals uh hello to um let me just delete that over here for a second we hit save and then we run our byte code again you can see my hello function as i have before my hello3 but if i run up to the calling code i just want you to see this for a second that i'm going to call function1 which is hello call function2 and then the result of hello 3 is going to be essentially returned and put in the accumulator and then you see the star r2 where it's going to store whatever value in the accumulator into r2 so in that case because i created a new variable which in this case was cost a it's assigned me a brand new register and my register count went up from two to three and therefore the whatever got returned from the hello three function gets stored in r2 but in this case like the function expression it doesn't care about the names of the variables it's it's not interested at a bytecode level everything is essentially associated a register and that's probably the key thing and and even when you look at the this call undefined receivers it is dealing with registers r0 r1 so every function is essentially treated uh as a variable as well because it's got an associated register and you can kind of see where where it's calling to create closures over here for hello and hello three and then it's storing that function uh into a register as well so everything that you're dealing with in a bytecode level is registered so it doesn't care about the names it only cares about registers so let's bring that back um let's remove that we'll come back to hello too and now we haven't got onto arrow functions yet so what the more astute review is probably going to point out is well i probably wouldn't write my code in that way even if i wasn't using an arrow function if i was using a function expression then what i would probably do is not name the function i would probably write syntax closer to this which is function have your open and close parentheses and then you'd have your brackets and return 30. so that is more likely what you're going to write and it's kind of closer to an arrow function so what do you think's going to happen is it going to generate an anonymous function or is it going to call it hello 2 well let's find out so let's run this again i'll just clear my screen uh we will run node price print by code example.js and yep as you can see we've got hello as we did before but this time is called a hello two so there is a key key thing i want you to realize that underneath the hood what it's actually doing is because you haven't named the function explicitly it is inferring the name of the function and calling it whatever the name of your variable or constant in this case is gonna be so in this case i named my constant hello two and therefore it's inferred that name when it's generated the bytecode and and again i've done this as a cons but the exact same would uh run true as if i did a let but of course if i gave it a name let's say i called it hello three again and then we generate it one more time then it's going to use the explicit name that i gave it in the function expression so that's just something for you to be aware of although it looks like this is an anonymous function in this case in reality the function name is inferred and it is therefore still a named function and at a bytecode level these two things are exactly the same so we've done our example now with named functions and inferred functions what i want to now look at is anonymous functions and to do that we are going to come back into using uh time notes so what i'm going to do here is we're going to create a set timeout function and we will therefore use the sort of function expression format so i'm just going to type in a function we'll call it my func um i will take an argument but i'm probably not going to use it and actually we will just return three two five so we'll just pick some sort of random number there and then i want that to execute every one and a half seconds um and then we're sort of good to go actually if i want to let's rather than returning that in this case i'll copy that and we'll just do a console.log we could actually put in one two three zero yep doesn't really matter let's save that and then we will just run this for a second and ensure it works and as you can see in the screen it's returned that one two three oh now i'm gonna come back to vs code and i'm gonna rather than doing a console log i wanna keep this super simple and i wanna make it easy for me to find in the bytecode and therefore i'm just gonna get rid of the console log because otherwise it's going to sort of chunk up and and appear all over the bytecode and i just want a really simple representation for me to find so i'm going to take that and then what we're going to do is come back into uh our our bytecode and if we just generate that for a second we'll take a second there but we should be able to easily find the code that we created and an easy way of doing that is if we were to just uh look for my funk and there we go there's my thunk and then we will just return this one more time and and as we did before you see this lda smi dot extra wise 71325 so i'm gonna i'm gonna copy that for a second but the key thing on this case is it's given that function a name again so it's called my func and you see that shared function info my funk so even in this format with set timeout where i put my function expression in in the middle of my parameters then it still generated me a named function now if i take away that my funk this is uh the same sort of format as we had before so it's a at this time it's an anonymous function um it should work exactly how it did before um but now when we look at the byte code let's just run that for a second if i were to uh put the filter on this for a second and i think i called this my funk you see it can't find anything so we know it's an anonymous function it can't find the named myfunk but if i were to run this and then let's search for uh i think it was seven one three two five uh that's the older version there if we look here you can see generated by code for function shared function info and there's my 71325 and as you can see it's not got a name and then if we just want to make sure that we haven't got the old code getting in the way let's do that again and then this time we will search for seven one three two six so let's change that to six and there we go there's my function and as you see it's got no name associated with that but if i come back and then add my funk to this again do that one more time and then we do a search for seven one three two six uh it's going to find the older version first and there you go my funk and again if i now run the with the print by code filter and put in the function name my func then it's going to return the generated function so that's kind of uh how anonymous functions work and this is probably a more realistic scenario of how you are going to use sort of a function expressions and this sort of leads us into the discussion about arrow functions so there we go in this particular example unlike earlier there is nothing that can be inferred about the name right there is there's no expressions there's no constants so in this particular case because i haven't given the function a name it can't infer it in any way shape or form so it will generate a genuinely anonymous function in this particular case so that is how that works and you can kind of see the difference now now that we understand the difference between function declarations and function expressions let's move on to arrow functions and again there's some subtleties around this now this is probably a perfect example of when to use arrow functions because this is this is quite nasty looking code right so i've got set timeout and because i don't want to go into the hassle i could declare a function up here i could do as a function declaration or expression but it but it all gets kind of painful and it makes my code look messy and all i'm really wanting this to do is return this number so this is where a function expression is probably a little bit overkill for this scenario because it makes my code look a little bit unreadable and this is where i could use an arrow function instead so what does an arrow function look like well i'm sure you all of you know what it is already but the quick version of this is an arrow function just means that you can remove the function and then you're left with the the brackets and in this case arg and then and all i need to do is put an arrow so that's the equals and the greater than symbol which forms the arrow and i now have my arrow function there so just to do that again if i want to take something from an arrow function and bring it back into being a function expression then all i need to do is remove that arrow put function at the beginning and again we're back to being a function expression right so to take a function expression turn into an arrow function you remove the function let's clean that up and then we can put an arrow in between cool and now that's a little bit cleaner and and that kind of works but because in this particular case i'm running one line of code i can shorten it even further now to be clear if i wanted to run multiple lines of code if i was gonna go constantly equals blah and then i was gonna do i don't know [Music] console.log arc for example um then i would still need these brackets here like that so this is actually runnable code if i go back to my terminal for a second and i just run example.js it's going to return undefined because actually i haven't passed through an argument so let's do that so i could pass in heller and then run that again and it comes back with hello so runnable piece of code um but because it's multi-line uh i need to sort of keep these brackets in here if i want i can i i can shorten it down so let's let's get rid of this and this and we can have a console.log.arc um so it's now down to one line of code and you see it still works says hello um but because it's one line of code i can remove these brackets and then i can bring this into one line of code which is kind of cool so if i run that again still works so that is an arrow function in that sense now what i'm going to do here is i can shorten it down even further so i'm going to come back and put that return value in there so now it's going to uh it's going to come up with a squiggly line and the reason it's coming up with that squiggly line is that with an arrow function i don't need to put return when i'm doing it as a single line you know when i'm in the brackets i do so if i if i sort of come back to my brackety scenario for a second um then if i was gonna uh want to return a value i would still need that return uh piece like that for that to happen but in in this case um and actually we could just return arg rather than 7132 whatever and then just run a node it's not going to come up with anything because i haven't done a console log but it doesn't need uh it needs to have that return in this case but if i get rid of this let's get rid of these brackets again let's get rid of the semicolon let's get it a bracket here and then i bring it to here then you see the squiggle because with an arrow function when it's a single line i don't need the return keyword and i can just do that and and that will work so if i return that you're going to see basically returns nothing which you would expect because i'm not doing anything with it but again if i could also put a console.log arc here and then it's back to saying hello again so next thing i'm going to do is i can break it down a little bit further as well so in this case because i'm not using the arg function at all i can just remove that and that leaves me with these empty brackets and that is kind of the clean version so in this case it's going to return 71362 but if i want it to console.log a number seven one two we can do that and then it will continue to work so if i have no parameters uh whatsoever i can just do bracket bracket the other thing i can do is i can do this sort of underscore symbol and that is the equivalent of bracket bracket for when i have no parameters and you see that that continues to work but most people tend not to do that most people tend to stick with with this version which is the bracket bracket or parenthesis parenthesis version okay so we've got arrow functions there are some other differences with arrow functions to function expressions which i'm going to go into in a second but how does this look from a bytecode perspective well let's generate the bytecode and we will see so i'm going to put back this return value so or in this case the two six actually let's just put a different number seven one three two seven and then we're gonna look at the byte code uh associated with that so i'm gonna run my uh node example.js but this time i'm going to print out the bytecode so let's scroll up a little bit here so now that the bytecode is generated let's just search for that number because it's going to be difficult to find it there it is you can see it's loading into the accumulator the small integer 71327 and what has actually done it's it's done exactly what it did before it's just generated me a function um so in this case it's an anonymous function you see no names associated with that so it works in the exact same way as a function expression so this is this is a pretty kind of neat way of doing that it looks cleaner it's much cleaner code i can bring it down to one line doesn't have all that funk junk uh all over my code so it's just nice and clean and it results in pretty much the same code or exactly the same code in this case now you might be thinking to yourself what if i want to give my arrow function a name well i can do that as well so if i go my func is equal to this then what i'm going to end up with is it's going to generate me a name again so if i go node print bytecode on my example.js and actually if i filter it out on my func you're going to see that it comes back with some generated by code 71327 and there is the name my funk so i can give my arrow functions a name if i want so i don't need to always run as an anonymous function if i want to give it a name which may i may want to do or may not want to do um good reasons why you may want to give it a name is that make it easier to find uh and and give a little bit of clarity within your code reasons you might not want to give it a name is you may if you're developing modules etc or you're developing a code to be reused by others you might not want to have name clashes so therefore that would be a good reason not to give it a name but it's it's really up to you uh what you want to do in those cases now what do i mean about name clashes well i want you to think about this for a second what if i change the 71327 and i did a console.log and let's just put in hiller as we did before and then what happens if i call my funk what what do we think's gonna happen do you want to take a guess well actually just just run it and then we'll see and then i'll explain what happens so if i do a nodeexample.js i would expect this to execute which it does right it's executed twice because the the first one is going to be the call to my funk hello and then the second one is where it goes hello as part of my timer now why is that happening well if we think about this for a second it it's all about scope and it's all about hoisting so this piece of code here where i've got my funk equals blah well it's not anonymous so i can call it if this was anonymous i wouldn't be able to call it because i wouldn't know what the name is there's no way of me being able to call this i just don't know what it's called but in this case because it's got a name and because it's represented in the exact same way as uh an any other function then it can be called but the key thing i want you to take away is if you name your arrow function here it is going to be the equivalent of declaring a function up top and therefore it's accessible here and and as i said if i do a console.log here with a hello when i run that code it's going to return so if you don't want your arrow functions to be accessible then don't name them so be really careful about that feature okay so there's one last thing i want to talk about which is one of the difference between arrow functions and function expressions and that's how it binds to the this object so we need an example to kind of show what that means but essentially the quick version of this is an arrow function is always going to use the parents this object whereas with a regular function expression it is going to use this object which is bound to itself okay so what we're going to do is bring that to life with things some examples what we're going to do is create a few objects which are going to be personal objects with names of characters so the first thing i want to do is i'm going to create my sort of global level here uh some some photons so i'm going to have myself this dot last name equals hey and then what i'm going to do is create myself an object so i'm going to call this one person one uh it's going to be a of an object and therefore i'm also going to have a first name uh we will call this one mickey and we will have a last name of mouse and then what we'll do is we'll create a little function which will concatenate these things together so we'll call it full name except i'm going to call it full name one because we're going to look at the bytecode a little bit later and i want to be able to find that so let's do that and we will we'll set that as a function and for that function we will concatenate the name so we'll return uh we'll do this as uh literal and to do that we're gonna do dollar and then this dot first name and we will then do a dollar this dot last name okay that should be good uh let's close that out and then i'm gonna do a console.log and we will look at person one dot full name one and what we would expect when we run that is we would expect it to ignore the chris hey and i'd expect it to output mickey mouse so if i just save that we go back to our terminal and i run node example dot js then as we'd expect it returns mickey mouse so that is good now let's look at the difference of when we do this as an arrow function so we can do a const and we will do person 2 in this case and we're going to have full name 2 and rather than have a mickey mouse we will have scooby-doo one of my particular favorites and then we will get rid of the function and we'll turn this into an arrow function and we don't need to return we don't need that we don't need this bracket here and if we save that then we should be in a good position so now that we've got full name one let's uh call person two dot full name two and then if i go to uh noteexample.js again i would expect to see mickey mouse and what i'm going to expect this time is rather than seeing scooby-doo it's going to come back with chris hey so let's run it and it does so you see mickey mouse and you see chris hey and the reason for that is as we said before is it's ignoring the kind of first name and last name and when it uses this it's not bind into its own object it is inheriting is using the this within whatever parent scope or whatever calling scope it has so in this case it's this dot first name this dot last name over here and hence why it's coming back with chris hey so that is a sort of fundamental difference between how function expressions and arrow functions actually work now in the example i gave earlier with mickey mouse if i didn't want that mickey mouse to be returned from this function and i actually wanted that this to be bound to the this um from from the scope above then i can actually then just do a dot bind and do this here which i obviously can't spell and then save that and then if i was to run that again you would see it returns chris hey chris hey but in this particular example um because i haven't done that bound naturally what it's going to do is use its own first name and last name so that's probably the key difference everybody really needs to recognize is is that bind here is automatically done when you do a arrow function rather than a function expression so that that binding happens automatically and we we can actually see this in the byte code so if i if i just get rid of the console.logs for a second um i'm going to keep the person one full name and last name there just now so let's do that here and then what we're going to do is we're going to generate the byte code and see what is actually going on underneath so let's clear this out and we'll bring up our bytecode now i'm not going to filter on any particular uh function in this particular case so it's going to look a little bit sort of crazy as as as always but you can you can kind of start to see what's going on so you can see here i've got generated byte code for full name two and if we if we look at full name two this is my my arrow function here so first name last name and then full name two is my function this first name the sort last name so we are gonna get the bytecode for this full name to function so it's created a function um the name name is interesting so you can kind of see it's got uh person2.fullname2 uh it says full name two there that's that's fine um this is probably the interesting part about this so what is what it's doing here is it's loading its context right so and it's pulling it from context slot two so if i was to look at the calling function then you're gonna see that i'm i'm gonna get all of the kind of global information associated with that and then it's gonna call the name property r1 which comes from kind of uh you know and it's going to load in properties 0 here which is going to be first name and then it's also going to call this the last name as well later on so you see that load name property two there as well it's going to add these together and return it but but a key thing is doing the first name plus the last name um but the the big thing that it's doing is it's it's it's using the current context slots there if i was to compare that to uh full name one which is the non-arrow function version which is my regular property you see it's not pulling any of that current context slot so it's not pulling any information from earlier all it is doing is um is is is using its own property so you see that load name property i think interestingly enough it's using the keyword this so it's it's it's this and then it's first name and then it's it's last name there so um so there's a bit of a a difference there um to be aware of but but the byte code is fundamentally different the first function the the regular function expression is working within its own bounds it's not looking at any sort of calling functions whereas the the second function is using context from its uh his previous caller so it's losing the current context at the moment which gives it access to this which allows us which allows us to use the the um the the chris hey above so there we have it as you can see although you might think they're doing the same thing the result is very different and the use of this is completely different so with a function it is automatically bound to its own object for the this keyword so we'll use first name last name from its own object but if you're using an arrow function then it's going to use um this from the calling function so in this case it was pulling first name and last name which was kind of uh from the this scope of my global layer so it's just a real subtlety to be aware of but again when we looked at the byte code underneath the bytecode that was generated is completely different one set of bytecoders is is looking at the this property and then the other set of bytecode is just doing the load properties so completely different set of byte code so although they look similar they're actually quite different uh underneath anyway this has been my deep dive of function declarations versus expressions versus um arrow functions it's been quite a deep dive i'm sure you've got a lot from that key thing i would sort of say there is just be aware of your usage right so for regular usage such as um you know uh so just uh declaring functions then the major difference is is essentially going to be hoisting as such obviously when you're getting into scenarios with objects then you get some subtleties with the use of this keyword so be aware of that um but again stylistically it's kind of your choice of whether you want to be using arrow function kind of type syntax when you're declaring your functions and use function expressions there or whether you want to stick with just using regular old function declarations that you used before stylistically it's kind of up to you but at least you know no deep dive under the hood what is actually occurring hopefully this was useful to you uh and if so we'll speak soon in the next video thanks bye
Info
Channel: Chris Hay
Views: 181
Rating: undefined out of 5
Keywords: chris hay, chrishayuk, arrow functions, javascript arrow function, function expression, es6 arrow functions, function declaration, javascript arrow function tutorial, function expressions, function expression vs anonymous function, regular function vs arrow function, arrow function vs function performance
Id: iZVaGh3pkLg
Channel Id: undefined
Length: 41min 48sec (2508 seconds)
Published: Mon Aug 09 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.