Skip to content

Instantly share code, notes, and snippets.

@pedpess
Last active May 31, 2022 09:48
Show Gist options
  • Save pedpess/2618ce746db94280f67052301470f1d0 to your computer and use it in GitHub Desktop.
Save pedpess/2618ce746db94280f67052301470f1d0 to your computer and use it in GitHub Desktop.
Javascript Study Notes

Javascript Study Notes

(by @pedpess)


These are my own paper version notes meeting the digital world. They are related to my learnings from 2017-18, or just stuff I thought would be nice to keep a record during the time I was grasping some Javascript topics.

PS: Don't expect all the material here to be 100% accurate or up-to-date. If you find some part that's wrong, or missing or old, please send me a comment to fix! :)

*The tips part are my own thoughts++.


Javascript

Data types mutability/immutability

In JS primitives are immutable. Primitive data types in JS are number, string, boolean, undefined, null and Symbol, which was recently added in ES5. Everything else in JS is an Object e.g. functions, which is mutable and stored by reference in memory. Everything that is not a primitive is stored by reference and not by value. An example of storing values by reference can be seen bellow.

+-----------+
|           |       new ObjectC
| ObjectA   +----------------+
|           |                |
+-----------+         +------v-----+
                      |            |
                      |   ObjectC  |   *References are alike C pointers
                      |            |
+-----------+         +------+-----+
|           |                ^
| ObjectB   +----------------+
|           |       new ObjectC
+-----------+

In this example, ObjectA and ObjectB are getting the reference from ObjectC. Thus, they both point to the same reference in memory.

Object declaration vs new operator

Choose { } object declaration way than new operator when defining your object structures. In the object declaration way you can define your object and assign values for its keys without having to call functions like new operator does with Object.create, and you won't even have the values assigned to the keys of your objects.

Explicit vs implicit coercion

JS being also dynamic typed language can coerce certain values. The explicit way would be through autoboxing String(name). The implicit way would be, for example, defining a number 42 + '', adding the quotes after a sum with the number make implicit to JS this operation will be converted to a string type.

Operators == vs ===

The comparison operators == vs ===. The == compares key/value pairs of an object while === checks if the object reference are also the same.

Ways of accessing object keys/values

You can access object values like this: objectA.number // '123' or objectA['number'] // '123'. Also, the keys of an object can be dynamic. For instance:

const key = 1
object[key] // 1

Shallow copy vs deep copy

  • Shallow copy: Grabs key/value pairs of some object and just throw them into another object blindly. Object.assign does that.
  • Deep copy: Expensive operation, it takes key/value pairs and plus, get every layer/node of an object deeply cloned.

Prototype chain

The inheritance of classes (they are actually objects) in Javascript happens through Object.prototype. Almost all objects in Javascript are instances of Object.

*Tip: If you want to go deeper in the prototype chain of an object you can try like this, objectA._proto_._proto_. In this example, we are going two levels upper to the next object that objectA has a reference targeted. BUT, you can also overwrite a prototype with a new function. Thus, don't do it, it breaks the previous state of the object.

Primitive data types have prototype wrappers/autobox.

const x = 42

x instance of Number // This will return false.

A problem with prototype chain is if you try to find a property in an objectA and this very same object doesn't have the property you are looking for, JS will look to the next objectB prototype you previous objectA has a reference from. If it finds a match then the value of objectB is displayed instead. This might mislead showing properties that you are not looking for.

Defining a variable with const and let

  • const: The const might lead the thought "this will 'automagically' makes the value immutable", but it won't. Actually, the const syntax is not immutable, but it's reference can't be reassigned and the value can't be declared again. It's block-scoped.

  • let: The let syntax compared to const can have its reference reassigned to another variable, it's also block-scoped and can be just defined once (the old var can be redefined).

let x = 20
x = 21 // 21 - Reference being reassigned

const x = 20
x = 21 // TypeError - Reference not being reassigned

Hoisting var and functions

Declare functions at the end of the file and call them in the top of the file. This is being hoisted.

hoisting(); // I'm being hoisted

function hoisting() {
  return `I'm being hoisted`;
}

Also, defining your variables with var can cause it being hoisted. The global variable is var. In another hand, const is not in use until it's not declared.

*Tip: Declare your variables and functions before calling them, just for better code reading.

Javascript Engine

A quick walkthrough of how it works:

  1. Reads the whole file and check for declaration errors. After that, functions are saved in memory and variable will be declared.
  2. Executes the code and variables are initialized.

Execution stack or Callstack

The Callstack is a synchronous part of how JS deals with the execution of the functions from the application you write. The asynchronous part is handled by browser APIs like .setTimeout()

A glance at how the Callstack architecture and execution happens can be seen in the example bellow:


                                             +-----------------------+
                                             |                       |
                                             |                       v
                                             |                 2)Callstack
                                             |             +------------------+
                               1)getNumber1()|             |                  |
                                 getNumber2()+             |                  | *It implements LIFO logic
                                 getNumber3()              |                  |
                                                           |                  |
                                                           |                  |
                                                           |                  |
                                                           |                  +--------+
                                                           |                  |        |
                                                           +------------------+        |
                                                           |  getNumber1      |        |
                                                           +------------------+        |
                                                           |  getNumber2      |        | *Async calls
                                                           +------------------+        |
                           +------------------------------->  getNumber3      |        |
                           |                               +------------------+        |
                           |                                                           |
                           |                                                           |
                           |                                                           |
                           |                                                           |
                4)Function | Queue                  Event loop                         |
           +---------------+-------------+          Javascript single thread           |
           |                             |                                             |
           |  getNumber1, getNumber2     |                                             | 3)APIs
           |                             |                                     +-------v------+
           +--------------+--------------+                                     |              |
