Javascript Flashcards
Javascript Engine
A JavaScript engine is a computer program that you give JavaScript code to and it tells the computer how to execute it. Basically a translator for the computer between JavaScript and a language that the computer understands.
But what happens inside of the engine? Well, that depends on the engine.
There are many JavaScript Engines out there and typically they are created by web browser vendors. All engines are standardized by ECMA Script or ES.
Parser
Parsing is the process of analyzing the source code, checking it for errors, and breaking it up into parts.
AST
The parser produces a data structure called the Abstract Syntax Tree or AST.
AST is a tree graph of the source code that does not show every detail of the original syntax, but contains structural or content-related details. Certain things are implicit in the tree and do not need to be shown, hence the title abstract.
Interpreter
An interpreter directly executes each line of code line by line, without requiring them to be compiled into a machine language program.
Interpreters can use different strategies to increase performance. They can parse the source code and execute it immediately, translate it into more efficient machine code, execute precompiled code made by a compiler, or some combination of these.
In the V8 engine, the interpreter outputs bytecode.
Compiler
The compiler works ahead of time to convert instructions into a machine-code or lower-level form so that they can be read and executed by a computer. It runs all of the code and tries to figure out what the code does and then compiles it down into another language that is easier for the computer to read.
Have you heard of Babel or TypeScript?
They are heavily used in the Javascript ecosystem and you should now have a good idea of what they are.
Babel is a Javascript compiler that takes your modern JS code and returns browser compatible JS (older JS code).
Typescript is a superset of Javascript that compiles down to Javascript. Both of these do exactly what compilers do. Take one language and convert into a different one!
Explain how interpreter and profiler works together
In modern engines, the interpreter starts reading the code line by line while the profiler watches for frequently used code and flags then passes is to the compiler to be optimized.
In the end, the JavaScript engine takes the bytecode the interpreter outputs and mixes in the optimized code the compiler outputs and then gives that to the computer. This is called “Just in Time” or JIT Compiler.
Memoization
Memoization is a way to cache a return value of a function based on its parameters. This makes the function that takes a long time run much faster after one execution. If the parameter changes, it will still have to reevaluate the function.
Here are a few things you should avoid when writing your code if possible:
eval()
arguments
for in
with
delete
There are a few main reasons these should be avoided.
Hidden Classes
By setting these values in a different order than they were instantiated, we are making the compiler slower because of hidden classes.
Hidden classes are what the compiler uses under the hood to say that these 2 objects have the same properties.
If values are introduced in a different order than it was set up in, the compiler can get confused and think they don’t have a shared hidden class, they are 2 different things, and will slow down the computation.
Also, the reason the delete keyword shouldn’t be used is because it would change the hidden class.
Memory Heap
The JavaScript engine does a lot of work for us, but 2 of the biggest jobs are reading and executing it. We need a place to store and write our data and a place to keep track line by line of what’s executing.
That’s where the call stack and the memory heap come in.
The memory heap is a place to store and write information so that we can use our memory appropriately. It is a place to allocate, use, and remove memory as needed. Think of it as a storage room of boxes that are unordered.
Call stack
The call stack keeps track of where we are in the code, so we can run the program in order.
Things are placed into the call stack on top and removed as they are finished. It runs in a first in last out mode. Each call stack can point to a location inside the memory heap. In the above snippet the call stack looks like this.
Garbage Collection
JavaScript is a garbage collected language. If you allocate memory inside of a function, JavaScript will automatically remove it from the memory heap when the function is done being called.
However, that does not mean you can forget about memory leaks. No system is perfect, so it is important to always remember memory management.
JavaScript completes garbage collection with a mark and sweep method.
Synchronous
JavaScript is a single threaded language, meaning only one thing can be executed at a time. It only has one call stack and therefore it is a synchronous language.
So, what is the issue with being a single threaded language?
Lets’s start from the beginning. When you visit a web page, you run a browser to do so (Chrome, Firefox, Safari, Edge). Each browser has its own version of JavaScript Runtime with a set of Web API’s, methods that developers can access from the window object.
In a synchronous language, only one thing can be done at a time. Imagine an alert on the page, blocking the user from accessing any part of the page until the OK button is clicked. If everything in JavaScript that took a significant amount of time, blocked the browser, then we would have a pretty bad user experience.
This is where concurrency and the event loop come in.
Event Loop and Callback Queue
When you run some JavaScript code in a browser, the engine starts to parse the code. Each line is executed and popped on and off the call stack.
But, what about Web API’s?
Web API’s are not something JavaScript recognizes, so the parser knows to pass it off to the browser for it to handle. When the browser has finished running its method, it puts what is needed to be ran by JavaScript into the callback queue.
The callback queue cannot be ran until the call stack is completely empty. So, the event loop is constantly checking the call stack to see if it is empty so that it can add anything in the callback queue back into the call stack. And finally, once it is back in the call stack, it is ran and then popped off the stack.
Job Queue
The job queue or microtask queue came about with promises in ES6. With promises we needed another callback queue that would give higher priority to promise calls. The JavaScript engine is going to check the job queue before the callback queue.
3 ways to promise
There are 3 ways you could want promises to resolve, parallel (all together), sequential (1 after another), or a race (doesn’t matter who wins).
Threads, Concurrency and Parallelism
Even though JavaScript is a single threaded language, there are worker threads that work in the background that don’t block the main thread.
Just like a browser creates a new thread when you open a new tab. The workers work through messages being sent, but don’t have access to the full program.
Web Workers
Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and channel attributes are always null) or fetch (with no such restrictions). Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa).
Multithreading
Modern operating systems can run multiple programs at the same time. That’s why you can read this article in your browser (a program) while listening to music on your media player (another program). Each program is known as a process that is being executed. The operating system knows many software tricks to make a process run along with others, as well as taking advantage from the underlying hardware. Either way, the final outcome is that you sense all your programs to be running simultaneously.
Running processes in an operating system is not the only way to perform several operations at the same time. Each process is able to run simultaneous sub-tasks within itself, called threads. You can think of a thread as a slice of the process itself. Every process triggers at least one thread on startup, which is called the main thread. Then, according to the program/programmer’s needs, additional threads may be started or terminated. Multithreading is about running multiple threads within a single process.
For example, it is likely that your media player runs multiple threads: one for rendering the interface — this is usually the main thread, another one for playing the music and so on.
Execution Context
Code in JavaScript is always run inside a type of execution context. Execution context is simply the environment within which your code is ran.
There are 2 types of execution context in JavaScript, global or function.
There are 2 stages as well to each context, the creation and executing phase.
As the JavaScript engine starts to read your code, it creates something called the Global Execution Context.
Global Execution Context
Creation Phase
global object created
initializes this keyword to global
Executing Phase 3. Variable Environment created - memory space for var variables and functions created 4. initializes all variables to undefined (also known as hoisting) and places them with any functions into memory
Function Execution Context
Only when a function is invoked, does a function execution context get created.
Creation Phase
argument object created with any arguments
initializes this keyword to point called or to the global object if not specified
Executing Phase
Variable Environment created - memory space for variable and functions created
initializes all variables to undefined and places them into memory with any new functions
The keyword arguments can be dangerous to use in your code as is. In ES6, a few methods were introduced that can help better use arguments.
Arrow Functions
Some people think of arrow functions as just being syntactic sugar for a regular function, but arrow functions work a bit differently than a regular function.
They are a compact alternative to a regular function, but also without its own bindings to this, arguments, super, or new.target keywords. Arrow functions cannot be used as constructors and are not the best option for methods.
Hoisting
Hoisting is the process of putting all variable and function declarations into memory during the compile phase.
In JavaScript, functions are fully hoisted, var variables are hoisted and initialized to undefined, and let and const variables are hoisted but not initialized a value.
Var variables are given a memory allocation and initialized a value of undefined until they are set to a value in line. So if a var variable is used in the code before it is initialized, then it will return undefined.
However, a function can be called from anywhere in the code base because it is fully hoisted. If let and const are used before they are declared, then they will throw a reference error because they have not yet been initialized.
Avoid hoisting when possible. It can cause memory leaks and hard to catch bugs in your code. Use let and const as your go to variables.
Lexical Environment
A lexical environment is basically the scope or environment the engine is currently reading code in.
A new lexical environment is created when curly brackets {} are used, even nested brackets {{…}} create a new lexical environment.
The execution context tells the engine which lexical environment it is currently working in and the lexical scope determines the available variables.
Scope Chain
Each environment context that is created has a link outside of its lexical environment called the scope chain. The scope chain gives us access to variables in the parent environment.
Function and Block Scope
Most programming languages are block scoped, meaning every time you see a new { } (curly braces) is a new lexical environment.
However, JavaScript is function scoped, meaning it only creates a new local environment if it sees the keyword function on the global scope.
To give us access to block scope, in ES6 let and const were added to the language. Using these can prevent memory leaks, but there is still an argument to be made for using var.