JavaScript Review — Part I.

Soo Kim
7 min readJul 1, 2022

After doing a project using Node.js, I realized that I hadn’t fully gone through JavaScript and was still getting some things confused with Dart. So, I decided to go over some concepts again.

Here are the concepts I go through in order:
- Arrow Functions vs. Regular Functions
- Hoisting
- Prototype
- Map
- For…in loop vs. For…of loop
- Destructuring

Arrow Functions vs. Regular Functions

Up until now, I’ve been using only arrow functions, but I’ve come to the realization that arrow functions lack some features of regular functions.

(1) Arguments Binding
-
Arrow functions do not have an arguments binding (but they do have access to the arguments of the nearest non-arrow parent function)

(left) regular function // (right) arrow function

(2) this keyword binding
-
Arrow functions do not have their own “this”, and similar to arguments, it will be the value of this of the closest non-arrow parent function (lexical this).

(3) Prototype Chaining & Constructor
- Arrow functions do not have access to prototype chaining nor constructor function. Therefore, you cannot use the new keyword to create an instance with arrow functions. (More on prototypes below).

Hoisting

Hoisting is a technique which moves variables and function declarations to the top of their scope before code execution begins.

Function Declaration // need function name
function sayHello(name) {
console.log(`Hello ${name}`);
}
Function Expression // don't need function name
const sayHello = function(name) {
console.log(`Hello ${name}`);
}

When a function is defined using a function declaration, a function object is created before run-time, which allows for hoisting.

sayHello("Soo"); // function hoistingfunction sayHello(name) {
console.log(`Hello ${name}`);
}

For variable hoisting, you need to use the var keyword (declaring with let keyword will result in a ReferenceError — ). This is because variables declared using var are both declared and initialized before runtime, whereas those declared with let are declared before runtime, but initialized after runtime.

I personally haven’t found a use for hoisting yet…and my personal opinion is that it could leave room for potential errors.

Prototype

I’m used to Dart, which is a class-based language but JavaScript does not have class implementation per se, and uses prototype. Prototypes are used to create inheritance among Objects and provide shared property. All prototypes are linked to a constructor function. You can access prototypes of an object through __proto__.

Above, I declared a created a constructor function and defined the bark prototype separately, but you can use Immediately Invoked Function Expression(IIFE) to bring those separate lines of code into one large function. IIFEs are functions that are immediately executed after being defined, and cannot be called again.

(function () { 
...
}()); // the bracket() here is what executes the function

Through prototype chaining, you can access methods and properties of parent prototypes. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.

You can create a property with the same name as the prototype, which overrides the instance property (Property Shadowing). If you delete the instance property and try to access it again, it will still be accessible through the prototype. You must do it directly through the prototype itself.

Map

Unlike Dart, which uses curly brackets {} for a Map, JS creates an instance of Map class using new Map(). I’ve been using Objects only because it provides the key-value relationship I need, and its easy to find the properties with a dot-notation. However, it seems that there are some cases where Map would be preferable.

  • Map’s key type can be anything (functions, objects, primitive) whereas Object’s keys must be a String or Symbol
  • you can easily find out the size of a Map, but not for Objects
  • Better for frequent additions and removals of key-value pairs
  • You can use for-each and for-of loops for Maps (for-loop in the next section)
  • Maps are considered iterables, where as Objects are not
const hobbyMap = new Map([["Soo", "snowboarding"], ["Mike", "singing"], ["Sarah", "DJing"]]);const hobbyFunc = (value, key, map) => console.log(`${key} likes ${value}`);hobbyMap.forEach(hobbyFunc);
// Soo likes snowboarding
// Mike likes singing
// Sarah likes DJing

Although you can technically set a property of a Map like an Object, it does not actually store it in the Map for queries. You should use .set(key, value) method.

