JavaScript
What is lexical binding and how can it be done?
Because the "this" keyword is not lexically scoped, if a function runs inside of a method on an object, the method's "this" will point to the object, but the function within the method's "this" will still default to the window object. In order to avoid this behavior, arrow functions can be used to lexically bind the "this" value to the outer object. Another way to do that without using an arrow function is to explicitly bind the "this" value like func.bind(this). One other way is to create a reference to the "this" value outside of the function itself (var self = this) and then use the self variable within the object instead of using the "this" keyword.
What does the bind method do and how is it different from call and apply?
Bind can be used similarly to call and apply, except it returns the function instead of calling the function. This can be used to save the function bound to the borrower object to a variable (const boundFunc = ownerObj.func.bind(borrowerObj, param1, param2...)). That variable can then be run like a normal function.
What is the difference between function scope and block scope?
Blocks refer to anything that exists between curly brackets {} e.g. conditionals, loops. Other programming languages use block scope meaning that blocks have their own context and environment, but in JS, scopes are only created for functions, not blocks. The ES6 const and let keywords enable variables to be block scoped and make variables not accessible from outer scopes.
How can you determine whether something is an array?
Array.isArray(arr)
What are the benefits of OOP?
1. Clear and understandable 2. Easy to extend 3. Easy to maintain 4. Memory efficient 5. DRY
Why are closures useful?
1. Closures are memory efficient: When calling the same function multiple times, instead of creating and destroying the same variable over and over again, we can create the variable once and return a new function that references that variable. This way, the variable is only created once and held in memory for the inner function to use. Now if we assign the invocation of the outer function to the variable and call that variable to access the inner function, we won't have to recreate the same data over and over. This is because the data was only created once when the outer function was invoked the first time when it was assigned to the variable. 2. Encapsulation: Encapsulation refers to the process of creating certain functions or variables such that they are not accessible or cannot be changed from scopes outside of the parent function. This ties into the programming concept of the principle of least privilege, meaning that we do not want to give just anyone access to the inner workings of an API. Using closures, we can restrict outside access to certain variables and functions but make others available by assigning them to an object that the function returns.
What is the anatomy of a perfect function?
1. Does only 1 task 2. Has a return statement 3. Is pure 4. Has no shared state 5. Has immutable state - doesn't modify the global state 6. Composable 7. Predictable - if we understand with 100% certainty what our function does, our code is predictable
Define and explain the 4 pillars of OOP.
1. Encapsulation - wrapping code into boxes that are self contained and can interact with each other. This makes code more maintainable and reusable. 2. Abstraction - hiding the complexity from the user (creating simpler interfaces). By taking care of all of the prototype chain stuff behind the scenes, it is easier to understand what is going on for the programmer. 3. Inheritance - allows us to avoid writing the same code multiple times and share methods which saves memory. 4. Polymorphism - there are many interpretations of what this actually means but the idea is that you can call the same method on different objects and each object can respond a different way. This can be done through method overwriting where a method appears differently on different subclasses or method overloading which allows us to add extra parameters to a method on a subclass to add to what it can do. This means we can use some of the functionality of a super class to adapt to our own specific needs.
What does it mean that "functions are first class citizens"?
1. Functions can be assigned to variables and properties of objects 2. It is possible to pass a function as an argument to another function 3. Functions can be returned as values from other functions
What are the 4 ways that the "this" keyword can be manipulated?
1. The binding of this using the new keyword - the new keyword automatically binds this to calling object when instantiating a new object using a constructor function. 2. Implicit binding - when the value of this is implied, for example in an object where this will automatically refer to the object that it is in. 3. Explicit binding - when we dictate exactly what the this keyword should refer to using the function.bind(object) method (or call/apply) where the object refers to the context that we want this to point to. 4. Arrow functions - when we make the function use lexical scoping so that this points to wherever it is actually written in the code.
Explain the difference between the === and == operators.
=== checks for equality with both type and value whereas with == JS uses type coercion to make the types of both values the same and then checks if the values are equal.
What is a JIT compiler?
A JIT (just in time) compiler was created as a hybrid between compilers and interpreters to optimize performance and run-time.
What is the difference between classical inheritance and prototypal inheritance?
A class is like a blueprint that you use to create an object. It can be thought of as a blueprint for a house. You can't live in a blueprint, but you can live in a house. The classical approach to creating an object is to define the structure of the object, using a class declaration, and instantiate that class to create a new object. JavaScript does not have classes unlike other languages. It uses the concept of prototypes and prototype chaining for inheritance. Prototypes are like model houses. You can copy it when creating your own house, but prototypes also function as objects (in this example, you can live in a model house). Prototypal inheritance is all about objects. Objects inherit properties from other objects. In prototypal inheritance, instead of defining the structure through a class, you simply create an object. This object then gets reused by new objects. Instances are typically instantiated via factory functions or Object.create() method. Instances may be composed from many different objects, allowing for easy selective inheritance. It is more flexible than Classic Inheritance. Any existing object can become a class from which additional objects will be spawned. This is handy where your objects offer several sets of services and/or they undergo a lot of state transformation before your program arrives at the point where the inheritance is needed.
What is a deep copy of an object?
A deep copy of an object is a new object that is created by recursively copying all of the values of the original object, including its nested objects or arrays, rather than simply creating a reference to the original object. In other words, a deep copy creates a completely independent copy of the original object and its sub-objects, ensuring that any modifications made to the copied object do not affect the original object.
What are higher order functions?
A function that can take a function as an argument, or a function that returns another function. With a regular function that accepts parameters, we gain the advantage of being able to tell the function which data to use which allows us to reuse functions for different pieces of data. With higher order functions, we get an extra advantage of not only being able to tell the function which data to use, but also what it should do during invocation based on various conditions, for example, a user object's level of security clearance. The parameter function can then be called with different values based on that information. This allows functions to be more generic and easier to reuse.
What are global variables? How are they declared and what are the problems associated with using them?
A global variable is a variable that is not locally scoped and available anywhere in code. Issues with global variables: 1. If we have too much data in the global execution environment, we risk polluting the global namespace. 2. The potential of variable collisions. If there are multiple script tags in an HTML file and the same variable exists in the global environment for multiple JS files, they will overwrite each other when compiled.
What is a non-primitive data type?
A non-primitive type (object, array, function) doesn't directly contain the actual value. It instead contains a pointer to somewhere in memory where the values are held. The only non-primitive data type in JS is an object because functions and arrays are actually just objects.
What is a primitive data type?
A primitive type is a piece of data that represents a single value. All JS types other than objects are primitive types. A variable of a primitive type only represents that one single value, and that value can't be broken down into anything smaller (like atoms). Examples include numbers, booleans, strings, undefined, null, Symbol.
Explain promises and how they are used. How does the finally method work?
A promise is an object that may produce a single value in the future - either a resolved value or a reason that it's not resolved (rejected). A promise may be in one of three possible states: fulfilled, rejected, or pending. Before promises were introduced in ES6, we had callbacks which use a lot of nested functions and repetition of code and aren't as powerful as promises. The syntax for creating a promise is: const promise = new Promise((resolve, reject) => true ? resolve('worked') : reject('error')). In order to process the result form the promise, you use promise.then(result => console.log(result)) for example to console log the result. You can also chain the then blocks to do different things with the result value. If an error occurs during the promise, you can use a catch block which will catch any error that occurs during the execution of the promise. If you put a then block after a catch block it will still run but the catch block only catches errors that are thrown above it, so if an error is thrown after a catch block it will not execute. Promises are useful for asynchronous programming because they are non-blocking and execute in the background, returning the response whenever the task gets completed or an error occurs. Promise.all is a method that can take in an array of promises and returns an array of the resulting return values once all of the promises have been fulfilled. The finally method is always called after a promise whether it is resolved or rejected. It usually contains a function that doesn't receive a parameter. This could be useful if for example you always want to display some type of notification to the user regardless of whether the promise resolves or rejects.
What is a shallow copy of an object?
A shallow copy of an object is a new object that is created by copying the values of the properties of the original object. However, if the original object contains nested objects or arrays, a shallow copy will create new references to those nested objects, rather than creating independent copies of them. In other words, a shallow copy creates a new object with its own properties, but the nested objects are still referenced from the original object.
What is WebAssembly?
A standard binary executable format that could allow us to avoid having to go through all the interpreter and compiler steps that are currently needed to run JS in the browser. Provides new features and major gains in performance and a way to run code written in multiple languages at near-native speed. Not intended to be written by hand, but designed to be an effective compilation target for source languages. WebAssembly modules can be imported into a web or Node.js app, exposing WebAssembly functions for use via JS.
What is a function's variable environment?
A variable environment exists within each function's execution context and stores information (e.g. variables themselves or references to objects in the memory heap).
What is an IIFE and how are they used?
An IIFE is a function expression that nests the function in parentheses and then appends it with () to invoke the function as soon as it is declared. IIFE's are a common design pattern that is used in certain libraries such as jQuery. The idea is that by placing the functions inside the brackets, we can place all library code inside a local scope to avoid any namespace collisions. During execution, IIFE's are immediately called and a new execution context and variable environment is created for the function. This allows us to prevent any variables inside the function from being accessed by the global execution context. Within an IIFE, multiple functions can be created and assigned to an object, then the functions can be accessed as properties on the object instead of having the functions exist in the global scope. An example of this is jQuery which has a ton of methods that are actually IIFE's that are assigned values on the jQuery object. This is why we can access jQuery methods using the $ operator (another way to access the methods on the jQuery object.
Explain constructor functions.
Any function that is invoked using the "new" keyword is a constructor function. The "new" keyword automatically returns the object for us, creating a constructor of the object with the given values. Constructor functions can take in parameters and use the this keyword (e.g. this.name = name) to assign values to the return object. Since constructor functions are functions, they have access to the prototype property. This prototype property is useless with most functions, but useful with constructor functions. To add a method to a constructor function and make it available to all objects created using the constructor, you can use Person.prototype.speak = function() { ... }. The resulting objects will not have their own prototype property, they will just have a __proto__ property that points to the prototype property of the constructor function. This is because objects don't have prototype properties so only the function actually has access to it.
What is arity?
Arity refers to the number of arguments that a function takes. In FP, it is good practice to limit the number of parameters in a function since that makes it easier to use. This is because you can do more interesting things and make functions more flexible to be used in currying or composition. There is no hard rule on this, but as a general rule it is best to stick to one or two parameters per function.
What is the job queue?
As of ES6, another component was added to the JS runtime: the job queue (or microtask queue). It was added by ECMA in order to accommodate promises which were also introduced in ES6. It has a higher priority than the callback queue, so the event loop checks the job queue before it checks the callback queue when pushing to the call stack. Once the job queue is empty, the event loop starts checking the callback queue. Because each browser implements these things slightly differently, some legacy browsers might not even have a job queue and the behavior of a program using web APIs and promises might be slightly different between browsers since the job queue is implemented differently for each.
Explain async await and how it is used.
Async Await is a new ES8 feature that is built on top of promises. It is a function that returns a promise but it can make code easier to read. To use this with a function the syntax would be: async function func() { await promiseFunction('success') }. The async keyword tells JS that we want to create an asynchronous function. Within a function that uses async, the await keyword can be used in front of a function that returns a promise to tell JS to wait until the promise is fulfilled before moving on to the next line of code. You can also assign a function that uses await to a variable. An example of a fetch call with Async Await would look like: async function fetchData() { const response = await fetch('url'); const data = await response.json(); console.log(data) } In order to get the Promise.all functionality when using Async Await, you can just use const result = await Promise.all( ... ). To replace the catch functionality of regular promises, Async Await functions use try catch blocks. All of the inner logic of the async function can be put into a try block and then the error handling can be done in the catch block. Syntax would look like: async function func() { try { ... } catch(err) { ... } }.
How do you handle exceptions/errors for asynchronous code?
Asynchronous error handling (promises): The catch() method can be used to handle asynchronous errors. With promises, it can be chained to the then() blocks and works the same as the synchronous catch block. It is important to keep in mind the flow of the program and where we put the catch block. Asynchronous error handling (async await): Within async await functions, we can use try {} catch(error) {} blocks inside of the functions as we usually would. When using async await if we don't wrap code in the try {} catch(error) {} blocks, the errors will never be displayed or handled.
What is asynchronous programming?
Asynchronous programming means that the engine runs in an event loop. When a blocking operation is needed, the request is started, and the code keeps running without blocking for the result. When the response is ready, an interrupt is fired, which causes an event handler to be run, where the control flow continues. In this way, a single program thread can handle many concurrent operations.
What is Babel?
Babel is a JS compiler that is used to convert ECMAScript 2015+ code into a backwards compatible version of JS in current and older browsers or environments.
What is the difference between the call and apply methods? What do they do?
Both the call and apply methods can be used on an object to borrow methods and use them on a different object. The two methods work the same except that the apply function expects an array of parameters with which to run the function as the second parameter, whereas call expects each parameter to be passed separately as individual parameters. Syntax: ownerObj.func.call(borrowerObj, param1, param2...) ownerObj.func.apply(borrowerObj, [param1, param2, ...])
Explain closures.
Closures are a unique feature of JS that exist because functions are first class citizens and because of the concept of lexical scope (that before the code is run the JS engine knows which variables each function has access to). They allow functions to access variables from an enclosing scope or environment even after it leaves the scope in which it was declared. With nested higher order functions (where the function returns another function and so on), even if an outer function is called and popped off the stack after returning, if the variables within the outer function are used in the nested functions, they get kept in memory due to the concept of closure. When the nested function returns and needs to access the variables that were declared in outer functions, it doesn't find those variables in its own variable environment so it looks where the closure variables are stored and finds them there. This works because these variables are stored in the memory heap instead of on the call stack.
What is CommonJS and Browserify?
CommonJS allows us to use the require syntax to import data and functions from modules. It also provides the module.exports syntax for exporting information. Node still uses CommonJS today - it is a big part of the reason that Node became so popular because it made code so easy to share. NPM is a way to share modules and also helped popularize Node. With CommonJS, modules are meant to be loaded synchronously. This isn't ideal for the browser which is why CommonJS was mainly used on the server side. Browserify was created to allow us to use modules on the browser. It did this by bundling up dependencies using something like browserify script.js > bundle.js which takes all of the JS that uses the require and export syntax and outputs a file that bundles all of the scripts in a way that the browser can understand it.
What are compilers and where are they used? Pros and cons?
Compilers look through the entire code and write a new program in a lower level language. When this language is interpreted line by line, it creates the same results as the original code. Pro: simplifies and optimizes code during compilation, which makes it faster during run-time. Con: takes longer to get running, since it reads through the file first.
What do the compose and pipe methods do?
Composability is a system design principal that deals with the relationship of components or functions and various configurations of those components to produce different results. It can be thought of as a factory conveyor belt where data gets passed between functions in a systematic and predictable way. There is a compose() method that can help us accomplish this which isn't part of JS but has become so commonly used that there are many external libraries (such as Ramda) that offer this functionality. To write our own compose method, we can do something like: const compose = (f, g) => data => f(g(data)) where f and g represent two functions that we will be passing into compose and data represents the data that we are passing into the function in which we are using the compose method (e.g. const func = compose(func1, func2) then we run func(data) to utilize the func1 and func2 methods on our data). This way we can easily shift around the order of functions that we are passing into the compose method to change the order of operations. Pipe is similar to compose, except instead of going from right to left, it goes left to right. Using the previous example of compose, it would instead look like const pipe = (f, g) => data => g(f(data)).
Explain concurrency and parallelism.
Concurrency is when we have a single-core CPU that can work across multiple threads but can only work on one at a time. Parallelism is possible when we have a mutli-core CPU that can work on multiple threads at once since each one can be run on a separate CPU. Parallelism isn't built into JS but can be achieved on Node using child_process and spawn.
Why does copying over data in FP to maintain immutability not have a very negative effect on memory efficiency?
Copying everything in this way doesn't have as much of a negative effect on memory efficiency as would be expected due to the concept of structural sharing. Structural sharing is commonly implemented in FP and means that when a copy of a data structure is created, not everything is copied. Under the hood, only the changes made to the state are copied and the things that don't change only exist once in memory.
What is currying and how is it used?
Currying refers to only partially giving a function its parameters. This can be useful if we want to explicitly set one of the parameters to a certain value. For example, if we have a function const multiply = (a, b) => a*b and we want to create a function that always multiplies by 2 using that function, we can say const multiplyByTwo = multiply.bind(this, 2). We then only need to supply one parameter when calling multiplyByTwo and the first parameter will always be 2.
How do you create a deep copy of an object? How do you check to see if two objects are identical?
Deep cloning can be done using the following JSON syntax: const deepCopy = JSON.parse(JSON.stringify(original)) Using the deep cloning method above can have performance implications because it can take a long time on a very large object. To compare values within objects that are deeply copied, you can use the following syntax: Object.toJSON(obj1) == Object.toJSON(obj2)
Explain dynamic and static typing. What are the pros and cons of each?
Dynamically typed languages include: JavaScript. Perl, PHP, Python, Ruby, Clojure, Erlang. Statically typed languages include: Java, Scala, C#, C++, C, Haskell. A dynamically typed language doesn't force us to specify the type of a variable when declaring it, whereas with a statically typed language, we would need to specify the type (e.g. int for an integer). In a dynamically typed language, type checking occurs during run-time. Pros of statically typed languages: 1. You get built in documentation since more detail is provided about the type expectations or constraints of a function. 2. Enhances autocompletion in text editors/IDE's. 3. You get less bugs in production because incorrect types failing during compile time are caught a lot earlier than type errors in run-time. Cons of statically typed languages: 1. It makes code harder to read and more complex. 2. Some people might not write as good unit tests because they assume that the language will catch everything for them. 3. The development process is slower because of the extra step that involves making sure there are no type errors.
Explain ES6 classes. Why are they useful?
ES6 classes were introduced to simplify the issues that exist with constructor functions and make object oriented programming more feasible with JS. Instead of assigning the properties within a constructor function, you have a class (e.g. class Person { ... }) that contains a constructor method which assigns all of the properties to the this keyword (e.g. constructor (name) { this.name = name; }). Other methods then can also be defined that can act upon the state. This allows us to encapsulate all of the properties and methods of a class into one contained environment and model real life scenarios in a more concise and understandable way. Any object that is created using a class is called an instance and the process of creating an object from a class using the new keyword is called instantiation. ES6 classes are actually just syntactic sugar because they are not real classes and they actually just use prototypal inheritance under the hood. This is commonly called pseudo-classical inheritance because it isn't really classical inheritance and still actually uses prototypal inheritance.
Explain ES6 modules.
ES6 modules are the first native modules in JS that allow us to use the import syntax to import modules and the export keyword in front of functions that we want to export. You can also use export default function to make it a default export where you no longer need curly brackets to access the function using import function from 'file' as opposed to import { function } from 'file'. In the opening script tag, you need to specify type = "module" for it to be able to use modules in the browser. You also need to serve the module from a HTT server (e.g. live-server) in order for it to work on the browser.
Explain the scope chain.
Each context has a link to its parent/outer context. This outer environment depends on where the function sits lexically (where it is declared in the code). Functions that are written in the global lexical environment have scope chains which serve as links to the parent environment (in this case, the global environment). If a variable is accessed within a function and wasn't declared within that function, it will move up the scope chain and see if the variable exists in its parents' variable environments in which case it will have access to it. Which variables a function has access to depends on the lexical scope, not the dynamic scope (where the function is called or where it is on the execution stack). The JS engine compiler takes care of attaching these scope chains during compile time before the code is even run. Every lexical environment has an object representing its information where variables declared within that environment are stored along with some information about them. Within the variable's information object, there is a [[Scopes]] property that holds an array of all of the scopes that the variable can be accessed in.
What is event bubbling and how can we prevent it?
Event bubbling is a type of event propagation where the event first triggers on the innermost target element, and then successively triggers on the ancestors (parents) of the target element in the same nesting hierarchy till it reaches the outermost DOM element or document object. e.stopPropagation() can be used to prevent event bubbling.
Why do we add methods to a class outside of the constructor?
Every time we use the "new" keyword to instantiate a class, the constructor gets run to assign the unique values to the object. The methods are meant to be shared among all instances of the class. If we moved the method into the constructor, it would take up more memory space because it would add the method to each individual object. Instead, we just have the functions in one place and all of the instances can access it using inheritance.
What is functional programming?
FP is all about separation concerns so each part of code focuses on one thing, which OOP does too. The goals of FP and the goals of OOP are actually the same: to make code understandable, easy to extend, easy to maintain, memory efficient, and DRY. FP also separates data and functions whereas OOP combines two into one object. Generally, functional languages have a focus on simplicity where data and functions are concerned. They do not use classes and methods, and instead the functions operate on well defined data structures (e.g. arrays or objects) instead of actually belonging to that data structure. The core concept of FP is pure functions, which creates a separation between data in a program and behavior in a program. All objects in FP are immutable, meaning that once something is created it can't be changed. So things like shared state are avoided. Libraries like Redux and React have popularized FP in JS.
What are the main differences between OOP and FP? When should we use what?
FP: many operations on fixed data OOP: few operations on common data FP: stateless OOP: stateful (concept of this and self) FP: functions are pure and have no side effects (this also means we can run code in parallel on multiple processors and it won't have an effect on our program) OOP: side effects exist since methods mutate the internal state FP: declarative (what we want to do) OOP: imperative (how we want to do it) FP is good at processing large data for applications (e.g. machine learning models) since it can be efficiently run on multiple processors. OOP is good if we have a few things that require a small amount of operations. React for example combines the two with the use of both class components and function components (which are generally pure functions without state).
Explain factory functions.
Factory functions are functions that programmatically create objects based on inputs. This is useful because it avoids repetitive code and allows us to use the same methods on all created objects without having to manually re-type the method in a new object. The issue with this approach is that the object needs to be stored in memory and the parts of the object that do not change such as the methods need to be stored multiple times in memory, which isn't very space efficient. This issue can be solved using the concept of prototypal inheritance.
What is the difference between composition and inheritance?
Inheritance is the concept of a superclass that is extended into smaller pieces that add or overwrite parts of the functionality of the superclass. Composition is the use of smaller pieces to achieve a desired result, for example the compose function in FP. The debate between composition and inheritance is pretty heavy and a lot of people prefer composition over inheritance.
How does hoisting work with functions?
Function declarations are fully hoisted, meaning that both their name and their content are saved in memory (or hoisted) from the beginning. Only function declarations (when functions are explicitly declared with the function keyword and not assigned to a variable) get fully hoisted. Function expressions (when a function is assigned to a variable) and arrow functions get hoisted like variables and assigned undefined. In other words, a function declaration is defined at parse time, whereas a function expression is defined at runtime. Due to this, a function expression can't be called before it is declared. If the same function is defined twice, the second value will be kept.
What is the difference between dynamic scope and lexical scope?
If a function is called inside another function, its "this" will still refer to the window object. If a function exists inside of an object, its "this" will point to the object. Unlike lexical scope where the main consideration is where the function was declared, dynamic scope cares more about where the function was called.
How and why are closures used in FP?
If a nested function modifies the state of a variable in an outer function, it doesn't adhere to the FP principle of pure functions. This doesn't mean that we can't use closures in FP because we can still use closures without actually modifying the variable but displaying it or returning it in a way that doesn't mutate the data. One use of closures in FP is to create private variables/data privacy. By declaring a variable in an outer function and then returning it in an inner function, we make sure that users aren't able to modify the variable but can still have access to it.
What is the strict mode in JS and how can it be enabled?
If a variable is created without var, const, or let, the scope chain checks to see if the variable exists in any of its parent environments. If not, the global environment creates a variable for the name/value. This can create some issues when it comes to scoping. You can use "use strict" at the top of the page to prevent this from happening and return a reference error if the variable isn't properly declared.
What happens if you try to add a function to a factory functions prototype object using an arrow function and why does this happen?
If you try to add a method to a prototype using an arrow function, it won't work. This is because arrow functions are lexically scoped, so they determine the value of this based on where it was written. Since the prototype function is written in the global execution context, this would point to the global object.
Explain the concept of immutability and why it matters in FP.
Immutability is a key concept of FP which means not changing the data or state. Instead, it is customary in FP to make a copy of the data and mutate and return the copy. Things can still be changed within the function but should not affect the outside world.
What is the difference between imperative and declarative code?
Imperative code tells the machine what to do and how to do it whereas declarative code tells the machine what to do and what should happen (but not how to do things). Computers are better at being imperative since they need to know how to do things. Humans are more declarative since we don't need step by step instructions for most actions. Machine code is extremely imperative whereas higher level languages are more declarative since all of the fine grained logic is abstracted out into more human readable code. A JS example is a for loop vs a forEach loop. A for loop is a lot more imperative since it lays out the steps needed for iteration whereas a forEach loop is more readable and doesn't really provide detailed instructions. Another example is jQuery being a lot more imperative than frameworks like React because the instructions are a lot more specific. Declarative code is always going to compile down or be processed into something more imperative like machine code.
What are the differences between public and private class properties/methods? Can we have private methods in JS?
In many OOP languages, there is an idea of public and private methods or properties. This didn't exist in JS until recently so it was customary to put an underscore before a private method (e.g. _speak() { ... }). This means that you shouldn't call the method or access the property from outside of the class but technically it could still be called and reassigned. Languages like Java have the keyword private which actually makes methods private. There is a new feature in ES2020 which enables class field declarations. This allows us to use the # sign in front of a property which actually makes it private (e.g. #age = 23). They are still working towards making this possible for methods.
Explain prototypal inheritance.
Inheritance in the prototype chain is when an object gets access to methods or properties on another object. For example, the Array object and the Function object get access to the methods on the Object object in JS. You can use array.__proto__ to move up the prototype chain. Prototypal inheritance is pretty unique - most other languages use classical inheritance whereas JS uses prototypal inheritance. The class keyword in JS is syntactic sugar and classes don't actually exist in JS. Within an object, there is a prototype property and the __proto__ property exists on the prototype object. The __proto__ property is works as a pointer upwards to the prototype property of the object above it on the prototype chain. The prototype object also contains all of the methods from its parent object which are used in the prototype chain. One way to properly inherit properties from another object through prototypal inheritence is to use the let borrowerObject = Object.create(ownerObject) syntax.
Explain inheritance and subclassing with ES6 classes.
Inheritance refers to the process of passing information down. When working with classes in JS, we can use the extends keyword to create a new class that inherits the functionality of another class and allows us to build on top of it. This is known as subclassing and the syntax is: class Borrower extends Owner { ... }. When creating a subclass, we need to call super(prop1, prop2) at the top of the constructor in order to call the constructor of the super class and inherit its properties. We can then add other properties to the constructor below that and methods to the class normally as we usually would. You can also use the super keyword like super.method() within a method to get the super class' method's functionality and then add additional functionality to the method in a child class. This is an example of polymorphism. When using the extends keyword, we are just using the prototype chain under the hood. The class itself won't be a prototype of its instances, but the class's prototype method will be. So Person.isPrototypeOf(zoe) would be false, but Person.prototype.isPrototypeOf(zoe) would be true. A better way to check these connections is the following syntax: zoe instanceof Person would be true. Languages like Java that have actual classes copy methods when we do their equivalent of extends whereas with JS classes, they just link to the prototype's methods which increases space efficiency.
What are interpreters and where are they used? Pros and cons?
Interpreters translate (e.g. into Bytecode) by reading files line by line. Pro: interpreters are quick to get up and running since there is no compilation step. Con: when the same code is run more than once (e.g. in a loop), it can get very slow.
What is garbage collection?
JS is a garbage collected language, so it automatically removes items from memory once they are no longer useful. The garbage collector frees memory and prevents memory leaks (which happen when we run out of memory). This is done using the mark and sweep algorithm which keeps track of each variable and maps it to the value. Once the value loses its reference (e.g. if a variable is reassigned to something else), the old value is garbage collected. In low level languages like C, garbage collection needs to be done manually.
Is JavaScript an interpreted language?
JS was initially an interpreted language - Brendan Eich, the creator of JavaScript, built Spider Monkey, an early JS engine that worked as an interpreter to run JS on Netscape, an early browser. Things have since evolved, so compilers are now used as well to optimize the running of JS code. Basically, whether or not JS is an interpreted language depends on the implementation of the JS engine.
How do you create a shallow copy of an object or array?
Object: const copy = Object.assign({}, original) or const copy = {...original} Array: const copy = [].concat(original) or const copy = [...original]
What is a lexical environment?
Lexical environment refers to where in the code something is written or where a function/variable was initialized. If a function is called inside of another function, but the function definition belongs to the global execution context, its lexical environment would be the global scope.
What is the difference between execution context and lexical environment?
Lexical environment simply refers to where in the code something was initialized whereas execution context refers to where we are on the call stack and what variables and functions we have access to. With every change of the execution context, the lexical environment changes as well. However the lexical environment may change independently from that as well, for example when entering a block.
What does idempotence mean and why does it matter in FP?
Means that given the same inputs, the function always returns what we expect. For example if we have an API call it would be idempotent if we can always expect the same HTTP response. This idea of being able to call the same function a thousand times and always get the same result is extremely valuable when it comes to parallel and distributed computation. Another interesting feature of idempotence is the idea of being able to call a function on a call of the same function many times and expect the same result as just calling the function once.
What is memoization and why is it useful? How does it work with closures?
Memoization is a specific form of caching that can be used in FP. When doing a calculation, we can cache a return value in an object and then if we need to do the same calculation again, we can check to see if it is in the cache first and return the result if it exists in the cache. This way we avoid repeating the same computations and we can quickly access the result in O(1) time. When using memoization to cache return values, it is preferable that the cache doesn't live in the global scope. This can be accomplished using closures in JS. You can bring the cache into the function and then return a second function, passing in the parent functions parameters, and move the logic into the second function. This way, the cache doesn't get reset every time we run the function because we are actually running the inner function and adding to the cache which is being stored in a closure.
What is a memory leak? What are some common ways that they occur?
Memory leaks occur when the memory heap runs out of space to hold all of the variables or values. Common memory leaks: 1. Global variables - take up a lot of space in the global context 2. Event listeners - can cause a memory leak if they are not removed when switching between pages in a SPA 3. Set interval - if objects are referenced in a setInterval function, they are never garbage collected as long as the interval keeps running
Describe the Node.js runtime. How is it different from the browser runtime?
Node.js is a JavaScript runtime built on Chrome's V8 JS engine that serves as a way to run JS outside of the browser. The Node.js runtime works pretty similarly to the browser JS runtime. It also uses the V8 engine, an event loop, and a callback queue. Instead of web API's, it uses something called the LIBUV written in C++ that allows us to do asynchronous operations. Node can do more than the browser when it comes to what we can do in the background since it isn't running in a sandboxed environment (e.g. it can access file systems). Instead of the window object, Node.js has a global object.
What is OOP?
OOP says that bringing together data and its behavior in a single object makes it easier to understand how our programs work whereas FP says that data and behavior are distinctly different things and should be kept separate for clarity. Both paradigms can be used together in JS to make code easy to understand, extendable, and efficient. In OOP, an object can have properties and methods. Properties are used to keep track of the state of the object and methods allow us to manipulate the state of the object. There are two main types of OOP: class-based and prototype-based programming languages. JS is a prototype-based programming language.
What is a JS engine?
On a base level, computers only understand binary. The JS engine allows us to give a machine a JS file and communicates the code to the computer. There are many different JS engines (known as ECMAScript engines). Major companies are all competing to create the fastest engines so that people use their tools.
What are the 3 different types of promise execution order and how do you implement each?
Parallel, sequence, and race: When it comes to promises, you need to make a decision about how you want the promises to execute in relation to each other. You might want to run them all at the same time in parallel, sequentially where each is dependent on or run after the previous promise, or as a "race" where you want to just use the promise that resolves first. In order to run promises parallel to each other we can use Promise.all like: const promises = [promise1, promise2]; const [output1, output2] = await Promise.all(promises); return output1, output2;. In order to run promises as a race we can use the Promice.race method like: const promises = [promise1, promise2]; const fastestOutput = await Promise.race(promises); return output;. In order to run promises in a sequence, we can just use async await like: async function sequence() { const output1 = await a(); const output2 = await b(); return output1, output2;. Here the await keyword is waiting for each of these to resolve before moving on to the next line of code.
What is partial application? How does it differ from currying?
Partial application is similar to currying but has some differences. It is a way to partially apply a function, meaning that you can take a function and apply some of its arguments so that it remembers them, then use closures to call it later on with all the rest of the arguments. For example, if we had a function const multiply(a, b, c) => a*b*c and wanted to first apply the a argument and later on apply the b and c arguments, we could instead write it as const partialMultiplyBy5 = multiply.bind(null, 5) and then later be able to supply the other two arguments like partialMultiplyBy5(4, 10). The difference between currying and partial application is that with currying you supply strictly one argument at a time whereas with partial application you can later supply multiple arguments at a time.
Explain stack overflow.
Stack overflow happens when for example in a recursive function, the function calls itself infinitely and the call stack runs out of room to keep track of all of the function calls. Previously, when this happened, the browser would just crash, but Chrome now has an upper limit after which it returns an error to prevent the browser from crashing.
What is the difference between pass by value and pass by reference?
Primitive types are immutable, meaning that they can't be changed. Instead, we need to remove the value from memory and and create a new value. Primitive types are "passed by value", so various variables holding primitive values don't know about each other and exist independently in memory. If you create a variable const a = 5 and then another, const b = a, variable b will copy the value from a and point it to a new separate space in memory holding the value 5. On the other hand, objects are "passed by reference", meaning that if you create an object const obj1 = { name: "Zoe" } and assign a new variable const obj2 = obj1, then change the value obj2.name = "James", both objects will have James as their name value since the variable obj2 is just a reference to the place in memory where the initial object created in obj1 lives. The advantage of objects being passed by reference is that by just having one object and not cloning it every time it is referenced, we are saving memory in the memory heap. This can become a disadvantage because it might become more likely for somebody to accidentally change a property on the object.
What is the difference between Promise.all() and Promise.allSettled()?
Promise.all() only works if all promises given to it resolve. In order for it to work, we need to have a catch block. Promise.allSettled() works similarly to Promise.all() except it doesn't wait for all promises to resolve, it just waits for all promises to be settled, whether they are resolved or rejected.
What are the pros and cons of the module pattern?
Pros: We are only polluting the global namespace with one variable which is the module itself, everything else is enclosed within the IIFE's scope. This is good for maintainability because a module is self contained and doesn't affect the outside world. They are easy to reuse using the return object. This allows us to only define the functions once and if we need to change some functionality we can do so in just one place. Cons: We are technically still polluting the global namespace with our modules. If we have a global variable with the same name as the module, we could overwrite the entire module. It's hard to determine the order of dependencies when including various script tags since the order in which script tags are included in the HTML files dictates what variables the below files have access to.
What is the difference between context and scope?
Scope is a function-based concept - it refers to the variable access of a function, or what is in the variable environment for a function when it is invoked. Context is more object based - it refers to the value of the "this" keyword, which is a reference to the object that owns the current executing code. The difference is that context refers to how a function is invoked with the value of the "this" keyword, whereas scope refers to the visibility of variables.
What are threads?
Tasks in a browser or in Node are executed in threads. Whenever a new tab is opened, the browser creates a new thread and environment for that tab. While the JS program runs on the main thread, the browser also has web workers that work in the background and handle some complicated tasks. Worker threads run in parallel to the main thread and communicate via messages. They don't have access to all of the web APIs such as the window or document object, etc. but they do have some abilities like setTimeout. They are not commonly used or interacted with but it's good to keep in mind that they exist.
Explain the "this" keyword in JS.
The "this" keyword comes with the global object and function execution context and refers to the object that the function is a property of. It can be used in an object method to access properties in that object. When a method from an object is called (e.g. obj.func()) whatever is to the left of the dot is the "this" value for that function. The keyword was created to: 1. Give methods access to their object and other methods/properties within that object. 2. Allow us to execute the same code for multiple objects - we can write a function that uses the this keyword and then it can be used in different objects with different values.
Describe the JS browser runtime and how it handles synchronous and asynchronous operations.
The JavaScript runtime consists of more than just the JS engine - the browser provides various tools that can work in the background while the synchronous JS code is running. Every browser has a JS engine implementation along with a JS runtime that provides a web API, event loop, and callback queue which enables asynchronous activity. The web API provides a variety of methods that can be used on the browser that are not native to JS. It can do a variety of things such as send HTTP requests, listen to DOM events, delay execution using timeouts or intervals, or even for caching/storage on the browser. Browsers use low level languages under the hood to perform these operations in the background. Web APIs are asynchronous, so they can do these operations in the background, and return values when they are done. When the callstack comes across a method that belongs to the web API, it passes it to the web API to execute in the background. Once the web API is done with whatever it is doing, it passes the return data to the callback queue. The event loop continuously monitors the callback queue and the call stack, and pushes the value from the callback queue to the call stack when the call stack is empty and the synchronous items in the file have all been executed.
What does the Object.create() method do?
The Object.create() method allows us to use prototypal inheritance to create a new object that inherits the methods from another object. For example, if we have an object with methods that we wish to inherit into a factory function, we can do let newItem = Object.create(methodObject) and then attach other properties to the newItem object. This illustrates true prototypal inheritance but isn't used commonly in practice.
How does the V8 JS engine work?
The V8 engine is used to translate the JS file to bytecode (or machine code) so that it can be executed by the computer. 1. HTML parser comes across script tag in HTML file 2. The JS file is fetched from the network or cache 3. This returns a stream of bytes which are passed to the byte stream decoder. The byte stream decoder decodes the stream of bytes and generates tokens based on the data that it receives For example, if the bytes decode to "function", the byte decoder can recognize that that's a keyword in JS and save it to a token 4. As the tokens are created, they are sent to the parser. The parser creates nodes based on tokens that match a syntax role in JS (e.g. VariableDeclaration) 5. The parser creates an abstract syntax tree using the nodes. At this point, it also checks for SyntaxErrors 6. The AST is sent to V8's Ignition interpreter, which generates the bytecode based on the AST that it receives 7. The bytecode that is generated goes through the bytecode optimizer and then the bytecode interpreter, after which it is executed. At this point, the machine can understand what's going on. 8. For operations that are repeated multiple times, the TurboFan optimizer is used to turn the bytecode into machine code which is even faster for the computer to process The above outlines the parsing and compilation steps that the V8 engine goes through. After these steps are done and the code is ready to be executed, the call stack/memory heap/event loop process begins
What is hoisting?
The behavior of figuratively moving variables or function declarations to the top of their respective environments during compilation. This occurs because JS engine allocates memory for functions and variables during the creation phase before executing.
What is the module pattern?
The goal of modules was to create a module scope that lived between the global scope and the function scope that allowed us to explicitly specify which functions and pieces of data we want to use in our file without polluting the global namespace. This was first implemented using the module pattern that uses closure and encapsulation to create a module scope. This was first done using IIFE's which allowed the variables that inner functions use to be enclosed within their scope but still immediately run. The module pattern assigns this IIFE to a variable and then returns an object from the IIFE that includes any of the variables and methods within it that are intended to be accessible from outside the function itself. This pattern is used by jQuery under the hood.
What is the call stack and the memory heap?
The memory heap is used to store information (variables, objects, and data) and allocate memory, using variables to point to the various bits of information. The call stack keeps track of where we are in the code, so that we can run the code in order. It runs functions in first in, last out order where the most deeply nested functions get added to the stack last and popped off (executed) first. As the functions in the call stack are executed, they refer to the memory heap to grab the values of the various functions, variables, and objects. Since JS engines are implemented differently, the storage of variables is not consistent between all engines. Some engines can store simple variables on the stack, and store complex data types like objects in the memory heap.
1. What are the JS data types?
There are 7 types in JS: numbers, booleans, strings, undefined, null, Symbol, objects.
What are some ways that static typing has been introduced into JS? Why has this become popular?
There are many ways that static typing can be introduced into JS: Flow, Elm, ReasonML, and TypeScript. ReasonML and Flow were created by Facebook and TypeScript was created by Microsoft, but all of these solutions make JavaScript a statically typed language. The goal of these is to make writing JS less buggy by introducing variable types. Flow is used commonly with React projects and works as a static type checker. It adds types by writing code that works with Flow and putting it into a compiler like Babel which converts code into ES5 so that all browsers can understand it. Babel then removes all of the changes to the code that have been made to use Flow to check types. It is used by default in create-react-app. TypeScript is different because it has its own compiler (it is a superset of JS so it just adds functionality on top of JS). The TypeScript compiler converts TypeScript to JS that the browser can read. ReasonML and Elm also have their own compilers but are entirely different languages and don't work as a superset of JS. Both of these are relatively new and not used very commonly. Angular is built with TypeScript but a lot of the React and overall JS community is starting to use TypeScript. Static type checking should be used under the below circumstances: 1. The project is continuously growing. 2. Tests are already in place and as developers join the team, you want to avoid bugs and make sure the code is self documenting. 3. It can be expected that development time will be slower and we will get the reliability and type safety as a tradeoff.
How do you handle exceptions/errors for synchronous code?
There is a native Error() constructor function in JS that can be used to create new instances of errors using new Error('error message'). Using this constructor doesn't actually do anything on its own - we need to throw the error which stops the scripts from executing. During runtime, when a throw statement is encountered by the program, the execution of the current function stops and control will be passed to the next thing on the call stack. When an error is encountered in the program, it checks to see if there is a catch at any point below it on the call stack and if not, it runs the onerror() function on the browser and the process.on('uncaughtException') function in Node. In order to efficiently handle errors and allow our programs to keep running, we can manually implement these catch statements. In synchronous JS, we can catch errors in our program using the try {} catch(error) {} block or the catch() method. The try {} catch(error) {} block runs the code within the try block and if any errors occur, they get handled in the catch block. The catch block accepts an error parameter that we can use to access the actual error. You can also use a finally block after the try and catch blocks which will run whatever is inside of it regardless of what happens in the try block or the catch block. This method can be used to handle any synchronous types of errors but doesn't work for asynchronous code.
Explain hidden classes.
They are used under the hood to optimize property access time by storing the offsets of all the properties of an object similarly to how objects are interpreted in statically typed languages like Java (object properties are predefined and cannot be changed in statically typed languages). If two objects are created using the same class and then more key/value pairs are added that were not predefined, and this is done in a different order for each object, multiple hidden classes will be created which can slow down execution. To avoid this, all class properties should be either predefined in the constructor (preferred), or new object properties should be instantiated in the same order for all objects. This issue applies to the delete keyword too, because the hidden classes are changed and no longer match up.
What does "favor object composition over class inheritance" mean?
This is a quote from "Design Patterns: Elements of Reusable Object-Oriented Software". It means that code reuse should be achieved by assembling smaller units of functionality into new objects instead of inheriting from classes and creating object taxonomies (e.g. gorilla banana problem).
What are pure functions?
Two main concepts of pure functions: the function must always return the same output given the same input and the function cannot modify anything outside of itself (no side effects). A function has side effects if the it modifies anything outside of itself. Side effects make code a lot more complicated and prone to bugs since the function is acting on shared state. In order to create a function with no side effects, we can create a new array using the newArray = [].concat(origArray) method or spread syntax. We can then mutate the copied array and return that array instead of mutating the original array. Because a pure function doesn't affect the outside world, we know what to expect from it. Consistently the same output for the same input is known as referential transparency. It means that if you change a function to its return value, it shouldn't have an effect on the program.
At what point does TypeScript type check?
Type checking in TypeScript occurs during the compilation phase of the code which occurs during build time. JavaScript on the other hand will either use type coercion to convert values to different types in order to avoid errors, or will throw a TypeError during runtime.
What is TypeScript?
TypeScript is a programming language developed and maintained by Microsoft which adds optional static typing to JS. It is designed for the development of large applications and transcompiles to JavaScript.
What is the difference between an undeclared and undefined variable?
Undeclared: when you are trying to access a variable which hasn't been declared using var, let or const. Undefined: when a variable has been declared using var, let or const but isn't given a value so has the default value of undefined.
How does hoisting work with variables?
Variables are assigned the value undefined so that the JS engine can make space for the variable in memory. Since the values aren't assigned yet, variables are partially hoisted. This is why if we log a variable before it is declared in the code, we get undefined, whereas if we call a function before it is declared, it works as expected. Variables declared with let or const do not get hoisted - only variables declared with var. If the same variable is declared twice, the first value will be kept because the compiler will skip any variables with the same name since that variable name is already assigned a value undefined in memory.
What is an execution context?
When a JS file is initially run, the JS engine creates a global() execution context automatically and pushes it to the call stack. When a function is called using the bracket notation, the JS engine creates a new execution context for that function and adds that execution context to the call stack on top of the global() execution context. When all functions/execution contexts are popped off of the call stack, and everything in the file has been executed, the global() execution context also gets popped off of the call stack. The global execution context gives us the global object (window in the browser, global in Node.js) and the this keyword. Initially, this === window. Execution context tells us which lexical environment is currently running.
Explain type coercion.
When the operands within an operation are different types, one of them will be converted to an equivalent value by the JS engine. JavaScript has a particularly heavy type coercion nature to it because it is dynamically typed.
Explain strong and weak typing.
While dynamic/static typing refers to the need to explicitly state the type of a variable while declaring it, strong/weak typing refers to the usage of type coercion when combining variables. For example, in JS (which is weakly typed) if you add a string and number together, it will coerce the number into a string and then append it to the first string. In a strongly typed language like Python, this would throw an error.
What are some problems that come with inheritance? How can these problems be fixed with composition?
With inheritance, code is structured around what things are. Classes have data and methods to act on that data. With composition, code is structured around what it has or what it does to data. When we define things as what they are, it makes it more difficult to change the structure of our data. This is called the tight coupling problem and means that if we make a small change to a class, we create a rippling effect to all subclasses. If a parent class is changed and a subclass has an extended version of the parent's function, it's possible that the change in the parent's function would break something in the subclass's functionality. This is known as the fragile base class problem. Another problem we might encounter with inheritance is the hierarchy problem where we might want a class that is lower in the hierarchy to only have one method but instead it inherits all methods and properties from all its ancestor classes. This is also known as the gorilla banana problem where we might just want a banana but instead we get a gorilla holding the banana and the entire jungle along with it. How can these issues be fixed with composition? We can instead create a base object and then add methods to it using composition. We can create a function that accepts our base object and returns a copy of it with the method as a property on the new object. This allows us to use composition to create copies of the base object with various methods in a way that is more flexible than inheritance.