├── Z_00_0_patterns.md ├── 003_0_error-handling.md ├── 007_0_closures.md ├── 005_0_scope.md ├── README.md ├── 00_0_intro.md ├── 002_0_statements-and-loops.md ├── Z_00_0_callbacks-promises-async_await.md ├── 00_1_variables.md ├── 001_0_data-types-and-operators.md ├── 005_1_this.md ├── 001_3_data-types-functions.md ├── 006_0_OOP.md ├── 001_1_data-types-objects.md ├── 001_2_data-types-arrays.md ├── async.md ├── Z_00_0_basic-es2015.md └── Z_00_0_basic-oop-with-js.md /Z_00_0_patterns.md: -------------------------------------------------------------------------------- 1 | # Patterns 2 | 3 | -------------------------------------------------------------------------------- /003_0_error-handling.md: -------------------------------------------------------------------------------- 1 | # Error handling 2 | 3 | ## Try and Catch 4 | 5 | ```js 6 | const person = { 7 | name: 'Peter', 8 | age: 30, 9 | get personData() { 10 | return `${this.name} is ${this.age} old`; 11 | }, 12 | set newName(name) { 13 | if (typeof name !== 'string') { 14 | throw new Error('Name must be a string'); 15 | } 16 | this.name = name; 17 | } 18 | } 19 | 20 | try { 21 | person.newName = 1; 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | 26 | // Error: 'Name must be a string' 27 | ``` -------------------------------------------------------------------------------- /007_0_closures.md: -------------------------------------------------------------------------------- 1 | # Closures 2 | 3 | 6 | 7 | They give us access to an outer function's scope from an inner function. 8 | 9 | More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures 10 | 11 | 12 | ```js 13 | function createFunction() { 14 | const num = 1; 15 | 16 | function increaseNumber(numberToIcrease) { 17 | return num + numberToIcrease; 18 | } 19 | 20 | return increaseNumber; 21 | } 22 | 23 | const cf = createFunction(); 24 | 25 | const increaseWith3 = cf(3); 26 | console.log(increaseWith3); // 4 27 | ``` -------------------------------------------------------------------------------- /005_0_scope.md: -------------------------------------------------------------------------------- 1 | # Scope 2 | Determines the context on which a variable can be accessed. 3 | 4 | This is constant is declared in the `global scope` and can be accessible everywhere. 5 | ```js 6 | const name = 'Peter'; 7 | 8 | const logName = (name) => { 9 | console.log(name) 10 | } 11 | 12 | console.log(name); // Peter 13 | logName(name); // Peter 14 | ``` 15 | 16 | This constant is declared in a `local or block scope` and can be accessible only inside `logName()` 17 | 18 | ```js 19 | const logName = () => { 20 | const name = 'Peter'; 21 | console.log(name) 22 | } 23 | 24 | logName(name); // Peter 25 | console.log(name); // undefined 26 | ``` 27 | 28 | One gotcha, `local variables` take precedence over `global variables`. 29 | 30 | ```js 31 | const name = 'Wendy'; 32 | 33 | const logName = () => { 34 | const name = 'Peter'; 35 | console.log(name) 36 | } 37 | 38 | logName(); // Peter 39 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning JavaScript 2 | 3 | **IMPORTANT**: Until new advise this will be more a collection of notes than a structured breviary. 4 | 5 | In 2018, the CTO of the company I was working for, entrusted me with the task of moving from unorganized JS to a JS MVC-ish implementation with React. For this purpose I created a repository to highlight some concepts and summarize the framework. It worked well (well enough), however, I found myself constantly going back and forth between React and JavaScript, something that polluted other training materials like Node (environment), Express (library), Vue (framework)... All these tutorials shared something in common: the language, JavaScript. 6 | 7 | Looking back, I see several repetitions that are not strictly related to the topic of some of those essays, but you definitely need to understand them before moving on. Someday I will make time to do a cleanup, but erstwhile, I need to consolidate all that knowledge in one place. This is learning JavaScript. 8 | 9 | ## Table of Contents 10 | 11 | - [Intro](./00_0_intro.md) 12 | - [Variables](./00_1_variables.md) 13 | - [Primitive Data Types and Operators](./001_0_data-types-and-operators.md) 14 | - Reference Data Types 15 | - [Objects](./001_1_data-types-objects.md) 16 | - [Arrays](./001_2_data-types-arrays.md) 17 | - [Functions](./001_3_data-types-functions.md) 18 | - [Statements and Loops](./002_0_statements-and-loops.md) 19 | - [Currying](./004_1_currying.md) 20 | - [Scope](./005_0_scope.md) 21 | - [This](./005_1_this.md) 22 | - [Closures](./007_0_closures.md) -------------------------------------------------------------------------------- /00_0_intro.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | 22 | 23 | 24 | JavaScript is a dynamic language. This means that we can change the type of the values that our variable are holding anytime. 25 | The type of the values in Dynamic languages is set at runtime in relation to the value. 26 | 27 | ```js 28 | let name = 'Peter'; 29 | name = 2; 30 | console.log(name); // 2 31 | ``` 32 | 33 | In static languages like JAVA, when we declare a variable we also assign its type. This prevents us to change the type of the value in the future. 34 | The type of the values in Static languages is set at compilation time. 35 | 36 | ```java 37 | String [] words = {"Hi", "Hello"}; 38 | words = 1; 39 | ``` 40 | 41 | In this example, we are declaring the variable `words`, assigning as its type `an array of strings` and setting `{"Hi", "Hello"}` as its value. 42 | Then, we are trying to set `1` (which is an integer), resulting in the following error: 43 | ``` 44 | error: incompatible types: int cannot be converted to String[] 45 | ``` -------------------------------------------------------------------------------- /002_0_statements-and-loops.md: -------------------------------------------------------------------------------- 1 | # Loops and conditional statements 2 | 3 | 12 | 13 | ## Conditional statements 14 | 15 | ### if... else 16 | 17 | ```js 18 | if (condition) { 19 | // ... 20 | } else if (anotherCondition) { 21 | // ... 22 | } else { 23 | // ... 24 | } 25 | ``` 26 | 27 | ### switch... case 28 | 29 | ```js 30 | switch (somethingToCompare) { 31 | case 'againstSomething': 32 | // ... 33 | break; 34 | 35 | case 'againstAnotherSomething': 36 | // ... 37 | break; 38 | 39 | default: 40 | // ... 41 | } 42 | ``` 43 | 44 | ## Loops 45 | 46 | Remember to have always an exit condition to avoid entering into an `infinite loop` 47 | 48 | ### while 49 | 50 | ```js 51 | while (condition) { 52 | // ... 53 | } 54 | ``` 55 | 56 | Example: 57 | 58 | ```js 59 | let i = 1; 60 | 61 | while (i < 4) { 62 | console.log(i); 63 | i++; 64 | } 65 | 66 | // 1 67 | // 2 68 | // 3 69 | ``` 70 | 71 | ### do... while 72 | First executes and then evaluates condition. So, the statement is executed at least once. 73 | 74 | ```js 75 | do { 76 | // ... 77 | } while (condition); 78 | ``` 79 | 80 | ### for 81 | 82 | ```js 83 | for (initialExpression; condition; incrementOrDecrement) { 84 | // ... 85 | } 86 | ``` 87 | 88 | Example: 89 | 90 | ```js 91 | for (let i = 1; i < 4; i++) { 92 | console.log(i) 93 | } 94 | 95 | // 1 96 | // 2 97 | // 3 98 | ``` 99 | 100 | ### for... in 101 | In `arrays` it will return each index as string 102 | In `objects` it will return the object keys 103 | 104 | ```js 105 | const arr = ['Hi','Hello','Hola'] 106 | 107 | for (let el in arr) { 108 | console.log(el); 109 | } 110 | 111 | // '0' 112 | // '1' 113 | // '2' 114 | 115 | const obj = { 116 | prop1: 1, 117 | prop2: 2, 118 | prop3: 3 119 | } 120 | 121 | for (let prop in obj) { 122 | console.log(prop); 123 | } 124 | 125 | // 'prop1' 126 | // 'prop2' 127 | // 'prop3' 128 | ``` 129 | 130 | ### for... of 131 | 132 | ```js 133 | const arr = ['Hi','Hello','Hola'] 134 | 135 | for (let el of arr) { 136 | console.log(el); 137 | } 138 | 139 | // 'Hi' 140 | // 'Hello' 141 | // 'Hola' 142 | ``` -------------------------------------------------------------------------------- /Z_00_0_callbacks-promises-async_await.md: -------------------------------------------------------------------------------- 1 | # Callbacks, Promises and Async/Await 2 | 3 | ## Async Callback 4 | An async callback is a function that is passed into another function (as a parameter) and invoked once the first function resolves or completes. 5 | 6 | In our example, we have the function `returnUserDataFromSourceX(userId, callback)` which receives 2 arguments: `userId` and the `callback`. I´m using `setTimeout()` to simulate the delay between "requesting and retrieving data from X source" (remember that this part is just an abstraction to focus on the callback´s side). 7 | Our operation "takes 1 second" to complete. As soon as we have the data, the `callback is invoked with that information`, and, since we are passing the function `logUser(dataFromDb)` as callback, the result is: `Hello Peter` 8 | 9 | ```javascript 10 | function returnUserDataFromSourceX(userId, callback) { 11 | // Do some ASYNC operation querying by userId 12 | // we will simulate the "delay" with setTimeOut() 13 | setTimeout(function() { 14 | callback({ name: 'Peter', age: 30 }) 15 | }, 1000) 16 | } 17 | 18 | function logUser(dataFromDb) { 19 | console.log('Hello', dataFromDb.name); 20 | } 21 | 22 | returnUserDataFromSourceX(1234, logUser); 23 | 24 | /* 25 | returnUserDataFromSourceX(1234, function(dataFromDb){ 26 | console.log('Hello', dataFromDb.name); 27 | }); 28 | */ 29 | 30 | ``` 31 | 32 | ## Promise 33 | Promises are objects that holds the result of an async operation. 34 | It has 3 stages: 35 | Pending 36 | Rejected 37 | Fulfilled or Resolved 38 | 39 | To consume the promise we use then() to get the result and catch() to get errors 40 | 41 | ```javascript 42 | function returnUserDataFromSourceX(userId) { 43 | // Do some ASYNC operation querying by userId 44 | // we will simulate the "delay" with setTimeOut() 45 | return new Promise(function(resolve, reject) { 46 | setTimeout(function() { 47 | resolve({ name: 'Peter', age: 30 }) 48 | }, 2000) 49 | }) 50 | } 51 | 52 | function logUser(dataFromDb) { 53 | console.log('Hello', dataFromDb.name); 54 | } 55 | 56 | const userFromDB = returnUserDataFromSourceX(1234); 57 | userFromDB 58 | .then(function(data) { 59 | logUser(data); 60 | }) 61 | .catch(function(err) { 62 | console.log(err); 63 | }); 64 | 65 | ``` 66 | 67 | ## Async and await 68 | 69 | ```javascript 70 | async function returnUserDataFromSourceX(userId) { 71 | 72 | const result = await new Promise(function(resolve, reject) { 73 | setTimeout(function() { 74 | resolve({ name: 'Peter', age: 30 }) 75 | }, 2000) 76 | }) 77 | 78 | return result; 79 | } 80 | 81 | let results = returnUserDataFromSourceX(1).then(function(response) { 82 | console.log(response); 83 | }); 84 | 85 | ``` -------------------------------------------------------------------------------- /00_1_variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | Variables store data in the computer's memory (this is a temporal storage). 3 | 4 | There are some constraints at the time of naming variables. 5 | 6 | 1. You cannot use `reserved keywords`, like `new`. 7 | 2. They should start with a letter. 8 | 3. The only symbol supported is `_`. 9 | 4. They are case sensitive. `age` and `Age` are totally different variables. 10 | 11 | Something important to consider is given them an appropriate name. There's no "how to" for this, just try to describe its value from "general" to "particular". 12 | 13 | 14 | 15 | 16 | We have 3 keywords to declare a variable in JS: `var`, `let` and `const` 17 | The main difference between `var` and `let/const` (ES2015) is that `var` is **function-scoped** (hoisted) and `let/const` are **block-scoped**. 18 | We should use const for values that should not change (constants). 19 | 20 | **Quick tip: AVOID using var** 21 | 22 | `var` summary: 23 | 1. Inside of a function, it will be hoisted to the top of the function. 24 | 2. Outside of a function, it will be attached to the `global` context (for example, `window.yourVarName`) 25 | 26 | > The main difference between `let` and `const` is that `let` allows us to reassign its value, while `const` persist its initial assignation. For variables that will not change the values they hold, you should use `const`. 27 | 28 | Let's jump to a quick example to see this in action. 29 | 30 | ```js 31 | function hoistingExample(name) { 32 | if (name) { 33 | var x = name; 34 | let y = name; 35 | } 36 | console.log(`With var x is ${x}`) 37 | console.log(`With let y is ${y}`) 38 | } 39 | 40 | hoistingExample('Peter'); 41 | ``` 42 | 43 | Result: 44 | ``` 45 | With var x is Peter 46 | ReferenceError: y is not defined 47 | ``` 48 | 49 | **What's going on...?** 50 | The variable declared with the `var` keyword is hoisted, or, raised to the top of the scope (in this case, the function). Remember: `const` and `let` are `block-scoped`. 51 | 52 | So, under the hood, this is what the interpreter is doing... 53 | 54 | ```js 55 | function hoistingExample(name) { 56 | var x; 57 | if (name) { 58 | x = name; 59 | let y = name; 60 | } 61 | console.log(`With var x is ${x}`) 62 | console.log(`With let y is ${y}`) 63 | } 64 | 65 | hoistingExample('Peter'); 66 | ``` 67 | 68 | ... and, it is what is going to be executed at runtime. 69 | 70 | But, what happens if we don't pass any argument. 71 | 72 | ```js 73 | hoistingExample(); 74 | ``` 75 | 76 | Result: 77 | 78 | ``` 79 | With var x is undefined 80 | ReferenceError: y is not defined 81 | ``` 82 | 83 | As you can see, the value of `x` will be `undefined`. 84 | Why? Well, the interpreter is going to host the variable declaration to the top of the scope, but, since its value is assigned inside the if statement and we are not meeting the if condition... Te value of `x` will resolve to `undefined`. 85 | 86 | 87 | We stated that `let/const` are block scoped, or "colloquially" tied to the curly braces `{}` 88 | 89 | 92 | 93 | This nested if example could make things clearer... 94 | We are declaring `x` in a 3 levels nested block, however, we have access at the top of the function. So, in each if (or block), we have access to `x` too. 95 | 96 | ```js 97 | function hoistingExample(name) { 98 | if (true) { 99 | if (true) { 100 | if (true) { 101 | var x = name; 102 | } 103 | } 104 | } 105 | console.log(name); 106 | } 107 | 108 | hoistingExample('Peter'); // Peter 109 | ``` 110 | 111 | **var and global scope** 112 | 113 | ```js 114 | var name1 = 'Peter'; 115 | let name2 = 'Paul'; 116 | 117 | window.name1; // Peter 118 | window.name2; // undefined 119 | ``` -------------------------------------------------------------------------------- /001_0_data-types-and-operators.md: -------------------------------------------------------------------------------- 1 | # Data Types and Operators 2 | 3 | ## Data Types 4 | 5 | ### Primitives or value types 6 | 7 | **Important**: 8 | We have `primitives` and `Primitives Objects` like String. 9 | 10 | ```js 11 | const stringLiteral = 'string literal'; 12 | 13 | const stringObjConst = new String('string object constructor'); 14 | 15 | console.log(typeof stringLiteral); // string 16 | console.log(typeof stringObjConst); // object 17 | ``` 18 | 19 | JS wraps all primitives with their primitive objects so we can have access to methods defined in the constructor function. 20 | 21 | * null (used when we want to indicate the absence of value. For example, if the user didn't provide his favorite hobby) 22 | * undefined 23 | * boolean 24 | * number (all numbers are just numbers, we don't distinguish between int, float, long, like in other languages) 25 | * string 26 | * symbol (ES2015) 27 | 28 | 29 | 30 | If we don't initialize it, the default value and type of the variables will be `undefined`. 31 | 32 | ```js 33 | let name; 34 | 35 | console.log(name) // undefined 36 | console.log(typeof name); // undefined 37 | ``` 38 | 39 | This values are immutable. 40 | For example, when we split a string we are not mutating the string in place, we are creating a new string. 41 | 42 | **String literal vs template literal** 43 | 44 | ```js 45 | const variable = 'something'; 46 | 47 | const stringLiteral = 'string \nliteral ' + variable; 48 | 49 | const templateLiteral = `template 50 | literal ${variable}`; 51 | 52 | console.log(stringLiteral); 53 | // string 54 | // literal something 55 | 56 | console.log(templateLiteral); 57 | template 58 | // literal something 59 | ``` 60 | 61 | ### Reference types 62 | 63 | * object 64 | * function 65 | * array (in JavaScript dynamic, we don't set a length and a type like in other languages. Its type is object) 66 | 67 | **The main difference between primitives and reference types** is how the value is stored 68 | 69 | In `primitives`, the variable holds the value (copied by value). 70 | ```js 71 | let person1 = 'Peter'; 72 | 73 | // Here we copy the value of person1 tyo person2 74 | let person2 = person1; 75 | 76 | person1 = 'Wendy'; 77 | 78 | console.log(person1, person2); 79 | // Wendy Peter 80 | ``` 81 | 82 | In `references`, the variable holds a reference to the address (in memory) where the value is stored (copied by reference). 83 | 84 | ```js 85 | let person1 = { name: 'Peter' }; 86 | let person2 = person1; 87 | 88 | person1.name = 'Wendy'; 89 | 90 | console.log(person1, person2); 91 | // { name: 'Wendy' } { name: 'Wendy' } 92 | ``` 93 | 94 | --- 95 | 96 | ## Operators 97 | 98 | * Arithmetic: `+`, `-`, `*`, `**`, `/`, `%` and increment (`++`) and decrement (`--`) and augmented assignment like `+=` 99 | * Assignment: `const name = 'Peter';` and shorthands like `num += 3` 100 | * Comparison: equality `==`, `===`, `!=`, `!==` and relational `>`, `>=`, `<`, `<=` 101 | * Equality: `==` and `===` 102 | * Ternary: `condition ? if-true-value : if-false-value;` 103 | * Logical: `AND (&&)`, `OR (||)`, `not (!)` 104 | * Bitwise: `|` and `&` 105 | 106 | **Difference between** `strict equality`(===) and `lose equality` (==) 107 | 108 | 1. Strict equality: **same type and value** 109 | 2. Lose Equality: same value. It cast the left side value into the data type of the right side value. So, if we have... 110 | * 6 == '6' it will convert '6' into 6 and compare 111 | * 1 == true will convert 1 to true and compare 112 | 113 | **Note about** `not (!)` 114 | 115 | It will convert the value me pass into the opposite: 116 | 117 | ```js 118 | const name = !'Peter'; // false 119 | const num = !30; // false 120 | const bool = !false; // true 121 | const bool1 = !true; // false 122 | ``` 123 | 124 | **Falsy values** 125 | 126 | * 0 127 | * '' 128 | * false 129 | * undefined 130 | * NaN 131 | * null 132 | 133 | Everything else is **truthy** 134 | 135 | **Short-circuiting** 136 | 137 | When we are using `OR`, when we find a truthy operand it returns that operand (the remaining operands are omitted) 138 | 139 | ```js 140 | console.log(false || 1 || 2 || 3); // 1 141 | ``` -------------------------------------------------------------------------------- /005_1_this.md: -------------------------------------------------------------------------------- 1 | # This keyword 2 | This references the object executing the function. 3 | 4 | 10 | 11 | ## What is `this`...? 12 | 13 | 1. This on an object's `method` refers to the `object itself` 14 | 2. This on a constructor function's method refers to the `constructor function` 15 | 3. This on a `function` refers to the `global scope` 16 | 4. This on a `function` in a method as callback function refers to the `global scope` 17 | 18 | Note: In relation to `3` and `4`, if the container function uses `strict mode`, the value of **this** will be `undefined` instead of the `global object`. 19 | 20 | 21 | **On an object's method** 22 | ```js 23 | const person = { 24 | name: 'Peter', 25 | logThisContext() { 26 | console.log(this); 27 | } 28 | } 29 | 30 | person.logThisContext(); 31 | // { 32 | // name: 'Peter', 33 | // logThisContext: ƒ logThisContext() 34 | // } 35 | ``` 36 | 37 | **On a constructor function method** 38 | 39 | ```js 40 | function Person() { 41 | this.name = 'Peter'; 42 | this.logThisContext = function() { 43 | console.log(this); 44 | } 45 | } 46 | 47 | const peter = new Person('Peter'); 48 | 49 | peter.logThisContext(); 50 | // Person { 51 | // name: 'Peter', 52 | // logThisContext: ƒ (), 53 | // __proto__: { constructor: ƒ Person() } 54 | // } 55 | ``` 56 | 57 | **On a function** 58 | 59 | ```js 60 | function logContextOfThis() { 61 | console.log(this); 62 | } 63 | 64 | logContextOfThis(); // Window object 65 | ``` 66 | 67 | **On a function WITH strict mode** 68 | 69 | ```js 70 | 71 | 72 | function logContextOfThis() { 73 | 'use strict' 74 | console.log(this); 75 | } 76 | 77 | logContextOfThis(); // undefined 78 | 79 | ``` 80 | 81 | **On a function in a method as callback** 82 | 83 | ```js 84 | const person = { 85 | name: 'Peter', 86 | nicknames: ['Pet', 'P', 'Pe'], 87 | logNicknames() { 88 | this.nicknames.map(function() { console.log(this) }); 89 | } 90 | } 91 | 92 | person.logNicknames(); // Window object 93 | ``` 94 | 95 | **On a function in a method as callback WITH strict mode** 96 | 97 | ```js 98 | const person = { 99 | name: 'Peter', 100 | nicknames: ['Pet', 'P', 'Pe'], 101 | logNicknames() { 102 | 'use strict' 103 | this.nicknames.map(function() { console.log(this) }); 104 | } 105 | } 106 | 107 | person.logNicknames(); // undefined 108 | ``` 109 | 110 | ## Changing this context 111 | 112 | 1. Arrow function 113 | 2. Assign this to a variable 114 | 3. Using `Function.prototype.call()` or `Function.prototype.apply()` or `Function.prototype.bind()` 115 | 116 | We can use an `arrow function` which will inherit this from the container function, in this case logNicknames() 117 | 118 | ```js 119 | const person = { 120 | name: 'Peter', 121 | nicknames: ['Pet', 'P', 'Pe'], 122 | logNicknames() { 123 | this.nicknames.map(() => console.log(this)); 124 | } 125 | } 126 | 127 | person.logNicknames(); 128 | // { 129 | // name: 'Peter', 130 | // nicknames: [ 'Pet', 'P', 'Pe' ], 131 | // logNicknames: ƒ logNicknames() 132 | // } 133 | // { 134 | // name: 'Peter', 135 | // nicknames: [ 'Pet', 'P', 'Pe' ], 136 | // logNicknames: ƒ logNicknames() 137 | // } 138 | // { 139 | // name: 'Peter', 140 | // nicknames: [ 'Pet', 'P', 'Pe' ], 141 | // logNicknames: ƒ logNicknames() 142 | // } 143 | ``` 144 | 145 | We can assign this to a `variable` 146 | 147 | ```js 148 | const person = { 149 | name: 'Peter', 150 | nicknames: ['Pet', 'P', 'Pe'], 151 | logNicknames() { 152 | const self = this; 153 | this.nicknames.map(function() { console.log(self) }); 154 | } 155 | } 156 | 157 | person.logNicknames(); 158 | // { 159 | // name: 'Peter', 160 | // nicknames: [ 'Pet', 'P', 'Pe' ], 161 | // logNicknames: ƒ logNicknames() 162 | // } 163 | // { 164 | // name: 'Peter', 165 | // nicknames: [ 'Pet', 'P', 'Pe' ], 166 | // logNicknames: ƒ logNicknames() 167 | // } 168 | // { 169 | // name: 'Peter', 170 | // nicknames: [ 'Pet', 'P', 'Pe' ], 171 | // logNicknames: ƒ logNicknames() 172 | // } 173 | ``` 174 | 175 | We can use `function() {}.bind(this);` 176 | 177 | ```js 178 | const person = { 179 | name: 'Peter', 180 | nicknames: ['Pet', 'P', 'Pe'], 181 | logNicknames() { 182 | this.nicknames 183 | .map(function() { 184 | console.log(this); 185 | }.bind(this) 186 | ) 187 | } 188 | } 189 | 190 | person.logNicknames(); 191 | // { 192 | // name: 'Peter', 193 | // nicknames: [ 'Pet', 'P', 'Pe' ], 194 | // logNicknames: ƒ logNicknames() 195 | // } 196 | // { 197 | // name: 'Peter', 198 | // nicknames: [ 'Pet', 'P', 'Pe' ], 199 | // logNicknames: ƒ logNicknames() 200 | // } 201 | // { 202 | // name: 'Peter', 203 | // nicknames: [ 'Pet', 'P', 'Pe' ], 204 | // logNicknames: ƒ logNicknames() 205 | // } 206 | ``` -------------------------------------------------------------------------------- /001_3_data-types-functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | In JS functions are First-class functions because they are treated as any other variable. 3 | Remember that functions are objects. 4 | 5 | 1. We can assign a function to a variable 6 | 2. We can pass a function as an argument 7 | 3. A function can return a function 8 | 9 | 13 | 14 | 15 | 40 | 41 | When we call a function the first thing that happens is that a new `execution context` is created with a `thread of execution` (line by line) and a space in memory for storage. Whatever we store in memory in this local scope (aka, the context of the function) will be available JUST inside this context. 42 | 43 | ## Function declaration 44 | 45 | ```js 46 | function sayHi() { 47 | console.log('Hi'); 48 | } 49 | 50 | sayHi(); // Hi 51 | ``` 52 | 53 | ## Function expression 54 | 55 | ```js 56 | const sayHi = function() { 57 | console.log('Hi'); 58 | } 59 | 60 | sayHi(); // Hi 61 | ``` 62 | 63 | The **main difference** between `function declaration` and `function expression` is that `WE CAN` call the `function` defined with `function declaration` before its definition. 64 | 65 | This is because JS engine moves all the `function declarations` to the top. This is called `hoisting`. 66 | 67 | You can see how `sayHi()` is hoisted. 68 | 69 | ```js 70 | sayHi(); // Hi 71 | 72 | function sayHi() { 73 | console.log('Hi'); 74 | } 75 | ``` 76 | 77 | Quick note: 78 | 1. If you use a name, like `const hi = function sayHi() {}` it is a named function expression. 79 | 2. If not, like `const hi = function() {}` it is an anonymous function expression. 80 | 81 | 82 | The less parameters a function has, the better. So we should have default values in our functions. 83 | 84 | ```js 85 | function Person(name, age) { 86 | this.name = name; 87 | this.age = age; 88 | 89 | // these are default properties hen we are creating the Person object 90 | this.friends = []; 91 | this.hobbies = []; 92 | this.isAlive = true; 93 | } 94 | 95 | const peter = new Person('Peter', 30); 96 | 97 | console.log(peter); 98 | 99 | // Person { 100 | // name: 'Peter', 101 | // age: 30, 102 | // friends: [], 103 | // hobbies: [], 104 | // isAlive: true, 105 | // __proto__: { constructor: ƒ Person() } 106 | // } 107 | ``` 108 | 109 | ## TODO: Add call, apply and bind 110 | 111 | ## Functions and default values 112 | 113 | ```js 114 | function sayHi(name = 'user') { 115 | console.log(`Hi ${name}`); 116 | } 117 | 118 | sayHi(); 119 | sayHi('Peter'); 120 | 121 | // 'Hi user' 122 | // 'Hi Peter' 123 | ``` 124 | 125 | ## Functions and arguments 126 | We can have functions with a varying number of parameters... 127 | 128 | 1. Using the rest operator (`parameter1, parameter2, ...args`) 129 | We will be looping through the args array. 130 | 131 | ```js 132 | function printNames(...args) { 133 | for (let element of args) { 134 | console.log(element) 135 | } 136 | } 137 | 138 | printNames('Peter', 'Paul', 'Lora'); 139 | // 'Peter' 140 | // 'Paul' 141 | // 'Lora' 142 | ``` 143 | 144 | 2. Accessing the `argument` object. 145 | 146 | ```js 147 | function printNames() { 148 | console.log(arguments); 149 | } 150 | 151 | printNames('Peter', 'Paul', 'Lora'); 152 | 153 | // { 154 | // '0': 'Peter', 155 | // '1': 'Paul', 156 | // '2': 'Lora', 157 | // length: 3, 158 | // callee: ƒ printNames(), 159 | // __proto__: { 160 | // // ... 161 | // } 162 | // } 163 | ``` 164 | 165 | Loop through the `arguments` keys: 166 | 167 | ```js 168 | function printNames() { 169 | for (let key in arguments) { 170 | console.log(arguments[key]) 171 | } 172 | } 173 | 174 | printNames('Peter', 'Paul', 'Lora'); 175 | // 'Peter' 176 | // 'Paul' 177 | // 'Lora' 178 | ``` 179 | 180 | Remember the difference between `parameters` and `arguments` 181 | 182 | In the example... 183 | 1. `color` is a `parameter` of the function printColor() 184 | 2. `black` is an `argument` 185 | 186 | ```js 187 | function printColor(color) { 188 | console.log(color); 189 | } 190 | 191 | printColor('black'); 192 | ``` 193 | 194 | ## HOF (Higher Order Functions) 195 | A function that takes a function as argument or returns a function and can be assigned as a value to a variable. 196 | 197 | More info: https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function 198 | 199 | ```js 200 | function performMathOperation(num1, num2, operation) { 201 | return operation(num1, num2); 202 | } 203 | 204 | function addition(num1, num2) { 205 | return num1 + num2; 206 | } 207 | 208 | function subtraction(num1, num2) { 209 | return num1 - num2; 210 | } 211 | 212 | const add = performMathOperation(1, 1, addition); 213 | console.log(add); // 2 214 | 215 | const subtract = performMathOperation(1, 1, subtraction); 216 | console.log(subtract); // 0 217 | ``` 218 | 219 | Note: In the previous example `performMathOperation()` is a HOF and `addition()` is a callback function. -------------------------------------------------------------------------------- /006_0_OOP.md: -------------------------------------------------------------------------------- 1 | # OOP 2 | 3 | > `Object-oriented programming` (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). 4 | [wikipedia](https://en.wikipedia.org/wiki/Object-oriented_programming) 5 | 6 | 7 | ## Fundamentals of OOP 8 | 9 | 1. Encapsulation: variables (properties, state) and methods that perform operations on those variables on the own object. Increases reusability. 10 | 2. Abstraction: hide some variables and methods from the outside. This will expose an easier interface and reduce the impact of change. 11 | 3. Inheritance: variables (properties) and methods can be inherited from a parent object reducing redundancy. 12 | 4. Polymorphism: TODO 13 | 14 | 15 | TODO: For more information about `OOP` link to JAVA OOP lesson 16 | 17 | Example: encapsulation and abstraction 18 | 19 | 20 | ### Inheritance 21 | 22 | Objects inheriting from another object (parent or base object, in JS Prototype). 23 | 24 | In JS we don't have classes, that's why we talk about `prototypical inheritance`. 25 | 26 | The downside of inheritance is that it is easy to end creating more levels of inheritance than what we need. **That's why we should opt for Composition over inheritance**. 27 | 28 | What is Prototypical Inheritance? 29 | When we access to a property or call a method JS engine will check first if the property or method is on the object. If not, it will look at the Prototype of that object. And so on (over the prototype chain), until it reaches the "base Object". 30 | 31 | For example, the following objects... 32 | 33 | ```js 34 | const person1 = { 35 | name: 'Peter' 36 | } 37 | 38 | const person2 = { 39 | name: 'Paul' 40 | } 41 | ``` 42 | 43 | ... have `Object`as their Prototype. 44 | 45 | ```js 46 | const person = { 47 | name: 'Peter' 48 | } 49 | 50 | Object.getPrototypeOf(person); 51 | 52 | {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} 53 | constructor: ƒ Object() 54 | assign: ƒ assign() 55 | create: ƒ create() 56 | defineProperties: ƒ defineProperties() 57 | defineProperty: ƒ defineProperty() 58 | entries: ƒ entries() 59 | // ... 60 | ``` 61 | 62 | Every object has a Prototype except the "base Object" 63 | 64 | ```js 65 | Object.getPrototypeOf(Object); 66 | ƒ () { [native code] } 67 | ``` 68 | 69 | Example of prototype chain: 70 | 71 | ```js 72 | function Person(name) { 73 | this.name = name; 74 | } 75 | 76 | const person = new Person("Peter"); 77 | ``` 78 | 79 | The object `person` inherits from `Person` which inherits from `Object`. 80 | 81 | ```js 82 | Person {name: 'Peter'} 83 | name: "Peter" 84 | [[Prototype]]: Object 85 | constructor: ƒ Person(name) 86 | [[Prototype]]: Object 87 | ``` 88 | 89 | Note: 90 | If we are using a `Constructor function` to create objects we could improve memory-storage adding the members (properties and methods) to the `object prototype`. 91 | 92 | Let's say we are creating person objects with the Person constructor function and the constructor functions has a property `this.isHuman: true` and a method `this.logHello = function() { console.log('Hello'); }` 93 | 94 | Each person object is going to have the property `isHuman` and the method `logHello()`. 95 | 96 | ```js 97 | function Person(name) { 98 | this.name = name; 99 | this.isHuman = true; 100 | this.logHello = function() { console.log(`Hello`); } 101 | } 102 | 103 | const person = new Person('Peter'); 104 | 105 | console.log(person); 106 | Person { name: 'Peter', isHuman: true, logHello: [Function] } 107 | ``` 108 | 109 | Now, imagine we are iterating through an array of objects containing `300` people, and for each one, we instantiate a `new Person` object. 110 | We will end storing 300 times the property and method. We can improve this adding them to the `object prototype`. As the result, we will store them just one time. 111 | 112 | Note: if the methods we are moving into the object prototype interact with properties on the own object, those properties have to be `public`. So we will need getters/setters. The issue with this is that we pollute the API (exposing more members, in this case the properties) and potentially causing troubles if we use setters, since the user could set new values for those properties. 113 | 114 | ```js 115 | function Person(name) { 116 | this.name = name; 117 | } 118 | 119 | Person.prototype.isHuman = true; 120 | Person.prototype.logHello = function() { 121 | console.log(`Hello`); 122 | } 123 | 124 | const person = new Person('Peter'); 125 | 126 | console.log(person); 127 | // Person { name: 'Peter' } 128 | 129 | person.logHello(); 130 | // Hello 131 | 132 | console.log(Person.prototype); 133 | // Person { isHuman: true, logHello: [Function] } 134 | ``` 135 | 136 | As a summary, we have: 137 | * Instance members (properties and methods on the instance) 138 | * Prototype members (properties and methods on the prototype) 139 | 140 | We can also `override methods` on the prototype. 141 | Note: **Avoid overriding built-in objects** like Object, String, etc. 142 | 143 | Example: 144 | 145 | ```js 146 | function Person(name) { 147 | this.name = name; 148 | } 149 | 150 | const person = new Person('Peter'); 151 | 152 | person.toString(); 153 | // [object Object] 154 | ``` 155 | 156 | And after we override the method: 157 | 158 | ```js 159 | Person.prototype.toString = function() { 160 | return `I'm a person and my name is ${this.name}`; 161 | } 162 | person.toString(); 163 | // I'm a person and my name is Peter 164 | ``` 165 | 166 | *One note:* 167 | Methods on the prototype can be called on the instance and vice-versa. 168 | 169 | --- 170 | 171 | **Prototypical inheritance** 172 | How we make an object inherit from another object? 173 | 174 | Example: `Cat` inheriting from `Animal` 175 | 176 | 1. Change the prototype of the object 177 | 2. Reset the constructor of the object 178 | 179 | In the following example we are using `Object.call()` to call the `super constructor` (parent constructor), passing `this` (which inside the Cat constructor function will point to the new instance of Cat) and the proper arguments, in our case, `name`. 180 | 181 | ```js 182 | function Animal(name) { 183 | this.name = name; 184 | } 185 | 186 | Animal.prototype.move = function() { 187 | console.log('Moving'); 188 | } 189 | 190 | function Cat(name) { 191 | Animal.call(this, name); 192 | } 193 | 194 | // We create a new object with a specific prototype 195 | Cat.prototype = Object.create(Animal.prototype); 196 | 197 | // And we reset the constructor 198 | Cat.prototype.constructor = Cat; 199 | 200 | const cat = new Cat('Bren'); 201 | 202 | cat.move(); 203 | // Moving 204 | ``` 205 | 206 | If `we don't reset the constructor` it will point to the constructor of the object we passed as the prototype. 207 | 208 | Example: 209 | 210 | ```js 211 | console.log('Constructor', Cat.prototype.constructor); // 'Constructor' ƒ Cat() 212 | 213 | Cat.prototype = Object.create(Animal.prototype); 214 | 215 | console.log('Constructor', Cat.prototype.constructor); // 'Constructor' ƒ Animal() 216 | ``` 217 | 218 | ### Polymorphism 219 | 220 | In the following example, me have 2 implementation of the same method, `makeSound()` 221 | 222 | ```js 223 | function Animal() {} 224 | 225 | Animal.prototype.move = function() { 226 | console.log('Moving'); 227 | } 228 | 229 | function Cat() {} 230 | 231 | Cat.prototype = Object.create(Animal.prototype); 232 | Cat.prototype.constructor = Cat; 233 | 234 | Cat.prototype.makeSound = function() { 235 | console.log('Meow') 236 | } 237 | 238 | function Dog() {} 239 | 240 | Dog.prototype = Object.create(Animal.prototype); 241 | Dog.prototype.constructor = Dog; 242 | 243 | Dog.prototype.makeSound = function() { 244 | console.log('Bark') 245 | } 246 | 247 | function Cat() {} 248 | 249 | 250 | const cat = new Cat() 251 | cat.makeSound(); // 'Meow' 252 | 253 | const dog = new Dog(); 254 | dog.makeSound(); // 'Bark' 255 | ``` -------------------------------------------------------------------------------- /001_1_data-types-objects.md: -------------------------------------------------------------------------------- 1 | # Objects 2 | It is a collection of `keys:values` 3 | 4 | We can see an object as the organized representation of "something". 5 | Objects in JS are dynamic... You can add and/or remove properties and methods in an existing object. 6 | 7 | For example, the following object represents a `car`: 8 | 9 | ```js 10 | const car = {}; 11 | car.brand = 'Toyota'; 12 | car.color = 'black'; 13 | 14 | console.log(car); // { brand: 'Toyota', color: 'black' } 15 | ``` 16 | 17 | All objects have a `constructor` property which references to the function used to create the object. 18 | 19 | For the following examples, the constructors will be: 20 | 1. Object() 21 | 2. Object() 22 | 3. CreateCar() 23 | 24 | Why...? 25 | Because under the hood JS uses the `Object() constructor function` when we are creating objects with the literal syntax. 26 | So, for example, when we do this: `const car = {};`, JS does: `const car = new Object();` 27 | 28 | ## Creating an object 29 | 30 | **1. Object literal syntax** 31 | 32 | ```js 33 | const car1 = { 34 | brand: 'Toyota', 35 | color: 'black', 36 | drive() { 37 | // ...do something 38 | } 39 | }; 40 | ``` 41 | 42 | *Quick note*: `drive() {}` and `drive: function() {}` are the same. 43 | 44 | **2. Factory functions** 45 | We can create multiple objects using the sample template (in this case, a function that returns an object) 46 | 47 | ```js 48 | function createCar(brand, color) { 49 | return { 50 | brand, 51 | color, 52 | drive: function() { 53 | console.log('Im driving!'); 54 | } 55 | } 56 | } 57 | 58 | 59 | const car1 = createCar('Toyota', 'black'); 60 | car1.drive(); 61 | // Im driving! 62 | 63 | const car2 = createCar('Ford', 'green'); 64 | car2.drive(); 65 | // Im driving! 66 | ``` 67 | 68 | **3. Constructor functions** 69 | 70 | ```js 71 | function CreateCar(brand, color) { 72 | // this refers to the object 73 | this.brand = brand; 74 | this.color = color; 75 | this.drive = () => { 76 | console.log('Im driving!'); 77 | }; 78 | } 79 | 80 | 81 | const car1 = new CreateCar('Toyota', 'black'); 82 | car1.drive(); 83 | // Im driving! 84 | 85 | const car2 = new CreateCar('Ford', 'green'); 86 | car2.drive(); 87 | // Im driving! 88 | ``` 89 | 90 | The `new` keyword... 91 | 1. Creates an `empty object` 92 | 2. It will set the newly created object as the value of `this` 93 | 3. It `returns` the object 94 | 95 | ## Manipulating an object 96 | 97 | If we want to remove a property or method from the object we can use the `delete` keyword. 98 | 99 | ```js 100 | const car = { 101 | brand: 'Toyota', 102 | color: 'black' 103 | }; 104 | 105 | console.log(car); 106 | // { brand: 'Toyota', color: 'black' } 107 | 108 | delete car.color; 109 | console.log(car); 110 | // { brand: 'Toyota' } 111 | ``` 112 | 113 | And, you can use the `dot notation` to add properties or methods. 114 | 115 | ```js 116 | const car = { 117 | brand: 'Toyota', 118 | color: 'black' 119 | }; 120 | 121 | console.log(car); 122 | // { brand: 'Toyota', color: 'black' } 123 | 124 | car.drive = () => { 125 | console.log('Im driving!'); 126 | }; 127 | 128 | car.drive(); 129 | // // Im driving! 130 | ``` 131 | 132 | ## Checking for a property in an object 133 | 134 | ```js 135 | const obj = { 136 | name: 'Peter', 137 | age: 33 138 | } 139 | 140 | console.log('age' in obj); // true 141 | ``` 142 | 143 | ## Getting object keys and values 144 | 145 | ```js 146 | const obj = { 147 | name: 'Peter', 148 | age: 33 149 | } 150 | 151 | // Getting keys 152 | for (let key in obj) { 153 | console.log(key); 154 | } 155 | // name 156 | // age 157 | 158 | // Getting values 159 | 160 | for (let key in obj) { 161 | console.log(obj[key]); 162 | } 163 | // 'Peter' 164 | // 33 165 | ``` 166 | 167 | Alternatively, we can... 168 | 169 | 1. Get ALL the keys of an object with the method `Object.keys(obj)` 170 | 171 | ```js 172 | const keys = Object.keys(obj); 173 | for (key of keys) { 174 | console.log(key); 175 | } 176 | // name 177 | // age 178 | ``` 179 | 180 | 2. Get ALL the values of an object with the method `Object.values(obj)` 181 | 182 | ```js 183 | const values = Object.values(obj); 184 | for (let value of values) { 185 | console.log(value); 186 | } 187 | // 'Peter' 188 | // 33 189 | ``` 190 | 191 | 3. Get ALL key:value pairs in an object 192 | 193 | ```js 194 | const kvArr = Object.entries(obj); 195 | console.log(kvArr); 196 | 197 | // [ [ 'name', 'Peter' ], [ 'age', 33 ] ] 198 | ``` 199 | 200 | ## Cloning objects 201 | 202 | 1. **Spread operator** 203 | 204 | ```js 205 | const obj = { 206 | name: 'Peter', 207 | age: 33 208 | } 209 | 210 | const obj1 = { ...obj }; 211 | console.log(obj1); 212 | // { name: 'Peter', age: 33 } 213 | ``` 214 | 215 | 2. **Object.assign(object, baseObject)** 216 | 217 | ```js 218 | const obj = { 219 | name: 'Peter', 220 | age: 33 221 | } 222 | 223 | const obj1 = Object.assign({}, obj); 224 | 225 | console.log(obj1); 226 | // { name: 'Peter', age: 33 } 227 | ``` 228 | 229 | *Note*: We can pass an existing object or add properties to the object we are passing to Object.assign() 230 | 231 | ```js 232 | Object.assign({ 233 | hobbies: [] 234 | }, obj); 235 | 236 | 237 | object.assign(previouslyCreatedObject, obj); 238 | ``` 239 | 240 | ## Getters and Setters 241 | * Getters let us access properties of our objects 242 | * Setters let us modify properties 243 | 244 | Both create read-only properties (*) 245 | 246 | ```js 247 | const person = { 248 | name: 'Peter', 249 | age: 30, 250 | get personData() { 251 | return `${this.name} is ${this.age} old` 252 | }, 253 | set newName(name) { 254 | this.name = name; 255 | } 256 | } 257 | 258 | console.log(person.personData); 259 | // 'Peter is 30 old' 260 | 261 | person.newName = 'Wendy'; 262 | 263 | console.log(person.personData); 264 | // 'Wendy is 30 old' 265 | ``` 266 | 267 | (*) As you can see, personData() and newName() are `read-only` 268 | 269 | ```js 270 | const person = { 271 | name: 'Peter', 272 | age: 30, 273 | get personData() { 274 | return `${this.name} is ${this.age} old` 275 | }, 276 | set newName(name) { 277 | this.name = name; 278 | } 279 | } 280 | 281 | person.name = 'Paul'; 282 | person.personData = 1; 283 | person.newName = 2; 284 | 285 | console.log(person); 286 | 287 | // { 288 | // name: 2, 289 | // age: 30, 290 | // personData: [Getter], 291 | // newName: [Setter] 292 | // } 293 | ``` 294 | 295 | A similar example using a `Constructor function` and `Object.defineProperty()` (getters and setters) 296 | 297 | ```js 298 | function Person(name) { 299 | this.name = name; 300 | 301 | Object.defineProperty(this, 'name', { 302 | get() { 303 | return name; 304 | }, 305 | set(value) { 306 | name = value; 307 | } 308 | }); 309 | } 310 | 311 | const peter = new Person('Peter'); 312 | 313 | console.log(peter); 314 | // Person { name: [Getter/Setter] } 315 | 316 | console.log(peter.name); 317 | // Peter 318 | 319 | peter.name = 'Paul'; 320 | 321 | console.log(peter.name); 322 | // Paul 323 | ``` 324 | 325 | ## Property descriptors 326 | 327 | We can use `Object.getOwnPropertyDescriptors(obj)` to get the `property descriptors` of a given object. 328 | 329 | ```js 330 | function Person(name) { 331 | this.name = name; 332 | } 333 | 334 | const peter = new Person("Peter"); 335 | 336 | Object.getOwnPropertyDescriptors(peter); 337 | 338 | // { 339 | // name: { 340 | // value: 'Peter', 341 | // writable: true, 342 | // enumerable: true, 343 | // configurable: true 344 | // } 345 | // } 346 | ``` 347 | 348 | Notes: 349 | 1. `value` is the value associated with the property 350 | 2. `writable` if the value can be changed 351 | 3. `enumerable` if its showed during enumerations (like looping) 352 | 4. `configurable` if we can delete the member or not 353 | 354 | We can use `Object.defineProperty()` to set the `property descriptors attributes`: 355 | * value 356 | * writable 357 | * get 358 | * set 359 | * configurable 360 | * enumerable 361 | 362 | ```js 363 | function Person(name) { 364 | this.name = name; 365 | 366 | Object.defineProperty(this, 'name', { 367 | writable: false, 368 | enumerable: false, 369 | value: 'Mike', 370 | configurable: false 371 | }); 372 | } 373 | 374 | let person = new Person('Peter'); 375 | person.name = 'Paul'; 376 | console.log(person.name); 377 | // Mike 378 | 379 | Object.keys(person); 380 | // [] 381 | 382 | delete person.name; 383 | // false 384 | ``` 385 | 386 | In the previous example... 387 | 1. We will not be able to change the value 388 | 2. We will not be able to list the property name 389 | 3. We will set Mike as the value 390 | 4. We will not be able to delete the property -------------------------------------------------------------------------------- /001_2_data-types-arrays.md: -------------------------------------------------------------------------------- 1 | # Arrays 2 | 3 | ```js 4 | const letters = ['a', 'b', 'c']; 5 | ``` 6 | 7 | ## Truncate array 8 | 9 | ```js 10 | let letters = ['a', 'b', 'c']; 11 | let myLetters = letters; 12 | 13 | letters.length = 0; 14 | console.log(letters); // [] 15 | console.log(myLetters); // [] 16 | ``` 17 | 18 | **Why don't we assign an empty array as value?** 19 | Remember that objects are stored by reference. 20 | So in the following example this is what's going to happen: 21 | 22 | 1. We create a new variable `letters` that will hold a reference to the memory address where we are storing the array `['a', 'b', 'c']` 23 | 2. We create a new variable `myLetters` and assign as its value the reference to the memory address where we are storing the array `['a', 'b', 'c']`. 24 | 3. We change the reference of `letters` since now it will point to a different location in memory where the empty array will be stored `[]` 25 | 4. The `myLetters` reference that points to the memory location where we stored `['a', 'b', 'c']` remains intact. 26 | 27 | ```js 28 | let letters = ['a', 'b', 'c']; 29 | let myLetters = letters; 30 | 31 | letters = []; 32 | 33 | console.log(letters); // [] 34 | console.log(myLetters); // [ 'a', 'b', 'c' ] 35 | 36 | ``` 37 | 38 | ## Adding elements to an array 39 | 40 | ### At the beginning: [O, x, x] 41 | 42 | ```js 43 | const letters = ['a', 'b', 'c']; 44 | 45 | letters.unshift('z'); 46 | console.log(letters); // [ 'z', 'a', 'b', 'c' ] 47 | ``` 48 | 49 | ### At a particular position: [x, x, O, x] 50 | 51 | `Array.splice(start, deleteCount, item1, item2, itemN)` 52 | 53 | ```js 54 | const letters = ['a', 'b', 'c']; 55 | 56 | letters.splice(1, 0, 'before b'); 57 | console.log(letters); // [ 'a', 'before b', 'b', 'c' ] 58 | ``` 59 | 60 | ### At the end: [x, x, O] 61 | 62 | ```js 63 | const letters = ['a', 'b', 'c']; 64 | 65 | letters.push('d'); 66 | console.log(letters); // [ 'a', 'b', 'c', 'd' ] 67 | ``` 68 | 69 | ## Removing elements from an array 70 | 71 | ### At the beginning: [O, x, x] 72 | 73 | ```js 74 | const letters = ['a', 'b', 'c']; 75 | 76 | letters.shift(); 77 | console.log(letters); // [ 'b', 'c' ] 78 | ``` 79 | 80 | ### At a particular position: [x, x, O, x] 81 | 82 | `Array.splice(start, deleteCount)` 83 | 84 | ```js 85 | const letters = ['a', 'b', 'c']; 86 | 87 | letters.splice(1, 2); 88 | console.log(letters); // [ 'a' ] 89 | ``` 90 | 91 | ### At the end: [x, x, O] 92 | 93 | ```js 94 | const letters = ['a', 'b', 'c']; 95 | 96 | letters.pop(); 97 | console.log(letters); // [ 'a', 'b' ] 98 | ``` 99 | 100 | ## Looking for an element 101 | 102 | * If you are dealing with a `value type` use `Array.includes()` 103 | * If you are dealing with a `reference type` use `Array.find()` 104 | 105 | **Array.includes(element, startingIndex)** 106 | It returns true or false. 107 | 108 | ```js 109 | const letters = ['a', 'b', 'c']; 110 | 111 | letters.includes('b'); // true 112 | ``` 113 | 114 | **Array.indexOf(element, startingIndex)** 115 | It returns the index of the element or -1 if the element doesn't exists in the array. 116 | 117 | ```js 118 | const letters = ['a', 'b', 'c']; 119 | 120 | letters.indexOf('b'); // 1 121 | ``` 122 | 123 | *Note:* If you want to find the last index of an element use `Array.lastIndexOf(element, startingIndex)`. Like in the case of: 124 | 125 | ```js 126 | const letters = ['a', 'b', 'a']; 127 | 128 | letters.lastIndexOf('a'); // 2 129 | ``` 130 | 131 | **Array.find(cb)** 132 | It takes a callback and it returns the first match or undefined 133 | 134 | ```js 135 | const people = [ 136 | { name: 'Peter', age: 30 }, 137 | { name: 'Paul', age: 20 }, 138 | { name: 'Paul', age: 10 } 139 | ]; 140 | 141 | const paul1 = people.find(person => person.name === 'Paul'); 142 | 143 | console.log(paul1); 144 | // { name: 'Paul', age: 20 } 145 | ``` 146 | 147 | ## Sorting array 148 | 149 | ```js 150 | const letters = ['c', 'a', 'b']; 151 | letters.sort(); 152 | 153 | console.log(letters); // [ 'a', 'b', 'c' ] 154 | ``` 155 | 156 | ### Sorting an array of number ASC 157 | 158 | ```js 159 | const letters = [1,5,2]; 160 | letters.sort((a, b) => a - b); 161 | 162 | console.log(letters); // [ 1, 2, 5 ] 163 | ``` 164 | 165 | ### Sorting an array of number DESC 166 | 167 | ```js 168 | const letters = [1,5,2]; 169 | letters.sort((a, b) => b - a); 170 | 171 | console.log(letters); // [ 5, 2, 1 ] 172 | ``` 173 | 174 | ### Sorting objects in array: Array.sort(cb) 175 | 176 | ```js 177 | const people = [ 178 | { name: 'Peter', age: 30 }, 179 | { name: 'Jen', age: 20 }, 180 | { name: 'Alice', age: 20 } 181 | ]; 182 | 183 | const sortNamesASC = (a, b) => { 184 | const nameA = a.name.toUpperCase(); 185 | const nameB = b.name.toUpperCase(); 186 | 187 | if (nameA < nameB) return -1; 188 | if (nameA > nameB) return 1; 189 | 190 | // for the same name 191 | return 0; 192 | } 193 | 194 | people.sort(sortNamesASC); 195 | 196 | // [ 197 | // { name: 'Alice', age: 20 }, 198 | // { name: 'Jen', age: 20 }, 199 | // { name: 'Peter', age: 30 } 200 | // ] 201 | ``` 202 | 203 | If you want to do it in `DESC` order switch... 204 | 205 | ```js 206 | if (nameA < nameB) return 1; 207 | if (nameA > nameB) return -1; 208 | ``` 209 | 210 | This will result in: 211 | 212 | ``` 213 | [ 214 | { name: 'Peter', age: 30 }, 215 | { name: 'Jen', age: 20 }, 216 | { name: 'Alice', age: 20 } 217 | ] 218 | ``` 219 | 220 | More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort 221 | 222 | ## Reversing an array 223 | 224 | ```js 225 | const letters = ['c', 'a', 'b']; 226 | letters.reverse(); 227 | 228 | console.log(letters); // [ 'b', 'a', 'c' ] 229 | ``` 230 | 231 | ## Concatenate arrays 232 | 233 | **Spread operator** 234 | 235 | ```js 236 | const letters1 = ['a', 'b', 'c']; 237 | const letters2 = ['d', 'e', 'f']; 238 | 239 | const letters = [...letters1, ...letters2] 240 | console.log(letters); // [ 'a', 'b', 'c', 'd', 'e', 'f' ] 241 | ``` 242 | 243 | **Array.concat(arr)** 244 | 245 | ```js 246 | const letters1 = ['a', 'b', 'c']; 247 | const letters2 = ['d', 'e', 'f']; 248 | 249 | const letters = letters1.concat(letters2); 250 | console.log(letters); // [ 'a', 'b', 'c', 'd', 'e', 'f' ] 251 | ``` 252 | 253 | ## Slice an array 254 | If we have an array of objects the reference will be copied but not the object per se. 255 | 256 | **Array.slice()** will copy the array. 257 | **Array.slice(startIndex)** will create a new array from starting index until the last element. 258 | **Array.slice(startIndex, endIndex)** will create a new array from starting index until the ending index. 259 | 260 | ```js 261 | const letters = ['a', 'b', 'c', 'd', 'e']; 262 | 263 | const letters1 = letters.slice(1,3); 264 | console.log(letters1); // [ 'b', 'c' ] 265 | ``` 266 | 267 | For copying an array opt for the `spread operator`: 268 | 269 | ```js 270 | const letters = ['a', 'b', 'c']; 271 | const newLetters = [...letters]; 272 | 273 | console.log(newLetters); // [ 'a', 'b', 'c' ] 274 | ``` 275 | 276 | # Joining an array 277 | 278 | ```js 279 | const letters = ['a', 'b', 'c']; 280 | 281 | console.log(letters.join('')); // abc 282 | ``` 283 | 284 | ## Find, forEach, every, some, map, reduce and filter 285 | 286 | ### Array.find(cb) 287 | It takes a callback and it returns the first match or undefined 288 | 289 | ```js 290 | const people = [ 291 | { name: 'Peter', age: 30 }, 292 | { name: 'Paul', age: 20 }, 293 | { name: 'Paul', age: 10 } 294 | ]; 295 | 296 | const paul1 = people.find(person => person.name === 'Paul'); 297 | 298 | console.log(paul1); 299 | // { name: 'Paul', age: 20 } 300 | ``` 301 | 302 | ### Array.forEach(cb) 303 | It takes a callback and executes it once per element. 304 | 305 | ```js 306 | const people = [ 307 | { name: 'Peter', age: 30 }, 308 | { name: 'Paul', age: 20 }, 309 | { name: 'Paul', age: 10 } 310 | ]; 311 | 312 | const pauls = people.forEach((person, index) => console.log(`${index} - ${person.name}`)); 313 | // '0 - Peter' 314 | // '1 - Paul' 315 | // '2 - Paul' 316 | ``` 317 | 318 | ### Array.every(cb) 319 | It returns true or false depending on what the expression evaluates to. 320 | 321 | The following example returns `false` since we have `1` which is of type `number`. 322 | 323 | ```js 324 | const mixedArray = ['a', 'b', 'c', 1]; 325 | 326 | mixedArray.every((element) => typeof element === 'string'); // false 327 | ``` 328 | 329 | ### Array.some(cb) 330 | It returns true or false depending on if at least one element matches the expression evaluation result. 331 | 332 | ```js 333 | const mixedArray = ['a', 'b', 'c', 1]; 334 | 335 | mixedArray.some((element) => typeof element === 'number'); // true 336 | ``` 337 | 338 | ### Array.map(cb) 339 | It returns a new array 340 | 341 | ```js 342 | const people = [ 343 | { name: 'Peter', age: 30 }, 344 | { name: 'Paul', age: 20 }, 345 | { name: 'Paul', age: 10 } 346 | ]; 347 | 348 | const names = people.map((person) => person.name); 349 | console.log(names); 350 | 351 | // [ 'Peter', 'Paul', 'Paul' ] 352 | ``` 353 | 354 | ### Array.reduce(cb, initialValue) 355 | It returns a new array 356 | 357 | ```js 358 | const people = [ 359 | { name: 'Peter', age: 30 }, 360 | { name: 'Paul', age: 20 }, 361 | { name: 'Paul', age: 10 } 362 | ]; 363 | 364 | const agesSum = people.reduce((accumulator, person) => { 365 | return accumulator + person.age; 366 | }, 0); 367 | 368 | console.log(agesSum); // 60 369 | ``` 370 | 371 | ### Array.filter(cb) 372 | It returns a new array with the element/s that satisfy the expression evaluation. 373 | 374 | ```js 375 | const people = [ 376 | { name: 'Peter', age: 30 }, 377 | { name: 'Paul', age: 20 }, 378 | { name: 'Paul', age: 10 } 379 | ]; 380 | 381 | const pauls = people.filter((person) => person.name === 'Paul'); 382 | 383 | console.log(pauls); 384 | 385 | // [ 386 | // { name: 'Paul', age: 20 }, 387 | // { name: 'Paul', age: 10 } 388 | // ] 389 | ``` -------------------------------------------------------------------------------- /async.md: -------------------------------------------------------------------------------- 1 | # Async JS 2 | 3 | Previously we said that JS is: 4 | 1. Single threaded (everything happens in one thread) 5 | 2. Synchronous (the code is executed line by line as it was written) 6 | 7 | So... What happens if we need to perform a heavy operation (which takes a long time) before we continue executing the code in the thread? 8 | 9 | Either we are running code in the Browser or Node, both runtimes has more than the JS Engine, they include (among others) extra functionality/features like APIs to execute code asynchronously. 10 | 11 | Web APIs: https://developer.mozilla.org/en-US/docs/Web/API 12 | For example, `setTimeout()` 13 | 14 | ```js 15 | function firstOperation() { 16 | console.log(`Operation 1`); 17 | } 18 | 19 | setTimeout(firstOperation, 5000); 20 | 21 | console.log(`Operation 2`); 22 | ``` 23 | 24 | Result: 25 | ``` 26 | Operation 2 27 | 28 | Operation 1 29 | ``` 30 | 31 | What's going on? 32 | When we execute `setTimeout(firstOperation, 5000);` we pass a reference to the function that we want to execute when our timer (in the browser) reaches 5000ms, so, in between, JS code can keep executing. 33 | Once the Browser/Node timer finishes, the reference to `firstOperation` is going to end in the `callback queue`, then the `Event Loop` is going to move it to the `call stack` and finally it will be executed. 34 | 35 | **Important: ** 36 | 37 | 1. The reference is going to be de-queued and moved to the `call stack` **ONLY** when all synchronous code finished executing. 38 | 39 | 2. The Event Loop checks constantly if the `call stack` is empty and if there's `no more sync code to run`. If that's the case, it will de-queue from the `callback queue` and move to the `call stack`. If not, it will wait before de-queueing. 40 | 41 | ## Callbacks 42 | 43 | A callback is a function that will be called with the result when the operation is done. 44 | 45 | Cross pattern example: 46 | ```js 47 | function getUser(id, callback) { 48 | setTimeout(() => { 49 | callback({ id, username: 'Peter' }); 50 | }, 3000); 51 | } 52 | 53 | getUser(10, (result) => console.log(result)); 54 | ``` 55 | 56 | Output: 57 | ``` 58 | { id: 10, username: 'Peter' } 59 | ``` 60 | 61 | ### Callback hell 62 | 63 | ```js 64 | function getUser(id, callback) { 65 | setTimeout(() => { 66 | callback({ id, username: 'Peter' }); 67 | }, 3000); 68 | } 69 | 70 | function getHobbiesForUser(userId, callback) { 71 | setTimeout(() => { 72 | callback(['reading', 'writing']); 73 | }, 1000); 74 | } 75 | 76 | function getExpensesForHobby(hobby, callback) { 77 | setTimeout(() => { 78 | callback(10); 79 | }, 1000); 80 | } 81 | 82 | 83 | // This is the callback hell 84 | getUser(10, (user) => { 85 | console.log(user); 86 | getHobbiesForUser(user.id, (hobbies) => { 87 | console.log(hobbies); 88 | for (let hobby of hobbies) { 89 | getExpensesForHobby(hobby, (expenseForHobby) => { 90 | console.log(expenseForHobby); 91 | }) 92 | } 93 | }) 94 | }); 95 | ``` 96 | 97 | Output: 98 | ``` 99 | { id: 10, username: 'Peter' } 100 | [ 'reading', 'writing' ] 101 | 10 102 | 10 103 | ``` 104 | 105 | **Fixing Callback Hell with Named Functions** 106 | 107 | ```js 108 | function getUser(id, callback) { 109 | setTimeout(() => { 110 | callback({ id, username: 'Peter' }); 111 | }, 3000); 112 | } 113 | 114 | function getHobbiesForUser(userId, callback) { 115 | setTimeout(() => { 116 | callback(['reading', 'writing']); 117 | }, 1000); 118 | } 119 | 120 | function getExpensesForHobby(hobby, callback) { 121 | setTimeout(() => { 122 | callback(10); 123 | }, 1000); 124 | } 125 | 126 | // 127 | 128 | getUser(10, getHobbiesForUseSync); 129 | 130 | function getHobbiesForUseSync(user) { 131 | console.log(user); 132 | getHobbiesForUser(user.id, getHobbiesAndExpensesSync); 133 | } 134 | 135 | function getHobbiesAndExpensesSync(hobbies) { 136 | console.log(hobbies); 137 | for (let hobby of hobbies) { 138 | getExpensesForHobby(hobby, logExpenseForHobby); 139 | } 140 | } 141 | 142 | function logExpenseForHobby(hobby) { 143 | console.log(hobby); 144 | } 145 | ``` 146 | 147 | Output: 148 | ``` 149 | { id: 10, username: 'Peter' } 150 | [ 'reading', 'writing' ] 151 | 10 152 | 10 153 | ``` 154 | 155 | ## Promises 156 | 157 | 160 | 161 | A Promise is an object representing the eventual completion or failure of an asynchronous operation. 162 | More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises 163 | 164 | **Resolving example:** 165 | 166 | ```js 167 | const promise = new Promise(function(resolve, reject) { 168 | // async operations 169 | setTimeout(() => { 170 | resolve({ data: 'This is the data' }); 171 | //reject(new Error('Something went wrong!')); 172 | }, 1000); 173 | }) 174 | 175 | promise 176 | .then(result => console.log(result)) 177 | .catch(err => console.log(err)); 178 | ``` 179 | 180 | Output: 181 | ``` 182 | { data: 'This is the data' } 183 | ``` 184 | 185 | **Rejecting example:** 186 | 187 | ```js 188 | const promise = new Promise(function(resolve, reject) { 189 | // async operations 190 | setTimeout(() => { 191 | //resolve({ data: 'This is the data' }); 192 | reject(new Error('Something went wrong!')); 193 | }, 1000); 194 | }) 195 | 196 | promise 197 | .then(result => console.log(result)) 198 | .catch(err => console.log(err)); 199 | ``` 200 | 201 | Output: 202 | ``` 203 | Error: 'Something went wrong!' 204 | ``` 205 | 206 | **Promises states** 207 | * Pending 208 | * Fulfilled 209 | * Rejected 210 | * Settled 211 | 212 | 213 | Cross pattern example: 214 | ```js 215 | function getUser(id) { 216 | return new Promise((resolve, reject) => { 217 | setTimeout(() => { 218 | resolve({ id, username: 'Peter' }); 219 | }, 3000); 220 | }); 221 | } 222 | 223 | function getHobbiesForUser(userId) { 224 | return new Promise((resolve, reject) => { 225 | setTimeout(() => { 226 | resolve(['reading', 'writing']); 227 | }, 3000); 228 | }); 229 | } 230 | 231 | function getExpensesForHobby(hobby) { 232 | return new Promise((resolve, reject) => { 233 | setTimeout(() => { 234 | resolve(10); 235 | }, 3000); 236 | }); 237 | } 238 | 239 | getUser(10) 240 | .then(user => { 241 | console.log(user); 242 | // here we return a new promise 243 | return getHobbiesForUser(user.id); 244 | }) 245 | .then(hobbies => { 246 | console.log(hobbies); 247 | }) 248 | .catch(err => console.log(err)); 249 | ``` 250 | 251 | Output: 252 | ``` 253 | { id: 10, username: 'Peter' } 254 | [ 'reading', 'writing' ] 255 | ``` 256 | 257 | 258 | Fetch example: 259 | ```js 260 | function parseAndLog(data) { 261 | console.log(data); 262 | } 263 | 264 | const result = fetch('https://jsonplaceholder.typicode.com/todos/1') 265 | .then(data => data.json()) 266 | .then(parseAndLog) 267 | .catch(err => console.log(err)); 268 | ``` 269 | 270 | Output: 271 | 272 | ``` 273 | { 274 | userId: 1, 275 | id: 1, 276 | title: 'delectus aut autem', 277 | completed: false 278 | } 279 | ``` 280 | 281 | ### Promise.resolve() and Promise.reject() 282 | 283 | **Promise.resolve(value)** 284 | ```js 285 | // This returns a resolved Promise 286 | const user = Promise.resolve({ id: 10, username: 'Peter' }); 287 | 288 | user.then(u => console.log(u)); 289 | // { id: 10, username: 'Peter' } 290 | ``` 291 | 292 | **Promise.reject(errorObject)** 293 | We use an Error object to include the callstack. 294 | 295 | ```js 296 | const user = Promise.reject(new Error('Something went wrong!')); 297 | 298 | user.catch(error => console.log(error)); 299 | ``` 300 | 301 | ### Promise.all(arrayOfPromises) 302 | 303 | The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. 304 | This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error. 305 | More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all 306 | 307 | ```js 308 | const promise1 = new Promise((resolve, reject) => { 309 | setTimeout(() => { 310 | resolve(1); 311 | }, 1000); 312 | }); 313 | 314 | const promise2 = new Promise((resolve, reject) => { 315 | setTimeout(() => { 316 | resolve(2); 317 | }, 1000); 318 | }); 319 | 320 | Promise.all([promise1, promise2]) 321 | .then(values => { 322 | console.log(values); 323 | }) 324 | .catch(err => console.log(err)); 325 | ``` 326 | 327 | Output: 328 | ``` 329 | [ 1, 2 ] 330 | ``` 331 | 332 | ### Promise.race() 333 | The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. 334 | More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race 335 | 336 | ```js 337 | const promise1 = new Promise((resolve, reject) => { 338 | setTimeout(() => { 339 | resolve(1); 340 | }, 1000); 341 | }); 342 | 343 | const promise2 = new Promise((resolve, reject) => { 344 | setTimeout(() => { 345 | resolve(2); 346 | }, 1000); 347 | }); 348 | 349 | Promise.race([promise1, promise2]) 350 | .then(values => { 351 | console.log(values); 352 | }) 353 | .catch(err => console.log(err)); 354 | ``` 355 | 356 | Output: 357 | ``` 358 | 1 359 | ``` 360 | 361 | ## Async/Await 362 | We write async code that looks like sync code 363 | 364 | ```js 365 | function getUser(id) { 366 | return new Promise((resolve, reject) => { 367 | setTimeout(() => { 368 | resolve({ id, username: 'Peter' }); 369 | }, 3000); 370 | }); 371 | } 372 | 373 | function getHobbiesForUser(userId) { 374 | return new Promise((resolve, reject) => { 375 | setTimeout(() => { 376 | resolve(['reading', 'writing']); 377 | }, 3000); 378 | }); 379 | } 380 | 381 | function getExpensesForHobby(hobby) { 382 | return new Promise((resolve, reject) => { 383 | setTimeout(() => { 384 | resolve(10); 385 | }, 3000); 386 | }); 387 | } 388 | 389 | async function logUserHobbies() { 390 | const user = await getUser(10); 391 | const userHobbies = await getHobbiesForUser(user.id); 392 | console.log(userHobbies); 393 | } 394 | 395 | logUserHobbies(); 396 | ``` 397 | 398 | Output: 399 | ``` 400 | [ 'reading', 'writing' ] 401 | ``` -------------------------------------------------------------------------------- /Z_00_0_basic-es2015.md: -------------------------------------------------------------------------------- 1 | # ES6 or ES2015 2 | 3 | 6 | 7 | 8 | 73 | 74 | 75 | 76 | 108 | 109 | ## let and const 110 | 111 | 112 | ## Template literals 113 | (Also called in early stages "template strings") 114 | 115 | Template literals are string literals allowing embedded expressions. 116 | You can use multi-line strings and string interpolation features with them. 117 | [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) 118 | 119 | Before, `template literals` we concatenate strings using the `+` operator or the `concat()` string method. 120 | 121 | ```js 122 | const str1 = 'Hello'; 123 | const str2 = 'Peter'; 124 | 125 | const result1 = str1 + ' ' + str2; 126 | 127 | const result2 = str1.concat(' ', str2); 128 | 129 | 130 | console.log(result1); 131 | console.log(result2); 132 | 133 | // Hello Peter 134 | // Hello Peter 135 | ``` 136 | 137 | Things were even more verbose when we wanted to have multi-lines strings. 138 | 139 | ```js 140 | const str1 = 'Hello'; 141 | const str2 = 'Peter'; 142 | 143 | const result1 = str1 + '\n' + str2; 144 | 145 | console.log(result1) 146 | // Hello 147 | // Peter 148 | ``` 149 | 150 | Now, with `template literals` we can easily do string interpolations using the backticks ``. 151 | 152 | ```js 153 | const str1 = 'Hello'; 154 | const str2 = 'Peter'; 155 | 156 | const result = `${str1}, 157 | my name is 158 | ${str2}`; 159 | 160 | console.log(result); 161 | 162 | // Hello, 163 | // my name is 164 | // Peter 165 | ``` 166 | 167 | Technically, you can do more than referencing a variable inside these `embedded expressions`, however, it is a good advise to abstract the logic to a function and invoke it. 168 | 169 | ```js 170 | const str1 = 'Hello'; 171 | const str2 = 'Peter'; 172 | 173 | const isPeter = (name) => { 174 | if (name.toLowerCase() === 'peter') { 175 | return `If you want more information about ${name}...` 176 | } 177 | return `Keep searching!` 178 | } 179 | 180 | 181 | const result = `${str1}. 182 | Are you lokking for ${str2}? 183 | ${isPeter(str2)}`; 184 | 185 | console.log(result); 186 | 187 | // Hello. 188 | // Are you lokking for Peter? 189 | // If you want more information about Peter... 190 | ``` 191 | 192 | ## Destructuring assignment 193 | 194 | The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. 195 | [Destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 196 | 197 | ```js 198 | const arr = [0, 1, 2, 3, 4, 5, 6]; 199 | 200 | const [a, b, c, ...rest] = arr; 201 | 202 | console.log(`${a} 203 | ${b} 204 | ${c} 205 | ${rest}`) 206 | 207 | // 0 208 | // 1 209 | // 2 210 | // 3,4,5,6 211 | ``` 212 | 213 | ## Arrow function 214 | 215 | *MDN Web Developer* defines it as "a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors". 216 | [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 217 | 218 | 219 | *Before es2015:* function example 220 | 221 | ```javascript 222 | function add(num1, num2) { 223 | return 'The results of this operation is ' + (num1 + num2); 224 | } 225 | 226 | add(2,3); 227 | ``` 228 | 229 | *With es2015:* function example 230 | 231 | ```javascript 232 | const add = (num1, num2) => { 233 | return `The results of this operation is ${num1 + num2}` 234 | } 235 | 236 | add(2,3); 237 | ``` 238 | In both cases the output will be `The results of this operation is 5` 239 | 240 | This is a "quick and dirty example". In a real world application (functional and pure programming), you -probably- will have... 241 | 1. `add(number, number, nameSpace = 'string')` receiving an extra argument for the message. In this case, you could also set a parameter with a default value, so, if you don´t explicitly pass it, it will use the pre-set one. 242 | 243 | ```javascript 244 | const add = (num1, num2, message = 'The results of this operation is') => `${message} ${num1 + num2}`; 245 | console.log(`${add(2,3, 'The sum is:')}`) 246 | ``` 247 | 248 | 2. **Preferred approach:** `add()` will just do what´s supposed to do... ADD! This pattern is "one function per need". `add()` will sum and return while other variable will hold the message. 249 | 250 | ```javascript 251 | const add = (num1, num2) => num1 + num2; 252 | const message = 'The results of this operation is'; 253 | 254 | console.log(`${message} ${add(2,3)}`) 255 | ``` 256 | 257 | *Note:* We could also create a new function, `showMessage('string', fn)`, pass the function `add()` as argument and log to the console the message (including add() return) from that function. However, this is extremely granular unless there´s a real possibility of reusability. 258 | 259 | **Remember:** in arrow functions there´s no `this keyword`. 260 | 261 | Example: `this` inside arrow function and regular function methods. 262 | ```javascript 263 | const someObj = { 264 | someProperty: 1, 265 | someMethodArrowFn: () => { 266 | console.log(this.someProperty, this) 267 | }, 268 | someMethodwFn: function() { 269 | console.log(this.someProperty, this) 270 | } 271 | } 272 | 273 | someObj.someMethodArrowFn(); 274 | someObj.someMethodwFn(); 275 | ``` 276 | 277 | Result: 278 | 279 | ``` 280 | undefined - global or window 281 | 282 | 1 { someProperty: 1, 283 | someMethodArrowFn: [Function: someMethodArrowFn], 284 | someMethodwFn: [Function: someMethodwFn] } 285 | ``` 286 | 287 | **Remember:** arrow functions cannot be used as constructor functions 288 | 289 | Example: invalid constructor 290 | ```javascript 291 | const Person = name => { 292 | this.name = name; 293 | } 294 | 295 | const person1 = new Person('Peter'); 296 | 297 | console.log(person1); 298 | ``` 299 | 300 | Result: 301 | ``` 302 | TypeError: Person is not a constructor 303 | ``` 304 | 305 | **Remember:** arrow functions don´t have prototype. 306 | 307 | ```javascript 308 | const Person = () => { 309 | } 310 | 311 | const Human = function() { 312 | } 313 | 314 | console.log(Person.prototype); 315 | console.log(Human.prototype); 316 | ``` 317 | Output: 318 | ``` 319 | undefined // using arrow function 320 | Human {} // using regular function expression 321 | ``` 322 | 323 | --- 324 | 325 | ## Classes 326 | We can see Classes as the blueprints of the objects created through these "special functions". 327 | In our first example, `Friend` (type: function) is the blueprint of `friend1` (type: object). 328 | 329 | Classes can be defined in 2 ways as we normally do it with our "regular functions" (remember they are *just* "special functions"): 330 | 331 | * Class expression 332 | ``` 333 | class Friend { 334 | ... 335 | } 336 | ``` 337 | 338 | * Class declaration 339 | ``` 340 | const Friend = class { 341 | ... 342 | } 343 | ``` 344 | 345 | Example: class expression 346 | ```javascript 347 | class Friend { 348 | constructor(name) { 349 | this.name = name; 350 | } 351 | sayHi = () => { 352 | return `Hi ${this.name}`; 353 | } 354 | } 355 | 356 | 357 | const friend1 = new Friend('Peter'); 358 | console.log(friend1.sayHi()); 359 | ``` 360 | 361 | Result: 362 | ``` 363 | 'Hi Peter' 364 | ``` 365 | 366 | 367 | 368 | 369 | 370 | JavaScript’s object system is based on prototypes, not classes. 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | Notes: 379 | The `constructor() method` will be executed when we instantiate the class with the `new keyword`. 380 | 381 | 382 | 383 | Instances are typically instantiated via constructor functions with the `new` keyword. 384 | 385 | We instantiate the class with the new keyword 386 | He refers to constructor functions ???? 387 | 388 | // We can inherit properties and methods class Person extends Master 389 | He refers to prototypes ???? 390 | 391 | Constructor iss a default function method that will be executed whenever we instantiate the class 392 | 393 | Classes are constructor functions and inheritance prototypes... 394 | 395 | 396 | 397 | --- 398 | 399 | TO ADD: this keyword issue and arrow function solution 400 | -------------------------------------------------------------------------------- /Z_00_0_basic-oop-with-js.md: -------------------------------------------------------------------------------- 1 | # OOP with JS 2 | 3 | 6 | 7 | ## Creating or initializing objects 8 | 9 | There are several ways to create or initialize an object. 10 | 11 | ### Literal notation 12 | 13 | ```js 14 | const myObj = { 15 | name: 'Peter' 16 | }; 17 | 18 | console.log(myObj); 19 | // { name: 'Peter' } 20 | ``` 21 | 22 | We can use `literal notation` to initialize an object and the `dot notation` to add properties. 23 | 24 | ```js 25 | // literal notation 26 | const myObj = {}; 27 | 28 | // dot notation 29 | myObj.name = 'Peter'; 30 | 31 | console.log(myObj); 32 | // { name: 'Peter' } 33 | ``` 34 | 35 | 36 | ### Object() constructor 37 | 38 | ```js 39 | const myObj = new Object(); 40 | 41 | myObj.name = 'Peter'; 42 | 43 | console.log(myObj); 44 | // { name: 'Peter' } 45 | ``` 46 | 47 | ### Object.create() 48 | We pass an existing object as the prototype of the new object **or** `null` 49 | 50 | ```js 51 | const myObj = {}; 52 | myObj.name = 'Peter'; 53 | 54 | const mySubObj = Object.create(myObj); 55 | 56 | console.log(mySubObj); 57 | // {} 58 | 59 | console.log(mySubObj.name); 60 | // Peter 61 | ``` 62 | 63 | ... and we can check if `myObj` is the prototype of `mySubObj` 64 | 65 | ```js 66 | console.log(Object.getPrototypeOf(mySubObj) === myObj) 67 | // true 68 | ``` 69 | 70 | *Note:* Later we will see what prototype means. 71 | 72 | *Literal notation* is faster and less verbose and it should be your first option. 73 | 74 | --- 75 | 76 | Now, let's say that you need to create several objects with "the same shape" or properties. 77 | 78 | You could do... 79 | ```js 80 | const peter = { 81 | name: 'Peter', 82 | greeting() { 83 | console.log(`Hello, I'm ${this.name}`) 84 | } 85 | } 86 | 87 | const wendy = { 88 | name: 'Wendy', 89 | greeting() { 90 | console.log(`Hello, I'm ${this.name}`) 91 | } 92 | } 93 | 94 | const tinkerbell = { 95 | name: 'Tinkerbell', 96 | greeting() { 97 | console.log(`Hello, I'm ${this.name}`) 98 | } 99 | } 100 | 101 | console.log(peter); 102 | // { name: 'Peter', greeting: [Function: greeting] } 103 | 104 | peter.greeting(); 105 | // Hello, I'm Peter 106 | ``` 107 | 108 | ... or, use a `regular function` to construct your objects. 109 | 110 | ```js 111 | function createCharacter(name) { 112 | const newCharacter = {}; 113 | newCharacter.name = name; 114 | newCharacter.greeting = function greeting() { 115 | console.log(`Hello, I'm ${this.name}`); 116 | } 117 | return newCharacter; 118 | } 119 | 120 | const peter = createCharacter('Peter'); 121 | 122 | const wendy = createCharacter('Wendy'); 123 | 124 | const tinkerbell = createCharacter('Tinkerbell'); 125 | 126 | console.log(peter); 127 | // { name: 'Peter', greeting: [Function: greeting] } 128 | 129 | peter.greeting(); 130 | // Hello, I'm Peter 131 | ``` 132 | 133 | *Note*: In `pre es2015` implementations you will see `greeting: function greeting() {}` instead of `greeting() {}`. Check the transpiled code into `pre es2015` in [babel](https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBADgUyggTjAvDA3gKBjMAQwFsEAuGAcgAUlVKAaPGAcxQSQEswWAKASmzN8oSCAA2CAHTiQfAAYAJBONkMYASUrEYAEixQAFpwhSipAL7z-zCzgtA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Ces2015&prettier=false&targets=&version=7.12.9&externalPlugins=) 134 | 135 | This is better (aka, cleaner/DRYer) than the previous snippet, but... We are still creating the same function and adding it as a method on the object. 136 | 137 | And here is where the "OOP" or JS Prototypal Inheritance shines. 138 | 139 | We are going to *abstract* the common logic into a `new object` and keep our `function` to initialize the characters objects. It sounds fairly simple, no? 140 | But, how are we going to let each character object know where to find the abstracted functionality and link it at runtime to the "caller object"? Voila, we will create the characters objects with the functionality object as their prototypes using `Object.create(objectPrototype)` instead of an `object literal`. 141 | 142 | ```js 143 | const characterFunctionality = { 144 | greeting: function greeting() { 145 | console.log(`Hello, I'm ${this.name}`); 146 | } 147 | }; 148 | 149 | 150 | function createCharacter(name) { 151 | const newCharacter = Object.create(characterFunctionality) 152 | newCharacter.name = name; 153 | return newCharacter; 154 | } 155 | 156 | const peter = createCharacter('Peter'); 157 | 158 | const wendy = createCharacter('Wendy'); 159 | 160 | const tinkerbell = createCharacter('Tinkerbell'); 161 | 162 | console.log(peter); 163 | // { name: 'Peter', greeting: [Function: greeting] } 164 | 165 | peter.greeting(); 166 | // Hello, I'm Peter 167 | ``` 168 | 169 | **What is going on...?** 170 | At a high level (we will see this in more detail) when we call the method `greeting()` on the object `peter`, the JS interpreter tries to find it on the own object (peter), but, since that method doesn't exist, it will go UP on the prototype chain trying to find that method in the next prototype. 171 | 172 | When we created our objects, we linked them to `characterFunctionality`, and, that link resides in a hidden property: `__proto__`. 173 | 174 | 175 | 180 | 181 | 182 | Until now, we have being using `regular functions` to create or initialize our objects. However, we can add the usage of the `new keyword` to simplify some of the initialization process deferring to it (the `new keyword`, allow me the alliteration) the workload. 183 | 184 | Before seeing an example, let's state what `new` is going to do for us: 185 | 186 | 1. Creates a blank object 187 | 2. Sets the prototype bind 188 | 3. Sets this as the newly object: {} 189 | 4. Returns 190 | 191 | ```js 192 | function CreateCharacter(name) { 193 | this.name = name; 194 | } 195 | 196 | const peter = new CreateCharacter('Peter'); 197 | 198 | console.log(peter); 199 | // CreateCharacter { name: 'Peter' } 200 | ``` 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | **Sample object** 210 | 211 | ```js 212 | const character = { 213 | name: 'Peter', 214 | lastName: 'Pan', 215 | greeting: (yourName) => `Hello, ${yourName}` 216 | } 217 | 218 | console.log( 219 | character.greeting('Wendy') 220 | ) 221 | ``` 222 | 223 | **Quick note about THIS:** 224 | 225 | We are going to address `this` keyword in other section. But for the moment, and to keep the sight in `OOP`, just remember... *Arrow functions close over `this` of the lexically enclosing context.* 226 | So, if you need to access to a property of your object, use regular functions. 227 | 228 | Examples: 229 | 230 | The context of our arrow function is the Global/Window object; the context of our "regular" function is the base object. 231 | 232 | ```js 233 | const name = 'Global-Peter' 234 | 235 | const character = { 236 | name: 'Scoped-Peter', 237 | arrowGreeting: () => { 238 | return `My name is ${name}` 239 | }, 240 | funcGreeting() { 241 | return `My name is ${this.name}` 242 | } 243 | } 244 | 245 | console.log( 246 | character.arrowGreeting(), 247 | character.funcGreeting() 248 | ) 249 | 250 | ``` 251 | 252 | ## Adding properties 253 | 254 | ```js 255 | const myObj = {} 256 | 257 | myObj.someProperty = 123 258 | 259 | myObj['property with special chars'] = 'abc' 260 | 261 | myObj.someMethod = function() { 262 | return 'Hi!' 263 | } 264 | 265 | console.log(myObj.someProperty) 266 | // 123 267 | 268 | console.log(myObj['property with special chars']) 269 | // abc 270 | 271 | console.log(myObj.someMethod()) 272 | // Hi! 273 | ``` 274 | 275 | A few notes before jumping to the next topic: 276 | 277 | 1. You can take advantage of named function to improve your debugging work (instead of anonymous functions). Here will not have much sense, but when you are going through stack traces it could make thing clearer and easier to understand or debug. 278 | ```js 279 | myObj.someMethod = function theFunctionName() { 280 | return 'Hi!' 281 | } 282 | ``` 283 | 284 | 2. Feel free to use **ES2015** 285 | ```js 286 | myObj.someMethod = () => { 287 | return 'Hi!' 288 | } 289 | ``` 290 | 291 | 3. Since we are just returning, if you want to have a "cleaner" code you can do: 292 | ```js 293 | myObj.someMethod = () => 'Hi!' 294 | ``` 295 | 296 | ## Removing properties 297 | 298 | ```js 299 | const myObj = {} 300 | 301 | myObj.someProperty = 123 302 | 303 | delete myObj.someProperty 304 | 305 | console.log(myObj) 306 | ``` 307 | 308 | Both previous methods to `ADD` and `DELETE` properties mutate the `object`. 309 | 310 | In **JavaScript** we have `primitive` (string, number, boolean, etc) and `reference types` (objects and its special object type, array) 311 | 312 | **Primitives are immutable while reference types are mutable.** 313 | 314 | Let's see some examples to illustrate the previous statements. They will also help us to start taking `scopes` into consideration. 315 | 316 | 1. `var` holding a `primitive` value and a `function updating` (or mutating) the value of that variable 317 | 318 | ```js 319 | var primitive = 123 320 | 321 | function updt(n) { 322 | primitive = n 323 | } 324 | 325 | updt(1) 326 | 327 | console.log(primitive) 328 | // 1 329 | 330 | console.log(window.primitive) 331 | // 1 332 | ``` 333 | 334 | Some considerations... 335 | When we use `var`, the scope is going to be global/window or the function within its declared. 336 | If we add the `var statement` inside the function we will have a totally different result, since now, we have 2 primitive variables, one scoped to the global object (in the browser, `window`) and the other to the function `updt` 337 | 338 | ```js 339 | var primitive = 123 340 | 341 | function updt(n) { 342 | var primitive = n 343 | return n 344 | } 345 | 346 | updt(1) 347 | 348 | console.log(window.primitive) 349 | // 123 350 | 351 | console.log(updt(1)) 352 | // 1 353 | ``` 354 | 355 | Remember, since our variable is scoped to the `updt function`, every other `function` within this one will have access to it. 356 | 357 | ```js 358 | var primitive = 123 359 | 360 | function updt(n) { 361 | var primitive = n 362 | return function insideUpdt() { 363 | return primitive 364 | }() 365 | } 366 | 367 | updt(1) 368 | 369 | console.log(console.log(updt(1))) 370 | ``` 371 | 372 | *Note*: Here we are returning what the invocation of `insideUpdt()` returns, that's why we have the extra `()`. And yes, we could totally avoid naming our function (aka, use an anonymous function) but, for debugging purposes, named function always help. 373 | 374 | 2. `var` holding a `primitive` value and an `if statement` re-declaring that variable and assigning a new value 375 | 376 | ```js 377 | var primitive = 123 378 | 379 | // same as if(true) 380 | if(1) { 381 | var primitive = 456 382 | } 383 | 384 | console.log(primitive) 385 | // 456 386 | ``` 387 | 388 | Remember `var` is **NOT scoped** at the `block-level`. 389 | We are re-declaring our `primitive` variable (doable with `var`) and assigning a new value. 390 | BTW, remember that JS is going to process variable declarations before execution (aka, `hoisting`). So, the engine will do the following: 391 | 392 | ``` 393 | var primitive = 123 394 | primitive = 456 395 | ``` 396 | 397 | Now... What happens if we replace the `var` key with `let`...? 398 | 399 | ```js 400 | let primitive = 123 401 | 402 | // same as if(true) 403 | if(1) { 404 | let primitive = 456 405 | } 406 | 407 | console.log(primitive) 408 | // 123 409 | ``` 410 | 411 | As you probably guessed, for everything inside the "`if` scope" the value of `primitive` is going to be `456`. For the rest, it will be `123`. We are going to obtain "similar" results with `const`, with the difference that once declared and assigned the value, we won't be able to mutate it. 412 | 413 | ```js 414 | if(1) { 415 | const primitive = 456 416 | primitive = 789 417 | console.log(primitive) 418 | } 419 | ``` 420 | 421 | Result: `TypeError: Assignment to constant variable.` 422 | 423 | One more thing to remember... Global constants (`let` and `const`) do not become properties of the window object, unlike var variables. 424 | 425 | ```js 426 | var global = 1 427 | let notGlobal0 = 2 428 | const notGlobal1 = 3 429 | 430 | console.log( 431 | ` 432 | ${window.global} 433 | ${window.notGlobal0} 434 | ${window.notGlobal1} 435 | ` 436 | ) 437 | ``` 438 | 439 | Result: 440 | 441 | ```js 442 | 1 443 | undefined 444 | undefined 445 | ``` 446 | 447 | 3. `const` holding a `reference type` value and a `function updating` (or mutating) the data source (original variable) 448 | 449 | ```js 450 | const referenceValue = [1,2,3] 451 | 452 | function addNumberToArray(arr, num) { 453 | arr.push(num) 454 | return arr 455 | } 456 | 457 | console.log(addNumberToArray(referenceValue, 4)) 458 | // [ 1, 2, 3, 4 ] 459 | 460 | console.log(referenceValue) 461 | // [ 1, 2, 3, 4 ] 462 | ``` 463 | 464 | We have seen `mutations` (and immutability patterns) in several sections. But, it never hurts to refresh the memory. 465 | 466 | Look at the snippet above...? Is the return of `addNumberToArray()` what you were expecting...? 467 | If not, let's make a quick go-through. 468 | 469 | We declared a `constant` holding an `array` (of numbers) as value. 470 | We declared a `function` which receives as arguments an `array` and a `number`. This function pushes the number as an item into the passed array (aka, it adds the item to the end of the array); then, it returns its value. 471 | 472 | However... When we are logging both, the return of our function and our original constant, we can see that both are returning the "same value". 473 | 474 | *First feeling:* Didn't we say we cannot reassign the value of a `constant`? 475 | Yes... Technically, we are not changing its value if not altering one of its properties (same for item's arrays) 476 | 477 | This **reassigns the values** and produces the expected ERROR 478 | 479 | ```js 480 | const numbers = [1,2,3] 481 | numbers = [1,2,3,4] 482 | 483 | const names = { 484 | dad: 'Peter', 485 | son: 'Pan' 486 | } 487 | 488 | names = { 489 | dad: 'Peter', 490 | son: 'Pan', 491 | sister: 'Wendy' 492 | } 493 | ``` 494 | 495 | Result: `TypeError: Assignment to constant variable.` 496 | 497 | Now, this doesn't reassign. It just update the property/list. 498 | 499 | ```js 500 | const numbers = [1,2,3] 501 | numbers.push(4) 502 | 503 | const names = { 504 | dad: 'Peter', 505 | son: 'Pan' 506 | } 507 | 508 | names.sister = 'Wendy' 509 | 510 | 511 | console.log(numbers) 512 | // [ 1, 2, 3, 4 ] 513 | 514 | console.log(names) 515 | // { dad: 'Peter', son: 'Pan', sister: 'Wendy' } 516 | ``` 517 | 518 | From Mozilla's docs... 519 | > The const declaration creates a read-only reference to a value. It does not mean the value it holds is immutable—just that the variable identifier cannot be reassigned. For instance, in the case where the content is an object, this means the object's contents (e.g., its properties) can be altered. 520 | 521 | 522 | *Second feeling:* Why did the original constant change...? 523 | 524 | Maybe you tried to declare a variable within the function, assigned as its value the `array` we are passing and then, performed the operation... 525 | 526 | ```js 527 | function addNumberToArray(arr, num) { 528 | const newReferenceValue = arr 529 | newReferenceValue.push(num) 530 | return newReferenceValue 531 | } 532 | ``` 533 | 534 | Yet, the output is still the same. 535 | 536 | And this is because we are dealing with `reference types`. 537 | Objects (and arrays) are reference types... This means that every time we perform an operation over the reference, we are actually affecting the original object itself. 538 | 539 | *Time to see some basic examples:* 540 | 541 | Here we are declaring a constant with an object as value. That object is created using `literal notation` 542 | Then, we declare a new constant which points to the previous one. 543 | That why when we compare both, the result is **true**: they refer to the same object. 544 | 545 | ```js 546 | const character = { 547 | name: 'Peter', 548 | latName: 'Pan' 549 | } 550 | 551 | const refToObj = character 552 | 553 | console.log(refToObj) 554 | console.log(character) 555 | 556 | // { name: 'Peter', latName: 'Pan' } 557 | // { name: 'Peter', latName: 'Pan' } 558 | 559 | console.log(character === refToObj) 560 | // true 561 | ``` 562 | 563 | Here we have 2 objects *with the same keys and values*. 564 | When we compare both, the result is **false** 565 | 566 | ```js 567 | const peter1 = { 568 | name: 'Peter', 569 | latName: 'Pan' 570 | } 571 | 572 | const peter2 = { 573 | name: 'Peter', 574 | latName: 'Pan' 575 | } 576 | 577 | console.log(peter1) 578 | console.log(peter2) 579 | 580 | // { name: 'Peter', latName: 'Pan' } 581 | // { name: 'Peter', latName: 'Pan' } 582 | 583 | console.log(peter1 === peter2) 584 | // false 585 | ``` 586 | 587 | This is because comparisons between objects is by `reference` (not value), and, as you can see, each constant (object) refers to itself. 588 | 589 | **DO NOT do the following. Take it just as an illustration** 590 | 591 | What happens if we convert into strings both object before comparing them... 592 | 593 | ```js 594 | console.log(JSON.stringify(peter1) === JSON.stringify(peter2)) 595 | // true 596 | ``` 597 | 598 | ... since string comparisons are by `value`, both strings are equal. 599 | 600 | --- 601 | 602 | --------------------------------------------------------------------------------