const infoMap = new Map([["firstName", "Soo"], ["lastName", "Kim"]]);infoMap.hasPets = true;
console.log(infoMap.hasPets); // true
console.log(infoMap.has("hasPets")); // false - does not work!
infoMap.set("hasPets", true);
console.log(infoMap.has("hasPets")); // true
infoMap.set("petNames", ["Happy", "Queens", "Luna"]);
console.log(infoMap.get("petNames")); // ["Happy", "Queens", "Luna"]

For…in loop

  • loops through enumerable properties of an object
    - my quick cheat definition of enumerable property is simply keys (= .hasOwnProperty)
  • each iteration returns a key (or if there is no explicit key associated with the object, the index)
const fruitArray = ["apple", "banana", "orange", "peach"];for (const x in fruitArray) console.log(x); // 0 1 2 3 for (const x in fruitArray) console.log(fruitArray[x]); 
// apple banana orange peach
const nameObject = { first: "Soo", last: "Kim" };for (const x in nameObject) console.log(x); // first last for (const x in nameObject) console.log(nameObject[x]); // Soo Kimconst infoMap = new Map([["name", "Soo Kim"], ["numOfPets", 3]]);for (const x in infoMap) console.log(x); // undefined

For…of loop

  • loops through the values of an iterable object, such as String, Array, Map, DOM
const fruitArray = ["apple", "banana", "orange", "peach"];for (const x of fruitsArray) console.log(x);
// apple banana orange peach
const nameObject = { first: "Soo", last: "Kim" };for (const key of Object.keys(nameObject)) console.log(key);
// first last
const infoMap = new Map([["name", "Soo Kim"], ["numOfPets", 3]]);for (const entry of infoMap) console.log(entries);
// ["name", "Soo Kim"] ["numOfPets", 3]
for (const key of infoMap.keys()) console.log(key ); // name numOfPetsfor (const [key, value] of infoMap) console.log(`${key}: ${value}`);
// name: Soo Kim numOfPets: 3

Destructuring

For iterables, you can destructure it and assign it to new variables according to its index, using square brackets [ ]. On the left side, you’ll define the new variables, and on the right must be the iterable.

const numbers = [1, 2, 3];
const [one, two, three] = numbers;
console.log(one, two, three); // 1, 2, 3
function Person(name) {
[this.firstName, this.lastName] = name.split(' ');
}

The assignment is in order but you do not need to have the same amount of variables on each side.

const numbers = [7, 8, 9];
const [seven, , nine] = numbers;
console.log(seven, nine); // 7, 9

You can also assign values to variables, but it DOES NOT supersede the destructured assignment.

const numbers = [1, 2, 3];
const [one, two, three = 10, four = 4] = numbers;
console.log(one, two, three, four); // 1, 2, 3, 4

For Objects, the destructuring is based on property key, using curly braces {}. The order does not matter BUT the new variable name must be the same as the property key.

const me = { firstName: "Soo", lastName: "Kim" };
const { firstName, lastName } = me;
console.log(firstName, lastName); // Soo Kim

You can rename the new variables like so:

const me = { firstName: "Soo", lastName: "Kim" };
const { firstName: myName, lastName: myLastName } = me;
console.log(myName, myLastName); // Soo Kim

It’s useful when you want only certain property values from a large Object.

The above can be tweaked like so that in the parameter itself, you can destructor the Object to be received.

You can use destructoring assignment for array and Object combined.

const employees = [
{ name: "Soo Kim", age: 30, job: "developer" },
{ name: "Mary Jane", age: 25, job: "marketer" },
{ name: "Lucy Jones", age: 37, job: "manager" },
];
const [ , { name: managerName }] = employees;
console.log(managerName); // Mary Jane

For an Object within another Object:

const manager = {
name: "Lucy Jones",
age: 37,
contact: {
email: "blah@blah.com",
mobile: "123456789",
}
};
const { contact: { email: managerEmail } } = manager;
console.log(managerEmail); // blah@blah.com

--

--