*It implements FIFO logic ^                                                    | getNumber1,  |
                          |                                                    |              |
                          +----------------------------------------------------+ getNumber2   |
                                                                               |              |
                                                                               +--------------+

In order to overflow the Callstack depends pretty much on the size of it and how many holding parameters and local variables. For instance, Chrome can hold 10402 recursive calls.

Closures

Functions have access to other variables that are inside of inner functions (if not let because it's block-scoped).

Immediately invoked function expression or IIFE can also create a closure as well. IIFE encapsulates function from outter effects and returns a value (usually to a const).

Ex:

const param = 'Hello';

(function (innerParam) {
    console.log(innerParam);
})(param);
// Hello

HOF or High Order Functions in Javascript

In a nutshell: HOF is a function that receives another function as a parameter (function fnA(fnB)) do something and returns a new function.

The three most famous ones in Javascript are: .reducer(), .map() and .filter().

How Promises came to be

The Promise feature came to JS as a 'promise' to solve the 'Callback Hell' problem while chaining multiple asynchronous callback requests. If you call a Promise, you have to wait for it to finish its execution. Promises are eagerly loaded. A promise after being settled will end up either being resolved or rejected (resolve/reject) if some error happens. You can't abort a Promise like you do in Task class in C# for example.

After Promises, many other implementations over it came to be, .fetch and most recently async/await, both of them return a Promise.

In a Promise you can process the data in three different data flows, they are: .then(), .catch() and .finally().

this in Javascript

In short: this notation refers to the context of an object (function invocation). If you don't define the context of the this, it will be referred to the global object called window.

Differences among .bind(), .call() and .apply()

The three of them you can set what context the this will be.

  • .bind(): It returns a new function where this is auto bound to store somewhere.
  • .call()/.apply(): They will immediately return the bounded function. The context is bound by the time you are writing it. The difference is that .apply() will also accept an array object as an argument for its second parameter.

Instead of using .bind() you can also use a so-called arrow function, which has the this already bound where you invoke your function. You can still rebind the arrow function adding a .bind()at the end of it though. The arrow function is lexically scoped different from normal functions that are based on context. The lexical scope is more interested in how you define the function.

const arrowFunction = () => {
  // doSomething
};
console.log(arrowFunction) // undefined

function nonArrowFunction() {
  // do Something
}
console.log(nonArrowFunction.bind(this)) // undefined

Iterators and Generators

Iterators

Iterators are all data structure that you can iterate over. For instance, all arrays have an implementation of an iterator that you can extract an interable from. This extraction can be done by using the new type from ES6 called Symbol(), this being a subtype of the type object. An example of how the extraction usinng Symbol() and object destructuring feature.

const arr = [1, 2, 3];

const [{ iterator }] = arr[Symbol.iterator]();

or

const iterator = arr[Symbol.iterator]()
iterator{[iterator]}

As mentioned, iterators can be used even inside of logical structures like for/of.

for (const number of numbers) {
  return numbers;
}

To iterate over numbers you can use iterator.next() to change from position 0 of the array to the n + 1. Once your array is in the end, it returns also a logical property that can be either done: true or done: false in case you haven't reached yet the end of the array, plus value, which is the undefined by default unless it contains something. You can even do that with a string which is in theory an array of char, where each letter occupy a position in the string array.

for (const person of persons[0]) {
  return persons;
}

*Good to mention:

  1. Iterators can be also async fetching data that can be gradually extracted from a for loop.
  2. Another ES feature that massively uses Iterators are Generators. Generators are synthatic sugar to create an iterable. There is an abstraction over each of these ES features.

+---------------+ +----------------+ +-----------------+ | for...of | | Iterator | | Generator | | | <-----------+ | <---------+ | +---------------+ +----------------+ +-----------------+

Generators

As mentioned in the Iterators part, Generators are a synthatic sugar that implements an Iterator. The basic syntax structure of a Generator is as it follows.

function* fn() {
  yield "Do Something";
  yield "Do Another Thing";
}

const generator = fn();

generator.next(); // "Do Something" - "Object { value: "Do Something", done: false }"
generator.next(); // "Do Another Thing" - "Object { value: "Do Another Thing", done: false }"
generator.next(); // "Object { value: undefined, done: true }"

The * marks this function will use the Generator power and yield is actually what it's written. It will yield the operation stopping the function giving you what you want. A better naming for yield would be to produce or to provide.

In a nutshell: Generators are "pausable" functions.

*Things to consider about Generators:

  1. The yield syntax is that specify what iterator.next() implementation should do.
  2. The iterator is the "driver" to run your Generator. So, iterates over and over again, perhaps that's why may be a good idea to use while logical structure.
  3. As an analogy of the idea, a debugger tool is a Generator that iterates over your code :).
Async iterators/generators

You may also want to use the combination of async/await with Generators to stream or pull certain async data like a request to an API. Generators would be a way to represent this stream of data.

A small piece of code using async iterators, which retrieves images from an API can be seen bellow. The same code could be shortened by using generators as well.

return {
  [Symbol.asyncIterator]: async function*() {
    let pageIndex = 1;
    while(true) {
      const pageData = await api(...args);
      for(const url of pageData) {
        yield url;
      }
      pageIndex = pageIndex++;
    }
  }
}

Async iterables might be the standard way of writing streams alike with libraries such as, Bacon.JS and RxJS.


License of usage of my notes

© Pedro Pessoa, 2017. Unauthorized use and/or duplication of this material without express and written permission from this text author and/or owner is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to Pedro Pessoa and with a reference to this gist link with appropriate direction to the original content.

Creative Commons License

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment