├── 01-intro.js ├── 02-types-exercise.js ├── 02-types.js ├── 03-coercion-exercise.js ├── 03-coercion.js ├── 04-equality-exercise.js ├── 04-equality.js ├── 06-scope.js ├── 07-scope-function-expressions-exercise.js ├── 07-scope-function-expressions.js ├── 08-advanced-scope-exercise.js ├── 08-advanced-scope.js ├── 09-closure-exercise.js ├── 09-closure.js ├── 10-objects-exercise-class.js ├── 10-objects-exercise-this.js ├── 10-objects.js ├── 11-prototypes.js └── README.md /01-intro.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Understanding your code 4 | * 5 | */ 6 | 7 | // Algorithm of ++ operation 8 | function plusPlus(originalValue) { 9 | var coercedValue = Number(originalValue); 10 | y = coercedValue + 1; 11 | return coercedValue; 12 | } 13 | 14 | var y = "5"; 15 | 16 | console.log(plusPlus(y)); // 5 17 | console.log(y); // 6 -------------------------------------------------------------------------------- /02-types-exercise.js: -------------------------------------------------------------------------------- 1 | if (!Object.is || true) { 2 | Object.is = function ObjectIs(x, y) { 3 | var xNegZero = isItNegZero(x); 4 | var yNegZero = isItNegZero(y); 5 | 6 | if (xNegZero || yNegZero) { 7 | return xNegZero && yNegZero; 8 | } else if (isItNaN(x) && isItNaN(y)) { 9 | return true; 10 | } else if (x === y) { 11 | return true; 12 | } 13 | 14 | return false; 15 | 16 | // ********** 17 | 18 | function isItNegZero(x) { 19 | return x === 0 && 1 / x === -Infinity; 20 | } 21 | 22 | function isItNaN(x) { 23 | return x !== x; 24 | } 25 | }; 26 | } 27 | 28 | // tests: 29 | console.log(Object.is(42, 42) === true); 30 | console.log(Object.is("foo", "foo") === true); 31 | console.log(Object.is(false, false) === true); 32 | console.log(Object.is(null, null) === true); 33 | console.log(Object.is(undefined, undefined) === true); 34 | console.log(Object.is(NaN, NaN) === true); 35 | console.log(Object.is(-0, -0) === true); 36 | console.log(Object.is(0, 0) === true); 37 | 38 | console.log(Object.is(-0, 0) === false); 39 | console.log(Object.is(0, -0) === false); 40 | console.log(Object.is(0, NaN) === false); 41 | console.log(Object.is(NaN, 0) === false); 42 | console.log(Object.is(42, "42") === false); 43 | console.log(Object.is("42", 42) === false); 44 | console.log(Object.is("foo", "bar") === false); 45 | console.log(Object.is(false, true) === false); 46 | console.log(Object.is(null, undefined) === false); 47 | console.log(Object.is(undefined, null) === false); 48 | -------------------------------------------------------------------------------- /02-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * typeof operator 4 | * 5 | */ 6 | 7 | // typeof always returns a "string" 8 | 9 | var v; 10 | typeof v; // "undefined" 11 | 12 | var v = "1"; 13 | typeof v; // "string" 14 | 15 | v = 2; 16 | typeof v; // "number" 17 | 18 | v = true; 19 | typeof v; // "boolean" 20 | 21 | v = {}; 22 | typeof v; // "object" 23 | 24 | v = Symbol(); 25 | typeof v; // "symbol" 26 | 27 | typeof doesntExist; // "undefined" 28 | 29 | var v = null; 30 | typeof v; // "object" 31 | 32 | v = function () {}; 33 | typeof v; // "function" 34 | 35 | v = [1, 2, 3]; 36 | typeof v; // "object" 37 | 38 | /** 39 | * 40 | * BigInt 41 | * 42 | */ 43 | 44 | var x = 42n; 45 | typeof x; // "bigint" 46 | 47 | /** 48 | * 49 | * NaN & isNaN 50 | * 51 | */ 52 | 53 | // NaN = it is not not-number, it is invalid number 54 | 55 | var myAge = Number("0o46"); // 38 56 | var myNextAge = Number("39"); // 39 57 | 58 | var myCatsAge = Number("n/a"); // NaN 59 | myAge - "my sons age"; // NaN 60 | 61 | myCatsAge === myCatsAge; // false OOPS 62 | 63 | isNaN(myAge); // false 64 | isNaN(myCatsAge); // true 65 | isNaN("my sons age"); // true OOPS 66 | 67 | Number.isNaN(myCatsAge); // true 68 | Number.isNaN("my sons age"); // false 69 | 70 | /** 71 | * 72 | * Negative zero 73 | * 74 | */ 75 | 76 | var trendRate = -0; 77 | trendRate === -0; // true 78 | 79 | trendRate.toString(); // "0" 80 | trendRate === 0; // true 81 | trendRate < 0; // false 82 | trendRate > 0; // false 83 | 84 | // Best way to check whether two values are the same value 85 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 86 | Object.is(trendRate, -0); // true 87 | Object.is(trendRate, 0); // false 88 | 89 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign 90 | Math.sign(-3); // -1 91 | Math.sign(3); // 1 92 | Math.sign(-0); // -0 ??? 93 | Math.sign(0); // 0 ??? 94 | 95 | // Fix Math.sign 96 | function sign(v) { 97 | return v !== 0 ? Math.sign(v) : Object.is(v, -0) ? -1 : 1; 98 | } 99 | 100 | Math.sign(-3); // -1 101 | Math.sign(3); // 1 102 | Math.sign(-0); // -1 103 | Math.sign(0); // 1 104 | 105 | function formatTrend(trendRate) { 106 | var direction = trendRate < 0 || Object.is(trendRate, -0) ? "↓" : "↑"; 107 | return `${direction} ${Math.abs(trendRate)}`; 108 | } 109 | 110 | formatTrend(-3); // "↓ 3" 111 | formatTrend(3); // "↑ 3" 112 | formatTrend(-0); // "↓ 0" 113 | formatTrend(0); // "↑ 0" 114 | 115 | /** 116 | * 117 | * Fundamental objects 118 | * 119 | */ 120 | 121 | // When to use new keyword? 122 | Object(); 123 | Array(); 124 | Function(); 125 | Date(); 126 | RegExp(); 127 | Error(); 128 | 129 | // Don't use new keyword 130 | String(); 131 | Number(); 132 | Boolean(); 133 | -------------------------------------------------------------------------------- /03-coercion-exercise.js: -------------------------------------------------------------------------------- 1 | function isValidName(name) { 2 | if (typeof name === "string" && name.trim().length >= 3) { 3 | return true; 4 | } else { 5 | return false; 6 | } 7 | } 8 | 9 | function hoursAttended(attended, length) { 10 | if (typeof attended == "string" && attended.trim() != "") { 11 | attended = Number(attended); 12 | } 13 | 14 | if (typeof length == "string" && length.trim() != "") { 15 | length = Number(length); 16 | } 17 | 18 | if ( 19 | typeof attended == "number" && 20 | typeof length == "number" && 21 | attended <= length && 22 | attended >= 0 && 23 | length >= 0 && 24 | Number.isInteger(attended) && 25 | Number.isInteger(length) 26 | ) { 27 | return true; 28 | } 29 | 30 | return false; 31 | } 32 | -------------------------------------------------------------------------------- /03-coercion.js: -------------------------------------------------------------------------------- 1 | // Algorithms in JS are largely recursive 2 | 3 | /** 4 | * 5 | * toString() 6 | * 7 | */ 8 | 9 | var negativeZero = -0; 10 | negativeZero.toString(); // "0" 11 | 12 | var x = []; 13 | x.toString(); // '' 14 | 15 | var y = ["..."]; 16 | y.toString(); // '...' 17 | 18 | var z = [null, undefined]; 19 | z.toString(); // ',' 20 | 21 | var obj = {}; 22 | obj.toString(); // '[object Object]' 23 | 24 | /** 25 | * 26 | * toNumber() 27 | * 28 | */ 29 | 30 | var num1 = ""; 31 | Number(num1); // 0 32 | 33 | var num2 = " 009 "; 34 | Number(num2); // 9 35 | 36 | var num3 = null; 37 | Number(num3); // 0 38 | 39 | var num4 = undefined; 40 | Number(num4); // NaN 41 | 42 | /** 43 | * 44 | * Cases of coercion 45 | * 46 | */ 47 | 48 | var num = 16; 49 | 50 | // Implicit coercion 51 | console.log(`There are ${num} dwarves in the garden`); 52 | // 'There are 16 dwarves in the garden' 53 | 54 | // Explicit coercion 55 | console.log(`There are ${String(num)} dwarves in the garden`); 56 | -------------------------------------------------------------------------------- /04-equality-exercise.js: -------------------------------------------------------------------------------- 1 | function findAll(match, arr) { 2 | var ret = []; 3 | 4 | for (let v of arr) { 5 | if (Object.is(match, v)) { 6 | ret.push(v); 7 | } else if (match == null && v == null) { 8 | ret.push(v); 9 | } else if (typeof match == "boolean") { 10 | if (match === v) { 11 | ret.push(v); 12 | } 13 | } else if ( 14 | typeof match == "string" && 15 | match.trim() != "" && 16 | typeof v == "number" && 17 | !Object.is(-0, v) 18 | ) { 19 | if (match == v) { 20 | ret.push(v); 21 | } 22 | } else if ( 23 | typeof match == "number" && 24 | !Object.is(match, -0) && 25 | !Object.is(match, NaN) && 26 | !Object.is(match, Infinity) && 27 | !Object.is(match, -Infinity) && 28 | typeof v == "string" && 29 | v.trim() != "" 30 | ) { 31 | if (match == v) { 32 | ret.push(v); 33 | } 34 | } 35 | } 36 | return ret; 37 | } 38 | -------------------------------------------------------------------------------- /04-equality.js: -------------------------------------------------------------------------------- 1 | // == -> allows coercion (types different) 2 | // === -> disallows coercion (types same) 3 | 4 | // Coercive equality prefers numeric comparison 5 | 6 | // Avoid: 7 | // == with 0 or "" (or even " ") 8 | // == with non-primitives 9 | // == true or == false (allow ToBoolean or use ===) 10 | 11 | // The case for preferring == 12 | // if you know the types 13 | -------------------------------------------------------------------------------- /06-scope.js: -------------------------------------------------------------------------------- 1 | // Scope = where to look for things 2 | // Javascript organizes scopes with functions and blocks (ES6) 3 | 4 | var teacher = "Kyle"; 5 | 6 | function otherClass() { 7 | var teacher = "Suzy"; 8 | console.log("Welcome"); // console is an "auto-global" 9 | } 10 | 11 | function ask() { 12 | var question = "Why?"; 13 | console.log(question); 14 | } 15 | 16 | otherClass(); // Welcome! 17 | ask(); // Why? 18 | 19 | // Example 20 | // ************************************* 21 | // 1. line 17 - ask() 22 | // 2. asks global scope for identifier called ask() - line 11 23 | // 3. () of ask() executes the function 24 | // 4. line 12 - target reference for identifier question 25 | // 5. line 13 - asks scope of ask for identifier question 26 | 27 | /** 28 | * 29 | * Dynamic global variables 30 | * 31 | */ 32 | 33 | var teacher = "Kyle"; 34 | 35 | function otherClass() { 36 | teacher = "Suzy"; 37 | topic = "React"; // Creates an auto-global declaration dynamically at runtime 38 | console.log("Welcome"); 39 | } 40 | 41 | otherClass(); 42 | 43 | teacher; // Suzy, reassigned at line 36 44 | topic; // React 45 | 46 | /** 47 | * 48 | * Strict mode 49 | * 50 | */ 51 | 52 | ("use strict"); 53 | 54 | var teacher = "Kyle"; 55 | 56 | function otherClass() { 57 | teacher = "Suzy"; 58 | topic = "React"; // ReferenceError: topic is not defined 59 | console.log("Welcome"); 60 | } 61 | 62 | otherClass(); 63 | 64 | /** 65 | * 66 | * Nested scope 67 | * 68 | */ 69 | 70 | var teacher = "Kyle"; 71 | 72 | function otherClass() { 73 | var teacher = "Suzy"; 74 | 75 | function ask(question) { 76 | console.log(teacher, question); 77 | } 78 | 79 | ask("Why?"); 80 | } 81 | 82 | otherClass(); // Suzy Why? 83 | ask("????"); // ReferenceError 84 | -------------------------------------------------------------------------------- /07-scope-function-expressions-exercise.js: -------------------------------------------------------------------------------- 1 | var currentEnrollment = [410, 105, 664, 375]; 2 | 3 | var studentRecords = [ 4 | { id: 313, name: "Frank", paid: true }, 5 | { id: 410, name: "Suzy", paid: true }, 6 | { id: 709, name: "Brian", paid: false }, 7 | { id: 105, name: "Henry", paid: false }, 8 | { id: 502, name: "Mary", paid: true }, 9 | { id: 664, name: "Bob", paid: false }, 10 | { id: 250, name: "Peter", paid: true }, 11 | { id: 375, name: "Sarah", paid: true }, 12 | { id: 867, name: "Greg", paid: false }, 13 | ]; 14 | 15 | /** 16 | * 17 | * Part 1 18 | * 19 | */ 20 | 21 | function getStudentFromId(studentId) { 22 | return studentRecords.find(function matchId(record) { 23 | return record.id == studentId; 24 | }); 25 | } 26 | 27 | function printRecords(recordIds) { 28 | var records = recordIds.map(getStudentFromId); 29 | 30 | records.sort(function sortByNameAsc(record1, record2) { 31 | if (record1.name < record2.name) return -1; 32 | else if (record1.name > record2.name) return 1; 33 | else return 0; 34 | }); 35 | 36 | records.forEach(function printRecord(record) { 37 | console.log( 38 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 39 | ); 40 | }); 41 | } 42 | 43 | function paidStudentsToEnroll() { 44 | var recordsToEnroll = studentRecords.filter(function needToEnroll(record) { 45 | return record.paid && !currentEnrollment.includes(record.id); 46 | }); 47 | 48 | var idsToEnroll = recordsToEnroll.map(function getStudentId(record) { 49 | return record.id; 50 | }); 51 | 52 | return [...currentEnrollment, ...idsToEnroll]; 53 | } 54 | 55 | function remindUnpaid(recordIds) { 56 | var unpaidIds = recordIds.filter(function notYetPaid(studentId) { 57 | var record = getStudentFromId(studentId); 58 | return !record.paid; 59 | }); 60 | 61 | printRecords(unpaidIds); 62 | } 63 | 64 | printRecords(currentEnrollment); 65 | console.log("----"); 66 | currentEnrollment = paidStudentsToEnroll(); 67 | printRecords(currentEnrollment); 68 | console.log("----"); 69 | remindUnpaid(currentEnrollment); 70 | 71 | /** 72 | * 73 | * Part 2 74 | * 75 | */ 76 | 77 | // Get student by ID 78 | var getStudentFromId = (studentId) => 79 | studentRecords.find((record) => record.id == studentId); 80 | 81 | var printRecords = (recordIds) => 82 | recordIds 83 | .map(getStudentFromId) // Is this basically the same as mapping over studentRecords? 84 | .sort((record1, record2) => 85 | record1.name < record2.name ? -1 : record1.name > record2.name ? 1 : 0 86 | ) 87 | .forEach((record) => 88 | console.log( 89 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 90 | ) 91 | ); 92 | 93 | var paidStudentsToEnroll = () => [ 94 | ...currentEnrollment, 95 | ...studentRecords 96 | .filter((record) => record.paid && !currentEnrollment.includes(record.id)) 97 | .map((record) => record.id), 98 | ]; 99 | 100 | var remindUnpaid = (recordIds) => 101 | printRecords( 102 | recordIds.filter((studentId) => !getStudentFromId(studentId).paid) 103 | ); 104 | 105 | printRecords(currentEnrollment); 106 | console.log("----"); 107 | currentEnrollment = paidStudentsToEnroll(); 108 | printRecords(currentEnrollment); 109 | console.log("----"); 110 | remindUnpaid(currentEnrollment); 111 | -------------------------------------------------------------------------------- /07-scope-function-expressions.js: -------------------------------------------------------------------------------- 1 | // Function declaration 2 | function teacher() {} 3 | 4 | // Function expression 5 | var myTeacher = function anotherTeacher() { 6 | console.log(anotherTeacher); 7 | }; 8 | 9 | console.log(teacher); 10 | console.log(myTeacher); 11 | console.log(anotherTeacher); // ReferenceError 12 | 13 | // Anonymous function expression 14 | var anonFooExp = function () {}; 15 | 16 | // Named function expression 17 | var namedFooExp = function foo() {}; 18 | 19 | // Prefer named function expressions! 20 | // ********************************** 21 | // 1. Reliable function self-reference 22 | // 2. More debuggable stack traces 23 | // 3. More self-documenting code 24 | 25 | // Arrow functions 26 | var ids = people.map((person) => person.id); 27 | 28 | var ids = people.map(function getId(person) { 29 | return person.id; 30 | }); 31 | 32 | // Named function declaration > Named function expression > Anonymous function expression 33 | -------------------------------------------------------------------------------- /08-advanced-scope-exercise.js: -------------------------------------------------------------------------------- 1 | function getStudentFromId(studentId) { 2 | return studentRecords.find(matchId); 3 | 4 | // Must be nested because of the reference to studentId 5 | function matchId(record) { 6 | return record.id == studentId; 7 | } 8 | } 9 | 10 | function printRecords(recordIds) { 11 | var records = recordIds.map(getStudentFromId); 12 | 13 | records.sort(sortByNameAsc); 14 | records.forEach(printRecord); 15 | } 16 | 17 | function sortByNameAsc(record1, record2) { 18 | if (record1.name < record2.name) return -1; 19 | else if (record1.name > record2.name) return 1; 20 | else return 0; 21 | } 22 | 23 | function printRecord(record) { 24 | console.log( 25 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 26 | ); 27 | } 28 | 29 | function paidStudentsToEnroll() { 30 | var recordsToEnroll = studentRecords.filter(needToEnroll); 31 | var idsToEnroll = recordsToEnroll.map(getStudentId); 32 | 33 | return [...currentEnrollment, ...idsToEnroll]; 34 | } 35 | 36 | function needToEnroll(record) { 37 | return record.paid && !currentEnrollment.includes(record.id); 38 | } 39 | 40 | function getStudentId(record) { 41 | return record.id; 42 | } 43 | 44 | function remindUnpaid(recordIds) { 45 | var unpaidIds = recordIds.filter(notYetPaid); 46 | 47 | printRecords(unpaidIds); 48 | } 49 | 50 | function notYetPaid(studentId) { 51 | var record = getStudentFromId(studentId); 52 | 53 | return !record.paid; 54 | } 55 | -------------------------------------------------------------------------------- /08-advanced-scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Lexical scope 4 | * 5 | */ 6 | 7 | // Think of lexical scope in terms of the compiler 8 | // Decided at compile time, not run time 9 | 10 | var teacher = "Kyle"; 11 | 12 | function otherClass() { 13 | var teacher = "Suzy"; 14 | 15 | function ask(question) { 16 | console.log(teacher, question); 17 | } 18 | 19 | ask("Why"); 20 | } 21 | 22 | /** 23 | * 24 | * Dynamic scope 25 | * 26 | */ 27 | 28 | // Does not exist in Javascript 29 | 30 | /** 31 | * 32 | * Function scoping 33 | * 34 | */ 35 | 36 | // Use the defensive approach of "minimum exposure" 37 | // Hide things from the global scope as much as possible 38 | 39 | /** 40 | * 41 | * IIFE 42 | * 43 | */ 44 | 45 | var teacher = "Kyle"; 46 | 47 | (function anotherTeacher() { 48 | var teacher = "Suzy"; 49 | console.log(teacher); // Suzy 50 | })(); 51 | 52 | console.log(teacher); // Kyle 53 | 54 | /** 55 | * 56 | * Block scoping 57 | * 58 | */ 59 | 60 | var teacher = "Kyle"; 61 | 62 | { 63 | let teacher = "Suzy"; 64 | console.log(teacher); // Suzy 65 | } 66 | 67 | console.log(teacher); // Kyle 68 | 69 | /** 70 | * 71 | * let and var 72 | * 73 | */ 74 | 75 | function repeat(fn, n) { 76 | var result; // used in the entire function scope 77 | 78 | // let for localized usage 79 | for (let i = 0; i < n; i++) { 80 | result = fn(result, i); 81 | } 82 | 83 | return result; 84 | } 85 | 86 | /** 87 | * 88 | * const 89 | * 90 | */ 91 | 92 | const myTeacher = teacher; 93 | myTeacher = "Suzy"; // TypeError 94 | 95 | const teachers = ["Kyle", "Suzy"]; 96 | teachers[1] = "Brian"; 97 | teachers; // [ 'Kyle', 'Brian' ] 98 | 99 | /** 100 | * 101 | * Hoisting 102 | * 103 | */ 104 | 105 | // variable hoisting is usually bad 106 | teacher = "Kyle"; 107 | var teacher; 108 | 109 | // function hoisting is pretty useful 110 | getTeacher(); 111 | 112 | function getTeacher() { 113 | return teacher; 114 | } 115 | -------------------------------------------------------------------------------- /09-closure-exercise.js: -------------------------------------------------------------------------------- 1 | function defineWorkshop() { 2 | var currentEnrollment = []; 3 | var studentRecords = []; 4 | 5 | var publicAPI = { 6 | addStudent, 7 | enrollStudent, 8 | printCurrentEnrollment, 9 | enrollPaidStudents, 10 | remindUnpaidStudents, 11 | }; 12 | 13 | return publicAPI; 14 | 15 | // ******************************** 16 | 17 | function addStudent(id, name, paid) { 18 | studentRecords.push({ id, name, paid }); 19 | } 20 | 21 | function enrollStudent(id) { 22 | if (!currentEnrollment.includes(id)) { 23 | currentEnrollment.push(id); 24 | } 25 | } 26 | 27 | function printCurrentEnrollment() { 28 | printRecords(currentEnrollment); 29 | } 30 | 31 | function enrollPaidStudents() { 32 | currentEnrollment = paidStudentsToEnroll(); 33 | printCurrentEnrollment(); 34 | } 35 | 36 | function remindUnpaidStudents() { 37 | remindUnpaid(currentEnrollment); 38 | } 39 | 40 | function getStudentFromId(studentId) { 41 | return studentRecords.find(matchId); 42 | 43 | // ************************* 44 | 45 | function matchId(record) { 46 | return record.id == studentId; 47 | } 48 | } 49 | 50 | function printRecords(recordIds) { 51 | var records = recordIds.map(getStudentFromId); 52 | 53 | records.sort(sortByNameAsc); 54 | records.forEach(printRecord); 55 | } 56 | 57 | function sortByNameAsc(record1, record2) { 58 | if (record1.name < record2.name) return -1; 59 | else if (record1.name > record2.name) return 1; 60 | else return 0; 61 | } 62 | 63 | function printRecord(record) { 64 | console.log( 65 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 66 | ); 67 | } 68 | 69 | function paidStudentsToEnroll() { 70 | var recordsToEnroll = studentRecords.filter(needToEnroll); 71 | var idsToEnroll = recordsToEnroll.map(getStudentId); 72 | 73 | return [...currentEnrollment, ...idsToEnroll]; 74 | } 75 | 76 | function needToEnroll(record) { 77 | return record.paid && !currentEnrollment.includes(record.id); 78 | } 79 | 80 | function getStudentId(record) { 81 | return record.id; 82 | } 83 | 84 | function remindUnpaid(recordIds) { 85 | var unpaidIds = recordIds.filter(notYetPaid); 86 | 87 | printRecords(unpaidIds); 88 | } 89 | 90 | function notYetPaid(studentId) { 91 | var record = getStudentFromId(studentId); 92 | return !record.paid; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /09-closure.js: -------------------------------------------------------------------------------- 1 | // Closure is when a function "remembers" its lexical scope 2 | // even when the function is executed outside that lexical scope 3 | 4 | function ask(question) { 5 | return function holdYourQuestion() { 6 | console.log(question); 7 | }; 8 | } 9 | 10 | var myQuestion = ask("What is a closure?"); 11 | 12 | myQuestion(); // What is a closure? 13 | 14 | // You close over variables, not values 15 | // Preserves access to variables 16 | 17 | var teacher = "Kyle"; 18 | 19 | var myTeacher = function () { 20 | console.log(teacher); // "Suzy" 21 | }; 22 | 23 | teacher = "Suzy"; 24 | 25 | myTeacher(); // "Suzy" 26 | 27 | /** 28 | * 29 | * Module pattern 30 | * 31 | */ 32 | 33 | // Module pattern requires encapsulation 34 | // You can't have a module without closure 35 | var workshop = (function Module(teacher) { 36 | var publicAPI = { ask }; 37 | return publicAPI; 38 | 39 | // ********************* 40 | function ask(question) { 41 | console.log(teacher, question); 42 | } 43 | })("Kyle"); 44 | 45 | workshop.ask("It's a module, right?"); 46 | // 'Kyle' 'It\'s a module, right?' 47 | 48 | /** 49 | * 50 | * ES6 modules 51 | * 52 | */ 53 | 54 | var teacher = "Kyle"; 55 | 56 | export default function ask(question) { 57 | console.log(teacher, question); 58 | } 59 | 60 | /** 61 | * 62 | * ES6 module syntax 63 | * 64 | */ 65 | 66 | var teacher = "Kyle"; 67 | 68 | export default function ask(question) { 69 | console.log(teacher, question); 70 | } 71 | 72 | import ask from "workshop.mjs"; 73 | 74 | ask("It's a default import, right"); 75 | // 'Kyle' 'It\'s a default import, right?' 76 | 77 | import * as workshop from "workshop.mjs"; 78 | 79 | workshop.ask("It's a namespace import, right?"); 80 | // 'Kyle' 'It\'s a namespace import, right?' 81 | -------------------------------------------------------------------------------- /10-objects-exercise-class.js: -------------------------------------------------------------------------------- 1 | class Helpers { 2 | sortByNameAsc(record1, record2) { 3 | if (record1.name < record2.name) return -1; 4 | else if (record1.name > record2.name) return 1; 5 | else return 0; 6 | } 7 | 8 | printRecord(record) { 9 | console.log( 10 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 11 | ); 12 | } 13 | } 14 | 15 | class Workshop extends Helpers { 16 | constructor() { 17 | super(); 18 | this.currentEnrollment = []; 19 | this.studentRecords = []; 20 | } 21 | 22 | addStudent(id, name, paid) { 23 | this.studentRecords.push({ id, name, paid }); 24 | } 25 | 26 | enrollStudent(id) { 27 | if (!this.currentEnrollment.includes(id)) { 28 | this.currentEnrollment.push(id); 29 | } 30 | } 31 | 32 | printCurrentEnrollment() { 33 | this.printRecords(this.currentEnrollment); 34 | } 35 | 36 | enrollPaidStudents() { 37 | this.currentEnrollment = this.paidStudentsToEnroll(); 38 | this.printCurrentEnrollment(); 39 | } 40 | 41 | remindUnpaidStudents() { 42 | this.remindUnpaid(this.currentEnrollment); 43 | } 44 | 45 | getStudentFromId(studentId) { 46 | return this.studentRecords.find(matchId); 47 | 48 | function matchId(record) { 49 | return record.id == studentId; 50 | } 51 | } 52 | 53 | printRecords(recordIds) { 54 | var records = recordIds.map(this.getStudentFromId.bind(this)); 55 | records.sort(this.sortByNameAsc); 56 | records.forEach(this.printRecord); 57 | } 58 | 59 | paidStudentsToEnroll() { 60 | var recordsToEnroll = this.studentRecords.filter( 61 | this.needToEnroll.bind(this) 62 | ); 63 | 64 | var idsToEnroll = recordsToEnroll.map(this.getStudentId); 65 | return [...this.currentEnrollment, ...idsToEnroll]; 66 | } 67 | 68 | needToEnroll(record) { 69 | return record.paid && !this.currentEnrollment.includes(record.id); 70 | } 71 | 72 | getStudentId(record) { 73 | return record.id; 74 | } 75 | 76 | remindUnpaid(recordIds) { 77 | var unpaidIds = recordIds.filter(this.notYetPaid.bind(this)); 78 | this.printRecords(unpaidIds); 79 | } 80 | 81 | notYetPaid(studentId) { 82 | var record = this.getStudentFromId(studentId); 83 | return !record.paid; 84 | } 85 | } 86 | 87 | var deepJS = new Workshop(); 88 | 89 | deepJS.addStudent(311, "Frank", /*paid=*/ true); 90 | deepJS.addStudent(410, "Suzy", /*paid=*/ true); 91 | deepJS.addStudent(709, "Brian", /*paid=*/ false); 92 | deepJS.addStudent(105, "Henry", /*paid=*/ false); 93 | deepJS.addStudent(502, "Mary", /*paid=*/ true); 94 | deepJS.addStudent(664, "Bob", /*paid=*/ false); 95 | deepJS.addStudent(250, "Peter", /*paid=*/ true); 96 | deepJS.addStudent(375, "Sarah", /*paid=*/ true); 97 | deepJS.addStudent(867, "Greg", /*paid=*/ false); 98 | 99 | deepJS.enrollStudent(410); 100 | deepJS.enrollStudent(105); 101 | deepJS.enrollStudent(664); 102 | deepJS.enrollStudent(375); 103 | 104 | deepJS.printCurrentEnrollment(); 105 | 106 | console.log("----"); 107 | 108 | deepJS.enrollPaidStudents(); 109 | 110 | console.log("----"); 111 | 112 | deepJS.remindUnpaidStudents(); 113 | -------------------------------------------------------------------------------- /10-objects-exercise-this.js: -------------------------------------------------------------------------------- 1 | var deepJS = { 2 | currentEnrollment: [], 3 | studentRecords: [], 4 | 5 | addStudent(id, name, paid) { 6 | this.studentRecords.push({ id, name, paid }); 7 | }, 8 | 9 | enrollStudent(id) { 10 | if (!this.currentEnrollment.includes(id)) { 11 | this.currentEnrollment.push(id); 12 | } 13 | }, 14 | 15 | printCurrentEnrollment() { 16 | this.printRecords(this.currentEnrollment); 17 | }, 18 | 19 | enrollPaidStudents() { 20 | this.currentEnrollment = this.paidStudentsToEnroll(); 21 | this.printCurrentEnrollment(); 22 | }, 23 | 24 | remindUnpaidStudents() { 25 | this.remindUnpaid(this.currentEnrollment); 26 | }, 27 | 28 | getStudentFromId(studentId) { 29 | return this.studentRecords.find(matchId); 30 | 31 | // actual function which does not need this 32 | function matchId(record) { 33 | return record.id == studentId; 34 | } 35 | }, 36 | 37 | printRecords(recordIds) { 38 | // bind is used for callback methods 39 | var records = recordIds.map(this.getStudentFromId.bind(this)); 40 | records.sort(this.sortByNameAsc); 41 | records.forEach(this.printRecord); 42 | }, 43 | 44 | sortByNameAsc(record1, record2) { 45 | if (record1.name < record2.name) return -1; 46 | else if (record1.name > record2.name) return 1; 47 | else return 0; 48 | }, 49 | 50 | printRecord(record) { 51 | console.log( 52 | `${record.name} (${record.id}): ${record.paid ? "Paid" : "Not Paid"}` 53 | ); 54 | }, 55 | 56 | paidStudentsToEnroll() { 57 | var recordsToEnroll = this.studentRecords.filter( 58 | this.needToEnroll.bind(this) 59 | ); 60 | 61 | var idsToEnroll = recordsToEnroll.map(this.getStudentId); 62 | return [...this.currentEnrollment, ...idsToEnroll]; 63 | }, 64 | 65 | needToEnroll(record) { 66 | return record.paid && !this.currentEnrollment.includes(record.id); 67 | }, 68 | 69 | getStudentId(record) { 70 | return record.id; 71 | }, 72 | 73 | remindUnpaid(recordIds) { 74 | var unpaidIds = recordIds.filter(this.notYetPaid.bind(this)); 75 | this.printRecords(unpaidIds); 76 | }, 77 | 78 | notYetPaid(studentId) { 79 | var record = this.getStudentFromId(studentId); 80 | return !record.paid; 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /10-objects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * this 4 | * 5 | */ 6 | 7 | // A function's this references the execution context for that call, 8 | // determined entirely by how the function was called 9 | 10 | function ask(question) { 11 | console.log(this.teacher, question); 12 | } 13 | 14 | function otherClass() { 15 | var myContext = { 16 | teacher: "Suzy", 17 | }; 18 | 19 | ask.call(myContext, "Why?"); 20 | } 21 | 22 | otherClass(); // 'Suzy' 'Why?' 23 | 24 | /** 25 | * 26 | * Implicit & explicit binding 27 | * 28 | */ 29 | 30 | // Implicit binding 31 | var workshop = { 32 | teacher: "Kyle", 33 | 34 | ask(question) { 35 | console.log(this.teacher, question); 36 | }, 37 | }; 38 | 39 | workshop.ask("What is implicit binding?"); // 'Kyle' 'What is implicit binding?' 40 | 41 | function ask(question) { 42 | console.log(this.teacher, question); 43 | } 44 | 45 | var workshop1 = { 46 | teacher: "Kyle", 47 | ask: ask, 48 | }; 49 | 50 | var workshop2 = { 51 | teacher: "Suzy", 52 | ask: ask, 53 | }; 54 | 55 | workshop1.ask("How do I share a method?"); // 'Kyle' 'How do I share a method?' 56 | workshop2.ask("How do I share a method?"); // 'Suzy' 'How do I share a method?' 57 | 58 | // Explicit binding 59 | function ask(question) { 60 | console.log(this.teacher, question); 61 | } 62 | 63 | var workshop1 = { 64 | teacher: "Kyle", 65 | ask: ask, 66 | }; 67 | 68 | var workshop2 = { 69 | teacher: "Suzy", 70 | ask: ask, 71 | }; 72 | 73 | ask.call(workshop1, "How do I share a method?"); // 'Kyle' 'How do I share a method?' 74 | ask.call(workshop2, "How do I share a method?"); // 'Suzy' 'How do I share a method?' 75 | 76 | // Hard binding 77 | var workshop = { 78 | teacher: "Kyle", 79 | 80 | ask(question) { 81 | console.log(this.teacher, question); 82 | }, 83 | }; 84 | 85 | setTimeout(workshop.ask, 10, "Lost this?"); 86 | // undefined 'Lost this?' 87 | 88 | setTimeout(workshop.ask.bind(workshop), 10, "Hard bound this?"); 89 | // 'Kyle' 'Hard bound this?' 90 | 91 | /** 92 | * 93 | * Arrow functions & lexical this 94 | * 95 | */ 96 | 97 | // An arrow function is this-bound 98 | // aka .bind() to its parent function 99 | 100 | var workshop = { 101 | teacher: "Kyle", 102 | 103 | ask(question) { 104 | setTimeout(() => { 105 | console.log(this.teacher, question); 106 | }, 100); 107 | }, 108 | }; 109 | 110 | workshop.ask("Is this lexical 'this'?"); 111 | // 'Kyle' 'Is this lexical \'this\'?' 112 | 113 | /** 114 | * 115 | * Resolving this in arrow functions 116 | * 117 | */ 118 | 119 | // workshop is not a scope! 120 | 121 | var workshop = { 122 | teacher: "Kyle", 123 | 124 | ask: (question) => { 125 | console.log(this.teacher, question); 126 | }, 127 | }; 128 | 129 | workshop.ask("What happened to 'this'?"); 130 | // TypeError: Cannot read property 'teacher' of undefined 131 | 132 | workshop.ask.call(workshop, "Still no 'this'?"); 133 | // TypeError: Cannot read property 'teacher' of undefined 134 | 135 | // only use arrow functions when you need lexical this 136 | 137 | /** 138 | * 139 | * ES6 class keyword 140 | * 141 | */ 142 | 143 | class Workshop { 144 | constructor(teacher) { 145 | this.teacher = teacher; 146 | } 147 | 148 | ask(question) { 149 | console.log(this.teacher, question); 150 | } 151 | } 152 | 153 | var deepJS = new Workshop("Kyle"); 154 | var reactJS = new Workshop("Suzy"); 155 | 156 | deepJS.ask("Is 'class' a class?"); 157 | // 'Kyle' 'Is \'class\' a class?' 158 | 159 | reactJS.ask("Is this class OK?"); 160 | // 'Suzy' 'Is this class OK?' 161 | 162 | class AnotherWorkshop extends Workshop { 163 | speakUp(msg) { 164 | this.ask(msg); 165 | } 166 | } 167 | 168 | var JSRecentParts = new AnotherWorkshop("Kyle"); 169 | 170 | JSRecentParts.speakUp("Are classes getting better?"); 171 | // 'Kyle' 'Are classes getting better?' 172 | 173 | class AnotherWorkshop1 extends Workshop { 174 | ask(msg) { 175 | super.ask(msg.toUpperCase()); 176 | } 177 | } 178 | 179 | var JSRecentParts1 = new AnotherWorkshop("Kyle"); 180 | 181 | JSRecentParts1.ask("Are classes super?"); 182 | // 'Kyle' 'Are classes super?' 183 | 184 | class Workshop { 185 | constructor(teacher) { 186 | this.teacher = teacher; 187 | } 188 | 189 | ask(question) { 190 | console.log(this.teacher, question); 191 | } 192 | } 193 | 194 | var deepJS = new Workshop("Kyle"); 195 | 196 | setTimeout(deepJS.ask, 100, "Still losing 'this?"); 197 | // undefined 'Still losing \'this?' 198 | 199 | class Workshop { 200 | constructor(teacher) { 201 | this.teacher = teacher; 202 | 203 | this.ask = (question) => { 204 | console.log(this.teacher, question); 205 | }; 206 | } 207 | 208 | ask(question) { 209 | console.log(this.teacher, question); 210 | } 211 | } 212 | 213 | var deepJS = new Workshop("Kyle"); 214 | 215 | setTimeout(deepJS.ask, 100, "Is 'this' fixed?"); 216 | // 'Kyle' 'Is \'this\' fixed?' 217 | -------------------------------------------------------------------------------- /11-prototypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Prototypes 4 | * 5 | */ 6 | 7 | // A "constructor call" makes an object its own prototype 8 | 9 | /** 10 | * 11 | * Prototypal class 12 | * 13 | */ 14 | 15 | function Workshop(teacher) { 16 | this.teacher = teacher; 17 | } 18 | 19 | Workshop.prototype.ask = function (question) { 20 | console.log(this.teacher, question); 21 | }; 22 | 23 | var deepJS = new Workshop("Kyle"); 24 | var reactJS = new Workshop("Suzy"); 25 | 26 | deepJS.ask("Is 'prototype' a class?"); 27 | // 'Kyle' 'Is \'prototype\' a class?' 28 | 29 | reactJS.ask("Isn't 'prototype' ugly?"); 30 | // 'Suzy' 'Isn\'t \'prototype\' ugly?' 31 | 32 | /** 33 | * 34 | * Dunder prototypes 35 | * 36 | */ 37 | 38 | function Workshop(teacher) { 39 | this.teacher = teacher; 40 | } 41 | 42 | Workshop.prototype.ask = function (question) { 43 | console.log(this.teacher, queueMicrotask); 44 | }; 45 | 46 | var deepJS = new Workshop("Kyle"); 47 | 48 | deepJS.constructor === Workshop; // true 49 | 50 | deepJS.__proto__ === Workshop.prototype; // true 51 | 52 | Object.getPrototypeOf(deepJS) === Workshop.prototype; // true 53 | 54 | /** 55 | * 56 | * Shadowing prototypes 57 | * 58 | */ 59 | 60 | function Workshop(teacher) { 61 | this.teacher = teacher; 62 | } 63 | 64 | Workshop.prototype.ask = function (question) { 65 | console.log(this.teacher, queueMicrotask); 66 | }; 67 | 68 | var deepJS = new Workshop("Kyle"); 69 | 70 | deepJS.ask = function (question) { 71 | this.ask(question.toUpperCase()); 72 | }; 73 | 74 | deepJS.ask("Oops, is this infinite recursion?"); 75 | // RangeError: Maximum call stack size exceeded 76 | 77 | function Workshop(teacher) { 78 | this.teacher = teacher; 79 | } 80 | 81 | Workshop.prototype.ask = function (question) { 82 | console.log(this.teacher, question); 83 | }; 84 | 85 | var deepJS = new Workshop("Kyle"); 86 | 87 | deepJS.ask = function (question) { 88 | this.__proto__.ask.call(this, question.toUpperCase()); 89 | }; 90 | 91 | deepJS.ask("Is this fake polymorphism?"); 92 | // 'Kyle' 'IS THIS FAKE POLYMORPHISM?' 93 | 94 | /** 95 | * 96 | * Prototypal inheritance 97 | * 98 | */ 99 | 100 | function Workshop(teacher) { 101 | this.teacher = teacher; 102 | } 103 | 104 | Workshop.prototype.ask = function (question) { 105 | console.log(this.teacher, question); 106 | }; 107 | 108 | function AnotherWorkshop(teacher) { 109 | Workshop.call(this, teacher); 110 | } 111 | 112 | AnotherWorkshop.prototype = Object.create(Workshop.prototype); 113 | 114 | AnotherWorkshop.prototype.speakUp = function (msg) { 115 | this.ask(msg.toUpperCase()); 116 | }; 117 | 118 | var JSRecentParts = new AnotherWorkshop("Kyle"); 119 | 120 | JSRecentParts.speakUp("Is this actually inheritance?"); 121 | // 'Kyle' 'IS THIS ACTUALLY INHERITANCE?' 122 | 123 | /** 124 | * 125 | * OLOO (Objects Linked to Other Objects) pattern 126 | * 127 | */ 128 | 129 | // ES6 class pattern 130 | class Workshop { 131 | constructor(teacher) { 132 | this.teacher = teacher; 133 | } 134 | 135 | ask(question) { 136 | console.log(this.teacher, question); 137 | } 138 | } 139 | 140 | class AnotherWorkshop extends Workshop { 141 | speakUp(msg) { 142 | this.ask(msg); 143 | } 144 | } 145 | 146 | var JSRecentParts = new AnotherWorkshop("Kyle"); 147 | 148 | JSRecentParts.speakUp("Are classes getting better?"); 149 | 150 | // OLOO pattern 151 | var Workshop = { 152 | setTeacher(teacher) { 153 | this.teacher = teacher; 154 | }, 155 | 156 | ask(question) { 157 | console.log(this.teacher, question); 158 | }, 159 | }; 160 | 161 | var AnotherWorkshop = Object.assign(Object.create(Workshop), { 162 | speakUp(msg) { 163 | this.ask(msg.toUpperCase()); 164 | }, 165 | }); 166 | 167 | var JSRecentParts = Object.create(AnotherWorkshop); 168 | 169 | JSRecentParts.setTeacher("Kyle"); 170 | 171 | JSRecentParts.speakUp("But isn't this cleaner?"); 172 | //'Kyle' 'BUT ISN\'T THIS CLEANER?' 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Javascript Foundations 2 | 3 | Partial notes on the course [Deep Javascript Foundations](https://frontendmasters.com/courses/deep-javascript-v3/) by [Kyle Simpson](https://github.com/getify). 4 | 5 | ## Contents 6 | 7 | - [Intro](#intro) 8 | - [Types](#types) 9 | - [Coercion](#coercion) 10 | - [Equality](#equality) 11 | - [Scope](#scope) 12 | - [Scope & function expressions](#scope-&-function-expressions) 13 | - [Advanced scope](#advanced-scope) 14 | - [Closure](#closure) 15 | - [Objects](#objects) 16 | - [Prototypes](#prototypes) 17 | 18 | ## Intro 19 | 20 | Algorithm of ++ operation 21 | 22 | ```javascript 23 | function plusPlus(originalValue) { 24 | var coercedValue = Number(originalValue); 25 | y = coercedValue + 1; 26 | 27 | return coercedValue; 28 | } 29 | 30 | var y = "5"; 31 | 32 | console.log(plusPlus(y)); // 5 33 | console.log(y); // 6 34 | ``` 35 | 36 | ## Types 37 | 38 | ### `typeof` 39 | 40 | `typeof` operator always returns a "string" 41 | 42 | ```javascript 43 | var v; 44 | typeof v; // "undefined" 45 | 46 | var v = "1"; 47 | typeof v; // "string" 48 | 49 | v = 2; 50 | typeof v; // "number" 51 | 52 | v = true; 53 | typeof v; // "boolean" 54 | 55 | v = {}; 56 | typeof v; // "object" 57 | 58 | v = Symbol(); 59 | typeof v; // "symbol" 60 | 61 | typeof doesntExist; // "undefined" 62 | 63 | var v = null; 64 | typeof v; // "object" 65 | 66 | v = function () {}; 67 | typeof v; // "function" 68 | 69 | v = [1, 2, 3]; 70 | typeof v; // "object" 71 | ``` 72 | 73 | ### BigInt 74 | 75 | ```javascript 76 | var x = 42n; 77 | typeof x; // "bigint" 78 | ``` 79 | 80 | ### NaN & isNaN 81 | 82 | It is not not-number, it is an invalid number. 83 | 84 | ```javascript 85 | var myAge = Number("0o46"); // 38 86 | var myNextAge = Number("39"); // 39 87 | 88 | var myCatsAge = Number("n/a"); // NaN 89 | myAge - "my sons age"; // NaN 90 | 91 | myCatsAge === myCatsAge; // false OOPS 92 | 93 | isNaN(myAge); // false 94 | isNaN(myCatsAge); // true 95 | isNaN("my sons age"); // true OOPS 96 | 97 | Number.isNaN(myCatsAge); // true 98 | Number.isNaN("my sons age"); // false 99 | ``` 100 | 101 | ### Negative zero 102 | 103 | ```js 104 | var trendRate = -0; 105 | trendRate === -0; // true 106 | 107 | trendRate.toString(); // "0" 108 | trendRate === 0; // true 109 | trendRate < 0; // false 110 | trendRate > 0; // false 111 | 112 | // Check whether two values are the same value 113 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 114 | 115 | Object.is(trendRate, -0); // true 116 | Object.is(trendRate, 0); // false 117 | 118 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign 119 | Math.sign(-3); // -1 120 | Math.sign(3); // 1 121 | Math.sign(-0); // -0 ??? 122 | Math.sign(0); // 0 ??? 123 | ``` 124 | 125 | Fix Math.sign: 126 | 127 | ```javascript 128 | function sign(v) { 129 | return v !== 0 ? Math.sign(v) : Object.is(v, -0) ? -1 : 1; 130 | } 131 | 132 | Math.sign(-3); // -1 133 | Math.sign(3); // 1 134 | Math.sign(-0); // -1 135 | Math.sign(0); // 1 136 | 137 | function formatTrend(trendRate) { 138 | var direction = trendRate < 0 || Object.is(trendRate, -0) ? "↓" : "↑"; 139 | 140 | return `${direction} ${Math.abs(trendRate)}`; 141 | } 142 | 143 | formatTrend(-3); // "↓ 3" 144 | formatTrend(3); // "↑ 3" 145 | formatTrend(-0); // "↓ 0" 146 | formatTrend(0); // "↑ 0" 147 | ``` 148 | 149 | ### Fundamental objects 150 | 151 | When to use `new` keyword: 152 | 153 | ```js 154 | Object(); 155 | Array(); 156 | Function(); 157 | Date(); 158 | RegExp(); 159 | Error(); 160 | ``` 161 | 162 | When not to use `new` keyword? 163 | 164 | ```js 165 | String(); 166 | Number(); 167 | Boolean(); 168 | ``` 169 | 170 | ### Coercion 171 | 172 | Algorithms in JS are largely recursive. 173 | 174 | #### `toString()` 175 | 176 | ```javascript 177 | var negativeZero = -0; 178 | negativeZero.toString(); // "0" 179 | 180 | var x = []; 181 | x.toString(); // '' 182 | 183 | var y = ["..."]; 184 | y.toString(); // '...' 185 | 186 | var z = [null, undefined]; 187 | z.toString(); // ',' 188 | 189 | var obj = {}; 190 | obj.toString(); // '[object Object]' 191 | ``` 192 | 193 | #### `Number` 194 | 195 | ```javascript 196 | var num1 = ""; 197 | Number(num1); // 0 198 | 199 | var num2 = " 009 "; 200 | Number(num2); // 9 201 | 202 | var num3 = null; 203 | Number(num3); // 0 204 | 205 | var num4 = undefined; 206 | Number(num4); // NaN 207 | ``` 208 | 209 | #### Cases of coercion 210 | 211 | ```javascript 212 | var num = 16; 213 | 214 | // Implicit coercion 215 | console.log(`There are ${num} dwarves in the garden`); 216 | // 'There are 16 dwarves in the garden' 217 | 218 | // Explicit coercion 219 | console.log(`There are ${String(num)} dwarves in the garden`); 220 | ``` 221 | 222 | ### Equality 223 | 224 | - `==` allows coercion (types are different) 225 | - `===` disallows coercion (types are same) 226 | 227 | Coercive equality prefers numeric comparison. 228 | 229 | Avoid: 230 | 231 | - `==` with `0` or `""` (or even `" "`) 232 | - `==` with non-primitives 233 | - `== true` or `== false` (allow `ToBoolean` or use `===`) 234 | 235 | If you know the types, it's ok to use `==`. 236 | 237 | ### Scope 238 | 239 | - scope = where to look for things 240 | - Javascript organizes scopes with functions and blocks (ES6). 241 | 242 | ```javascript 243 | var teacher = "Kyle"; 244 | 245 | function otherClass() { 246 | var teacher = "Suzy"; 247 | console.log("Welcome"); // console is an "auto-global" 248 | } 249 | 250 | function ask() { 251 | var question = "Why?"; 252 | console.log(question); 253 | } 254 | 255 | otherClass(); // Welcome! 256 | ask(); // Why? 257 | ``` 258 | 259 | 1. `ask()` 260 | 2. asks global scope for identifier called `ask()` 261 | 3. `()` of `ask()` executes the function 262 | 4. `var question` - target reference for identifier `question` 263 | 5. `console.log(question)` - asks scope of function `ask()` for identifier question 264 | 265 | #### Dynamic global variables 266 | 267 | ```javascript 268 | var teacher = "Kyle"; 269 | 270 | function otherClass() { 271 | teacher = "Suzy"; 272 | topic = "React"; // Creates an auto-global declaration dynamically at runtime 273 | console.log("Welcome"); 274 | } 275 | 276 | otherClass(); 277 | 278 | teacher; // Suzy, reassigned at line 36 279 | topic; // React 280 | ``` 281 | 282 | #### Strict mode 283 | 284 | ```javascript 285 | ("use strict"); 286 | 287 | var teacher = "Kyle"; 288 | 289 | function otherClass() { 290 | teacher = "Suzy"; 291 | topic = "React"; // ReferenceError: topic is not defined 292 | 293 | console.log("Welcome"); 294 | } 295 | 296 | otherClass(); 297 | ``` 298 | 299 | #### Nested scope 300 | 301 | ```javascript 302 | var teacher = "Kyle"; 303 | 304 | function otherClass() { 305 | var teacher = "Suzy"; 306 | 307 | function ask(question) { 308 | console.log(teacher, question); 309 | } 310 | 311 | ask("Why?"); 312 | } 313 | 314 | otherClass(); // Suzy Why? 315 | ask("????"); // ReferenceError 316 | ``` 317 | 318 | ### Scope & function expressions 319 | 320 | #### Function declaration and expression 321 | 322 | ```javascript 323 | // Function declaration 324 | function teacher() {} 325 | 326 | // Function expression 327 | var myTeacher = function anotherTeacher() { 328 | console.log(anotherTeacher); 329 | }; 330 | 331 | console.log(teacher); 332 | console.log(myTeacher); 333 | console.log(anotherTeacher); // ReferenceError 334 | 335 | // Anonymous function expression 336 | var anonFooExp = function () {}; 337 | 338 | // Named function expression 339 | var namedFooExp = function foo() {}; 340 | ``` 341 | 342 | - Prefer named function expressions because: 343 | - Reliable function self-reference 344 | - More debuggable stack traces 345 | - More self-documenting code 346 | 347 | #### Arrow functions 348 | 349 | ```javascript 350 | var ids = people.map((person) => person.id); 351 | 352 | var ids = people.map(function getId(person) { 353 | return person.id; 354 | }); 355 | ``` 356 | 357 | Choose: 358 | 359 | 1. named function declaration _over_ 360 | 2. named function expression _over_ 361 | 3. anonymous function expression. 362 | 363 | ### Advanced scope 364 | 365 | #### Lexical scope 366 | 367 | Think of lexical scope in terms of the compiler. Decided at compile time, not run time. 368 | 369 | #### Function scoping 370 | 371 | - Use the defensive approach of "minimum exposure" 372 | - Hide things from the global scope as much as possible 373 | 374 | #### IIFE 375 | 376 | ```javascript 377 | var teacher = "Kyle"; 378 | 379 | (function anotherTeacher() { 380 | var teacher = "Suzy"; 381 | console.log(teacher); // Suzy 382 | })(); 383 | 384 | console.log(teacher); // Kyle 385 | ``` 386 | 387 | #### Block scoping 388 | 389 | ```javascript 390 | var teacher = "Kyle"; 391 | 392 | { 393 | let teacher = "Suzy"; 394 | console.log(teacher); // Suzy 395 | } 396 | 397 | console.log(teacher); // Kyle 398 | ``` 399 | 400 | #### `let` and `var` 401 | 402 | ```javascript 403 | function repeat(fn, n) { 404 | var result; // Used in the entire function scope 405 | 406 | // let used for localized usage 407 | for (let i = 0; i < n; i++) { 408 | result = fn(result, i); 409 | } 410 | 411 | return result; 412 | } 413 | ``` 414 | 415 | #### const 416 | 417 | ```javascript 418 | const myTeacher = teacher; 419 | myTeacher = "Suzy"; // TypeError 420 | 421 | const teachers = ["Kyle", "Suzy"]; 422 | teachers[1] = "Brian"; 423 | teachers; // [ 'Kyle', 'Brian' ] 424 | ``` 425 | 426 | #### Hoisting 427 | 428 | ```javascript 429 | // Variable hoisting is usually bad 430 | teacher = "Kyle"; 431 | var teacher; 432 | 433 | // Function hoisting is pretty useful 434 | getTeacher(); 435 | 436 | function getTeacher() { 437 | return teacher; 438 | } 439 | ``` 440 | 441 | ### Closure 442 | 443 | _Closure is when a function "remembers" its lexical scope even when the function is executed outside that lexical scope._ 444 | 445 | ```javascript 446 | function ask(question) { 447 | return function holdYourQuestion() { 448 | console.log(question); 449 | }; 450 | } 451 | 452 | var myQuestion = ask("What is a closure?"); 453 | 454 | myQuestion(); // What is a closure? 455 | ``` 456 | 457 | You close over variables, not values - preserves access to variables. 458 | 459 | ```javascript 460 | var teacher = "Kyle"; 461 | 462 | var myTeacher = function () { 463 | console.log(teacher); // "Suzy" 464 | }; 465 | 466 | teacher = "Suzy"; 467 | 468 | myTeacher(); // "Suzy" 469 | ``` 470 | 471 | #### Module pattern 472 | 473 | Module pattern requires encapsulation. You can't have a module without closure. 474 | 475 | ```javascript 476 | var workshop = (function Module(teacher) { 477 | var publicAPI = { ask }; 478 | return publicAPI; 479 | 480 | function ask(question) { 481 | console.log(teacher, question); 482 | } 483 | })("Kyle"); 484 | 485 | workshop.ask("It's a module, right?"); 486 | // 'Kyle' 'It\'s a module, right?' 487 | ``` 488 | 489 | #### ES6 modules 490 | 491 | ```javascript 492 | var teacher = "Kyle"; 493 | 494 | export default function ask(question) { 495 | console.log(teacher, question); 496 | } 497 | ``` 498 | 499 | #### ES6 module syntax 500 | 501 | ```javascript 502 | // workshop.mjs 503 | 504 | var teacher = "Kyle"; 505 | 506 | export default function ask(question) { 507 | console.log(teacher, question); 508 | } 509 | ``` 510 | 511 | ```javascript 512 | import ask from "workshop.mjs"; 513 | 514 | ask("It's a default import, right"); 515 | // 'Kyle' 'It\'s a default import, right?' 516 | ``` 517 | 518 | ```javascript 519 | import * as workshop from "workshop.mjs"; 520 | 521 | workshop.ask("It's a namespace import, right?"); 522 | // 'Kyle' 'It\'s a namespace import, right?' 523 | ``` 524 | 525 | ### Objects 526 | 527 | #### `this` 528 | 529 | A function's `this` references the execution context for that call, determined entirely by how the function was called. 530 | 531 | ```javascript 532 | function ask(question) { 533 | console.log(this.teacher, question); 534 | } 535 | 536 | function otherClass() { 537 | var myContext = { 538 | teacher: "Suzy", 539 | }; 540 | 541 | ask.call(myContext, "Why?"); 542 | } 543 | 544 | otherClass(); // 'Suzy' 'Why?' 545 | ``` 546 | 547 | #### Implicit & explicit binding 548 | 549 | ```javascript 550 | // Implicit binding 551 | 552 | var workshop = { 553 | teacher: "Kyle", 554 | 555 | ask(question) { 556 | console.log(this.teacher, question); 557 | }, 558 | }; 559 | 560 | workshop.ask("What is implicit binding?"); // 'Kyle' 'What is implicit binding?' 561 | 562 | function ask(question) { 563 | console.log(this.teacher, question); 564 | } 565 | 566 | var workshop1 = { 567 | teacher: "Kyle", 568 | ask: ask, 569 | }; 570 | 571 | var workshop2 = { 572 | teacher: "Suzy", 573 | ask: ask, 574 | }; 575 | 576 | workshop1.ask("How do I share a method?"); // 'Kyle' 'How do I share a method?' 577 | workshop2.ask("How do I share a method?"); // 'Suzy' 'How do I share a method?' 578 | ``` 579 | 580 | ```javascript 581 | // Explicit binding 582 | 583 | function ask(question) { 584 | console.log(this.teacher, question); 585 | } 586 | 587 | var workshop1 = { 588 | teacher: "Kyle", 589 | ask: ask, 590 | }; 591 | 592 | var workshop2 = { 593 | teacher: "Suzy", 594 | ask: ask, 595 | }; 596 | 597 | ask.call(workshop1, "How do I share a method?"); // 'Kyle' 'How do I share a method?' 598 | 599 | ask.call(workshop2, "How do I share a method?"); // 'Suzy' 'How do I share a method?' 600 | ``` 601 | 602 | ```javascript 603 | // Hard binding 604 | 605 | var workshop = { 606 | teacher: "Kyle", 607 | 608 | ask(question) { 609 | console.log(this.teacher, question); 610 | }, 611 | }; 612 | 613 | setTimeout(workshop.ask, 10, "Lost this?"); 614 | // undefined 'Lost this?' 615 | 616 | setTimeout(workshop.ask.bind(workshop), 10, "Hard bound this?"); 617 | // 'Kyle' 'Hard bound this?' 618 | ``` 619 | 620 | #### Arrow functions & lexical `this` 621 | 622 | An arrow function is `this`-bound aka `.bind()` to its parent function. 623 | 624 | ```javascript 625 | var workshop = { 626 | teacher: "Kyle", 627 | 628 | ask(question) { 629 | setTimeout(() => { 630 | console.log(this.teacher, question); 631 | }, 100); 632 | }, 633 | }; 634 | 635 | workshop.ask("Is this lexical 'this'?"); 636 | // 'Kyle' 'Is this lexical \'this\'?' 637 | ``` 638 | 639 | #### Resolving this in arrow functions 640 | 641 | ```javascript 642 | // workshop is not a scope! 643 | 644 | var workshop = { 645 | teacher: "Kyle", 646 | 647 | ask: (question) => { 648 | console.log(this.teacher, question); 649 | }, 650 | }; 651 | 652 | workshop.ask("What happened to 'this'?"); 653 | // TypeError: Cannot read property 'teacher' of undefined 654 | 655 | workshop.ask.call(workshop, "Still no 'this'?"); 656 | // TypeError: Cannot read property 'teacher' of undefined 657 | ``` 658 | 659 | Only use arrow functions when you need lexical `this`. 660 | 661 | #### ES6 `class` keyword 662 | 663 | ```javascript 664 | class Workshop { 665 | constructor(teacher) { 666 | this.teacher = teacher; 667 | } 668 | 669 | ask(question) { 670 | console.log(this.teacher, question); 671 | } 672 | } 673 | 674 | var deepJS = new Workshop("Kyle"); 675 | var reactJS = new Workshop("Suzy"); 676 | 677 | deepJS.ask("Is 'class' a class?"); 678 | // 'Kyle' 'Is \'class\' a class?' 679 | 680 | reactJS.ask("Is this class OK?"); 681 | // 'Suzy' 'Is this class OK?' 682 | 683 | class AnotherWorkshop extends Workshop { 684 | speakUp(msg) { 685 | this.ask(msg); 686 | } 687 | } 688 | 689 | var JSRecentParts = new AnotherWorkshop("Kyle"); 690 | 691 | JSRecentParts.speakUp("Are classes getting better?"); 692 | // 'Kyle' 'Are classes getting better?' 693 | 694 | class AnotherWorkshop1 extends Workshop { 695 | ask(msg) { 696 | super.ask(msg.toUpperCase()); 697 | } 698 | } 699 | 700 | var JSRecentParts1 = new AnotherWorkshop("Kyle"); 701 | 702 | JSRecentParts1.ask("Are classes super?"); 703 | // 'Kyle' 'Are classes super?' 704 | ``` 705 | 706 | ```javascript 707 | class Workshop { 708 | constructor(teacher) { 709 | this.teacher = teacher; 710 | } 711 | 712 | ask(question) { 713 | console.log(this.teacher, question); 714 | } 715 | } 716 | 717 | var deepJS = new Workshop("Kyle"); 718 | 719 | setTimeout(deepJS.ask, 100, "Still losing 'this?"); 720 | // undefined 'Still losing \'this?' 721 | ``` 722 | 723 | ```javascript 724 | class Workshop { 725 | constructor(teacher) { 726 | this.teacher = teacher; 727 | 728 | this.ask = (question) => { 729 | console.log(this.teacher, question); 730 | }; 731 | } 732 | 733 | ask(question) { 734 | console.log(this.teacher, question); 735 | } 736 | } 737 | 738 | var deepJS = new Workshop("Kyle"); 739 | 740 | setTimeout(deepJS.ask, 100, "Is 'this' fixed?"); 741 | // 'Kyle' 'Is \'this\' fixed?' 742 | ``` 743 | 744 | ### Prototypes 745 | 746 | A "constructor call" makes an object its own prototype. 747 | 748 | #### Prototypal class 749 | 750 | ```javascript 751 | function Workshop(teacher) { 752 | this.teacher = teacher; 753 | } 754 | 755 | Workshop.prototype.ask = function (question) { 756 | console.log(this.teacher, question); 757 | }; 758 | 759 | var deepJS = new Workshop("Kyle"); 760 | var reactJS = new Workshop("Suzy"); 761 | 762 | deepJS.ask("Is 'prototype' a class?"); 763 | // 'Kyle' 'Is \'prototype\' a class?' 764 | 765 | reactJS.ask("Isn't 'prototype' ugly?"); 766 | // 'Suzy' 'Isn\'t \'prototype\' ugly?' 767 | ``` 768 | 769 | #### Dunder prototypes 770 | 771 | ```javascript 772 | function Workshop(teacher) { 773 | this.teacher = teacher; 774 | } 775 | 776 | Workshop.prototype.ask = function (question) { 777 | console.log(this.teacher, queueMicrotask); 778 | }; 779 | 780 | var deepJS = new Workshop("Kyle"); 781 | 782 | deepJS.constructor === Workshop; // true 783 | 784 | deepJS.__proto__ === Workshop.prototype; // true 785 | 786 | Object.getPrototypeOf(deepJS) === Workshop.prototype; // true 787 | ``` 788 | 789 | #### Shadowing prototypes 790 | 791 | ```javascript 792 | function Workshop(teacher) { 793 | this.teacher = teacher; 794 | } 795 | 796 | Workshop.prototype.ask = function (question) { 797 | console.log(this.teacher, queueMicrotask); 798 | }; 799 | 800 | var deepJS = new Workshop("Kyle"); 801 | 802 | deepJS.ask = function (question) { 803 | this.ask(question.toUpperCase()); 804 | }; 805 | 806 | deepJS.ask("Oops, is this infinite recursion?"); 807 | // RangeError: Maximum call stack size exceeded 808 | 809 | function Workshop(teacher) { 810 | this.teacher = teacher; 811 | } 812 | 813 | Workshop.prototype.ask = function (question) { 814 | console.log(this.teacher, question); 815 | }; 816 | 817 | var deepJS = new Workshop("Kyle"); 818 | 819 | deepJS.ask = function (question) { 820 | this.__proto__.ask.call(this, question.toUpperCase()); 821 | }; 822 | 823 | deepJS.ask("Is this fake polymorphism?"); 824 | // 'Kyle' 'IS THIS FAKE POLYMORPHISM?' 825 | ``` 826 | 827 | #### Prototypal inheritance 828 | 829 | ```javascript 830 | function Workshop(teacher) { 831 | this.teacher = teacher; 832 | } 833 | 834 | Workshop.prototype.ask = function (question) { 835 | console.log(this.teacher, question); 836 | }; 837 | 838 | function AnotherWorkshop(teacher) { 839 | Workshop.call(this, teacher); 840 | } 841 | 842 | AnotherWorkshop.prototype = Object.create(Workshop.prototype); 843 | 844 | AnotherWorkshop.prototype.speakUp = function (msg) { 845 | this.ask(msg.toUpperCase()); 846 | }; 847 | 848 | var JSRecentParts = new AnotherWorkshop("Kyle"); 849 | 850 | JSRecentParts.speakUp("Is this actually inheritance?"); 851 | // 'Kyle' 'IS THIS ACTUALLY INHERITANCE?' 852 | ``` 853 | 854 | #### OLOO (Objects Linked to Other Objects) pattern 855 | 856 | ```javascript 857 | // ES6 class pattern 858 | class Workshop { 859 | constructor(teacher) { 860 | this.teacher = teacher; 861 | } 862 | 863 | ask(question) { 864 | console.log(this.teacher, question); 865 | } 866 | } 867 | 868 | class AnotherWorkshop extends Workshop { 869 | speakUp(msg) { 870 | this.ask(msg); 871 | } 872 | } 873 | 874 | var JSRecentParts = new AnotherWorkshop("Kyle"); 875 | 876 | JSRecentParts.speakUp("Are classes getting better?"); 877 | 878 | // OLOO pattern 879 | var Workshop = { 880 | setTeacher(teacher) { 881 | this.teacher = teacher; 882 | }, 883 | 884 | ask(question) { 885 | console.log(this.teacher, question); 886 | }, 887 | }; 888 | 889 | var AnotherWorkshop = Object.assign(Object.create(Workshop), { 890 | speakUp(msg) { 891 | this.ask(msg.toUpperCase()); 892 | }, 893 | }); 894 | 895 | var JSRecentParts = Object.create(AnotherWorkshop); 896 | 897 | JSRecentParts.setTeacher("Kyle"); 898 | 899 | JSRecentParts.speakUp("But isn't this cleaner?"); 900 | //'Kyle' 'BUT ISN\'T THIS CLEANER?' 901 | ``` 902 | --------------------------------------------------------------------------------